728x90
반응형

Go로 REST API 서버를 만들어서 사용하는데 특정 API의 응답 시간이 오래 걸리는 경우가 있었다.

 

처음에 어떤 문제로 오래 걸리는지 파악이 되지 않아 캐시를 사용했지만 계속 발생했었다.

 

데이터베이스 조회에 걸리는 시간을 측정해보니 여기서 오래 걸리는 것을 확인할 수 있었다.

 

오래 걸리는 이유는 데이터베이스 커넥션 풀과 관련한 설정 문제인 것 같아 문서를 읽어보았다.

 

SetMaxIdleConns 설정값을 SetMaxOpenConns 설정값보다 크거나 같게 설정하는 것이 좋다고 써있었다.

 

SetMaxIdleConns 함수는 유휴 상태 커넥션 풀에서 최대 연결 수로 적절한 값을 설정했다.

 

SetMaxOpenConns도 기본값인 무제한으로 사용하고 있어 크게 문제가 없어 보였지만

 

SetMaxIdleConns의 설정값과 비슷하게 하는 것이 좋다고 해서 같은 값으로 설정했다.

 

며칠을 지켜보니 응답 시간이 오래 걸리는 경우가 더이상 발생하지 않았다.

 

참고 문헌

  1. golang.org/pkg/database/sql/#DB.SetMaxIdleConns

  2. golang.org/pkg/database/sql/#DB.SetMaxOpenConns

  3. github.com/go-sql-driver/mysql

반응형
728x90
반응형

서버 장애가 나서 panic이 발생했다는 알람이 많이 왔었다.

 

원인은 데이터베이스가 다운돼 쿼리를 실행할 수 없어 발생한 장애였다.

 

코드를 확인해보니 에러를 확인하고 에러가 없는 경우에 Close 함수를 실행해야 했는데 

 

항상 Close 함수를 실행을 하여 rows가 nil 값인데도 참조하여 발생한 문제였다.

rows, err := db.Query(query, args)
if err != nil {
    return
}
defer rows.Close()
...

그래서 에러를 확인하고 Close 함수를 실행하도록 코드를 수정했다.

혹시 몰라 일정 시간 이상 응답이 없으면 에러가 발생하도록 하는 코드도 추가했다.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, query, args)
if err != nil {
    return
}
defer rows.Close()
...

 

참고 문헌

  1. https://golang.org/pkg/database/sql/#DB.ExecContext

  2. https://golang.org/pkg/database/sql/#Conn.QueryContext

  3. https://golang.org/pkg/database/sql/#DB.PingContext

반응형
728x90
반응형

Go 프로젝트에서 http 클라이언트를 만들어서 활용하고 있었는데 어느날 서버에 응답이 없었다.

 

그래서 서버에 들어가서 확인해보니 too many open files라는 에러가 나고 있었다.

 

netstat으로 확인해보니 CLOSE_WAIT 상태로 많이 쌓여 있었다.

 

어디가 문제인가 살펴보니 defer resp.Body.Close() 를 하는데 상태값이 200인 경우에만 닫도록 돼있었다.

 

(문서에서 Body를 읽은 경우엔 항상 닫아주라고 써있다.)

resp, err := http.Get("http://example.com/")
if err != nil {
    return
} else if resp.StatusCode != 200 {
    return
}
defer resp.Body.Close()

다음과 같이 수정한 이후 경과를 살펴보니 잘 동작하는 것을 확인할 수 있었다.

resp, err := http.Get("http://example.com/")
if err != nil {
    return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
    return
}

 

참고 문헌

  1. https://golang.org/pkg/net/http/

반응형
728x90
반응형

echo 프레임워크로 파일 업로더 서버를 구축했다.

 

쿡북에서 단일 파일, 여러 파일을 업로드하는 예제가 있어 이를 바탕으로 쉽게 만들 수 있었다.

 

하지만 업로드를 하는 클라이언트에서 폼의 키값을 다른 것으로 하는 경우를 고려해야 했다.

 

그래서 여러 파일을 업로드하는 코드를 참고하여 입맛에 맞게 수정했다.

func upload(c echo.Context) error {
    form, err := c.MultipartForm()
    if err != nil {
        return err
    }

    for _, file := range form.File {
        // Source
        src, err := file[0].Open()
        if err != nil {
            return err
        }
        defer src.Close()

        // Destination
        dst, err := os.Create(file[0].Filename)
        if err != nil {
            return err
        }
        defer dst.Close()

        // Copy
        if _, err = io.Copy(dst, src); err != nil {
            return err
        }
    }
}

그리고 용량이 큰 파일을 업로드하는 경우에 디스크 사용량이 파일 사이즈의 2배가 늘어났다.

 

원인을 찾아보니 go에서 설정한 메모리(32 MB + 10MB)보다 큰 경우 /tmp/에 파일을 저장하고 있었다.

 

다음과 같이 하면 파일을 원하는 곳에 복사한 다음 임시 파일을 모두 제거할 수 있다.

func upload(c echo.Context) error {
    defer func() {
        form, err := ctx.MultipartForm()
	    if err != nil {
    	    return
        }
    	form.RemoveAll()
    }()
    ...
}

 

참고 문헌

  1. https://echo.labstack.com/cookbook/file-upload
  2. https://github.com/labstack/echo/blob/master/context.go#L369
  3. https://github.com/golang/go/blob/master/src/mime/multipart/formdata.go#L86
반응형

+ Recent posts