playwright 이라는 헤드리스 브라우저로 스크래핑을 돌리는 컨테이너가 자꾸 의문사하는 현상이 발생함.
로그를 봤는데도 이건 대체 왜죽었지 싶은거임;
그냥 event emitter 걸고 req 들어올 때마다 header 긁어오는 부분에서 에러가 남
이미 닫힌 page, context, browser 에 대해서 저런 event emitter 가 돌아가고 있다는데
그렇게 코드를 안 짜서 뭐지 싶었음
그래서 일단은 의심이 가는 범인들을 한 번 추림
0번용의자. 메모리릭
1번용의자. 라이브러리(우분투 패키지든, 애플리케이션 패키지든)
경험 상 컨테이너가 죽는건 거의 OOM 맞고 뻗었던 경우가 많아서 일단은 0번으로 메모리릭을 뒀음
일단은 라이브러리부터 싹 다 업데이트 하고 이제 메모리 릭을 어떻게 잡을까
그러니까 프로파일링을 어떻게 할 거냐 고민을 했음
https://nodejs.org/en/docs/guides/diagnostics/memory/using-heap-snapshot
Memory Diagnostics - Using Heap Snapshot | Node.jsNode.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.nodejs.orgnode.js 에선 이런 heap snapshot 기능으로 프로파일링을 할 수 있긴 한데,
뭔가 컨테이너로 한번 싸놨으니 좀 편한거 없나? 라는 생각을 하고 좀 더 찾아봄
https://github.com/grafana/pyroscope
GitHub - grafana/pyroscope: Continuous Profiling Platform. Debug performance issues down to a single line of codeContinuous Profiling Platform. Debug performance issues down to a single line of code - GitHub - grafana/pyroscope: Continuous Profiling Platform. Debug performance issues down to a single line of ...github.com그러다보니 pyroscope 라는걸 알게됨
이미 grafana 에 loki + promtail 써서 로그 쌓는 중이라
컨테이너 하나 더올리고 스크래퍼 애플리케이션에 에이전트 물리면 되는 상황이어서 후다닥 구성함
근데 문제가 생김
쓰던 이미지(playwright docker 이미지)에서 pyroscope 라이브러리 설치가 안되는거임
pyroscope 가 pprof 라는 라이브러리를 내부적으로 또 사용해야 해서 추가적인 설치를 하는데,
얘의 베이스 이미지가 이거저거 경량화한 버전 기반이라 pprof 설치에 필요한 것이 없어서 그런게 아닐까 추측해봄
그래서 multistage build 로 build 따로 execution 따로 분리시켰음
build 할 때는 경량화 되지 않은 순정 node.js 이미지를 사용하고,
해당 build 의 결과를 이제 COPY --from 으로 execution stage 의 이미지에 옮겨넣음
이렇게 하니 성공적으로 잘 구동됨
그리고 그렇게 프로파일링을 한 지 하루가 지난 뒤 확인해봄
열심히 우상향 중
flame graph
왼쪽이 프로파일링 시작한 지 4시간 지난거, 오른쪽이 24시간 지난건데
playwright 점유율이 꾸준히 올라가고 있었음
잡았다 요놈
좀비 프로세스도 게속해서 양산 중이었음
내가 중간에 ... 으로 생략해서 그렇지, 약 100개 정도로 어마어마하게 많았음
이게 대체 뭔가 싶어서 defunct(zombie) process 가 뭐고, 이게 왜 생기는건가 확인해 봄
여기서부턴 잠깐 운영체제 이야
운영체제에선 어떤 process 의 child process 가 맡은 일을 끝내
커널에서 해당 child process 의 parent process 에게 SIGCHLD 라는 signal 을 보냄.
해당 signal 을 들은 process 는 wait() 을 실행하고, child process 들 중 이제 종료시켜야 할 애들은 없나 탐색을 진행하고
해당되는 process 들은 수거함
그리고 이렇게 수거된 process 들은 PID 1 인 init process 가 알아서 잘 관리해줌
갑자기 parent process 가 죽은 orphan process 들도 마찬가지로
근데 일부 docker image 엔 이런 역할을 하는 init process 가 없음
특히, slim 태그가 붙은 등의 경량화 작업을 거친 이미지들의 경우엔 높은 확률로 존재하지 않음
https://github.com/Yelp/dumb-init
GitHub - Yelp/dumb-init: A minimal init system for Linux containersA minimal init system for Linux containers. Contribute to Yelp/dumb-init development by creating an account on GitHub.github.com나말고도 이런 이유로 고통을 겪는 사람들이 많았는지 해결책도 다행히 있었음
대표적으로, 미국의 테크회사중 하나인 yelp 가 이런 문제를 해결하기 위한 dumb-init 이라는 프로세스를 만듬
왜 이런문제가 생기는지, 그래서 자기들이 이걸 왜 만들었고 어떻게 만들었는지를 잘 설명해 줘서 재밌다
README.md 에 아주 잘 나와있음
https://engineeringblog.yelp.com/2016/01/dumb-init-an-init-for-docker.html
Introducing dumb-init, an init system for Docker containersIntroducing dumb-init, an init system for Docker containersengineeringblog.yelp.comyelp 의 Engineering blog 에 적은 dumb-init 에 대한 글인데, 관심이 가는 사람은 이것도 읽어보는걸 추천함
https://github.com/krallin/tini
GitHub - krallin/tini: A tiny but valid `init` for containersA tiny but valid `init` for containers. Contribute to krallin/tini development by creating an account on GitHub.github.com* 추가로, 같은 일을 하지만 더 경량화 된 init process 인 tini 도 존재함
여튼, 위에서 소개한 dumb-init 을 설치하고
ENTRYPOINT 에 넣어줌
제발 잡혀라 기도를 하고 이틀이 지남
놀러갔다 옴
하… 애매한데?
잡히긴 잡혔는데 여전히 또 어딘가에서 줄줄 새고 있음
ps -e 로 확인해보니 좀비 프로세스는 더 이상 안만들고 있었음
그리고 여전히 메모리 점유율이 올라가는 건 playwright 라이브러리
https://github.com/microsoft/playwright/issues/15400
‘나같이 고통을 호소하는 사람이 또 있을까?’ 하는 생각에 해당 라이브러리 이슈탭에 검색을 해 봤는데, 있었다
https://github.com/microsoft/playwright/issues/15400
[Question] Why does it seem like playwright is leaking memory? · Issue #15400 · microsoft/playwrightHi! I was curious on why playwright does not seem to let some memory be garbage collected no matter how old it is. As an easy replicable example: With playwright launch chromium Make a page and mak...github.com“이건 테스트를 위한 라이브러리인 만큼 context 객체를 .close() 로 명시적으로 종료하지 않으면 테스트 디버깅 등을 위해서 정보를 계속 쌓음.
context.close() 로 끄삼”
아
추가완료
배포하고 다시 하루를 기다려봄
점점 잡혀가는데, 또 어딘가에서 새고 있었다
https://github.com/microsoft/playwright/issues/6319
[BUG] Memory increases when same context is used · Issue #6319 · microsoft/playwrightContext: Playwright Version: Latest (today is 26/044/2021) Operating System: Linux Node.js version: tested on both node.js version Browser: chromium Describe the bug I'm watching full-js apps (e.g ...github.com이슈탭을 좀 더 찾아보니, 애초에 이 문제로 고통받고 있는 사람들이 많았고 그게 1년간 지속된 모양이다
같은 context 를 사용하면 사용할수록 메모리가 증가한다는데,
얘내들도 이거 좀 잡아야겠다 생각했는지 이슈 얼리고 우선순위로 올려서 개선중이라고 하는 모습을 볼 수 있었다
그리고 저 위의 사항에 해당되는 내 코드는 이거.
네트워크 트랜잭션이 들어올 때마다 저런 일을 수행하기에, 같은 context 에서 여러 번 무언가를 반복하고 그것이 쌓이게 된다.
그래서 실제로 flame graph 를 보았을 때, req.allHeaders() 의 부분이 상위에 있었다.
이 로직은 request 가 들어올 때마다 cookie 포함 header 를 전부 갖고와서 대충 이런 저런 작업을 해왔을 때 필요했는데,
이제 더 이상 이건 안 해도 되겠다고 판단해서 그냥 제거했다.
그리고 그 결과는...
편-안
---
후기
관찰이 가능한가? 측정이 가능한가? 는 정말 중요한 문제라는 걸 깨달았다.
인스턴스 접속해서 sudo docker logs hotdeal-scrapper 로 로그 확인하기가 귀찮아서 모니터링 환경을 구축한건데,
이렇게 구축해놓으니 조금만 더 얹으면 돼서 도움이 되더라
docker-compose 로 구동중이라 restart policy 에 재부팅 적어주면 어차피 죽어도 다시 살아나니 그만이긴 한데
반복적으로 발생하는 문제는 해결하는 것이 장기적으로도 이로울 거 같고,
흥미로운 도전 과제라 생각해서 이거저거 해 본 게 큰 요 며칠이었음
그리고 부끄럽게도 여태껏 프로그램을 짜면서 메모리 문제를 겪어본 적이 없었는데,
이번에 겪어서 삽질을 좀 해본 덕에 ‘이렇게 트러블슈팅하면 되겠다’ 라는 경험 하나가 생겼다
주변에 잘하는 사람들에게 헬프콜을 쳐서 의심이 가는 핵심 원인들 위주로 찾아본 덕분도 컸다
---
기타
휴학시절에 yelp 서류 위이이잉 당했었는데, 졸업하고 나서도 다시 한 번 지원해 봐야지
너네 dumb-init 진짜 멋있었다고 이야기하면서 ㅋㅋ
---
끗
왠일로 이런 맛있는 글이
ㅆㅅㅌㅊ
잘 봤오요
개고수
멋져
고수다..
핫딜 알림 그거 하던 분이시구나 오..
이런거 펨코쪽에서 보면 막는거 아니냐?
이걸로 수익만 안 내면 법적으로 막을수가 없어
주기적인 모니터링 데이터를 쌓는게 매우 매우 중요함. 추이를 볼 수 있어야 폴트를 판별할 수 있음 난 원래 텍스트로 데이터 쌓아서 보곤 했는데, GUI 안좋아하는데도 그라파나 같은거 보면서 하면 좋더라.
이거보고 퍼페티어 쓰기로 결정했다
이건 OS 지식 응용이네 굳
선생님 질문있습니다! 글을 쓰시면서 중간중간 메모리 우상향 그래프 스크린샷 찍어두신거는 속으로 '이 문제 해결하는걸로 글써야지' 이런 생각으로 찍으신건가요!!? 저는 문제를 해결하면 거기에 집중하는데, 중간중간 스크린샷 찍는 사람들은 무슨 생각으로 저렇게 찍어두는지 궁금해서요.
ㄴㄴ 다끝낸뒤에 뭐하지 하다가 배운거 정리할겸 글써봐야겟다 해서 특정시점별로 그래프 다시보고 캡처한거임
복기군요!
선생님 근데 이건 문제를 해결한게 아니라 그냥 문제되는 부분을 없앤거 아닌가요??? 해결이라하기엔 쫌..
ㅇㅇ 맞음 마지막은 라이브러리 쪽에서도 해결하지못한 문제라 기능의사용성을 고려해서 어쩔수없이 문제되는부분을 없애는식으로 처리함 그래도 프로파일링 해보면서 init 프로세스가 없어서 init 프로세스 주입해주고, 라이브러리에서 데이터가 쌓이는걸 명시적으로 종료해준거는 문제해결이라고 할수잇다고 생각함
글 잘 읽었습니다 ㅎㅇㅌ
오 이런 맛있는글 좋다