Avatar
회사에서는
Todd

Cybertramp

아래에서 위로

해결하기 - too many open files; retrying in 1s

글을 쓴날: 2021-11-07

Go로 http 라이브러리를 사용하여 API 서버를 만들었으나, 테스트로 1일 동안 여러 장비에서 API를 주고 받았는데, 문제가 생겨있었다.


2021/10/30 04:26:49 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:50 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:51 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:52 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:53 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:54 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:55 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s
2021/10/30 04:26:56 http: Accept error: accept tcp [::]:4547: accept4: too many open files; retrying in 1s

파일을 너무 많이 열어서 문제가 생긴건데, 원인이 뭔지 몰랐다. 아마 각 장비에서 날아오는 요청이 닫히지 않고 계속 열려서 문제가 생긴듯 했다.

리눅스에 열수 있는 파일 개수 제한은 분명 무제한이 되어 있으나, 아마 메모리나 용량을 넘어서 발생한게 아닐까 생각이 들었다.


[opc@dq-mon dq_mon]$ free
              total        used        free      shared  buff/cache   available
Mem:         823260      182832      252828       10848      387600      623816
Swap:       8388604           0     8388604
[opc@dq-mon dq_mon]$ df -h
Filesystem                     Size  Used Avail Use% Mounted on
devtmpfs                       357M     0  357M   0% /dev
tmpfs                          402M     0  402M   0% /dev/shm
tmpfs                          402M   11M  392M   3% /run
tmpfs                          402M     0  402M   0% /sys/fs/cgroup
/dev/mapper/centosvolume-root   40G   11G   29G  28% /
/dev/sda2                     1014M  557M  458M  55% /boot
/dev/sda1                      100M  7.3M   93M   8% /boot/efi
tmpfs                           81M     0   81M   0% /run/user/987
tmpfs                           81M     0   81M   0% /run/user/1000
[opc@dq-mon dq_mon]$ ulimit
unlimited

이 문제는 확인해보니 클라이언트 측에서 Close를 해주지 않아 발생한 문제이다. Client 측에 다음과 같이 적용하였더니 lsof | wc -l에서 값이 증가하지 않는 것을 확인하였다.

resp, _ := http.Post(url, "application/json", buff)
resp.Body.Close()

http에서는 3 Way Hand shaking을 하고 서로 연결을 하는데, HTTP/1.1 부터 Keep Alive라는 기능을 통해 Hand shaking을 건너띄는 것이 있다. 이로 인해 발생하는 것으로 보인다.

사용중 다시 문제가 있었다. 서버가 꺼져있는 경우 Service가 정상적으로 동작하지 않으며, Fail로 꺼져버리는 문제가 확인됬다.

Golang - http.Client에는 Timeout이 들어가야 한다. (tistory.com)

Golang의 Http 패키지 잘 사용하기. 최근에 서비스에 http 호출 클라이언트를 개발하며 net/http… | by Daniel | 센트비 기술 블로그 | Medium

HTTP request timeouts in Go for beginners | by Aditya pratap singh | ITNEXT

여러 링크들을 참조했지만 딱히 별 효과는 없었다.

2주나 삽질을 했지만 찾지 못했다가 드디어 방법을 찾았다.

참조: go http requests in loop - Stack Overflow

세가지가 문제였던것 같다.

  1. 내가 log.Fatal() 함수를 사용한 점

    이 함수는 에러 로그를 출력하고 프로그램을 완전히 종료한다… 그래서 계속 꺼졌던 것이다.

  2. defer res.Body.Close를 사용하여 서비스가 꺼지게 한 점

    defer는 보통 함수가 끝나면 동작하는 것으로 알고 있다. 여기서 말하기를 body는 읽을때 마다 닫아야하기 때문에 잘못 되었다고 한다.

  3. go routin으로 사용안한 점

    이건 책을 좀 찾아봐야했다.

    • goroutine
      • Go에서 동시에 실행할 수 있는 Go 만의 최소 실행단위를 말한다.
      • 호출할 함수 앞에 go 키워드를 붙이면, 그 함수는 즉시 return 하고 실제 동작은 background에서 Go 루틴 형태로 실행하면서 원래 수행하던 프로그램 흐름은 계속 진행된다.
      • Go 자체의 스케쥴러에 의해 제어 된다. 따라서 실행 순서는 예측할 수 없다.
      • 실행 사이즈: 프로세스 > Go 루틴 > 쓰레드

자세한 사항은 내가 좀 더 공부를 해야겠지만 잘 해결되었다.



 

comments powered by Disqus