-
OkHttp VS Apache HttpClientcoding 2022. 2. 18. 17:21
새벽에 알림이 울렸다.
불길하다.
예감이 맞다.
장애가 발생했다.
허겁지겁 PC를 켜고 여기 저기 살펴봤다.
로그를 보고 깜짝 놀랐다.
예상과 달리 OkHttp에서 에러가 발생했다.
난 많이 쓰는 오픈소스라 안정성이 높을 거라 생각했다.
역시 버그는 나의 방심을 절묘하게 찾아 공격한다.
아~~ 머리 아프다!! 다시 도를 닦는 순간이다.
새로 개발하는 기능에 Okhttp를 도입했다. 기존 코드는 모두 Apache HttpClient로 개발했다. 내가 OkHttp를 도입한 이유는 이렇다.
- 사용법이 단순하다.
- 안드로이드에서 많이 쓰니 충분한 검증
- 왠지 대세를 안 따라가면 뒤쳐지는 느낌적 느낌
운영중 서비스는 고객사들의 상품을 크롤링하는 백엔드 시스템이다. 대용량 Http 요청이 기본이다. Apache HttpClient를 쓸 때마다 사용법이 헷갈린다. 매번 샘플 코드를 보고 코딩한다. 내 머리가 나쁜건지 아파치가 나쁜건지. 아파치 탓을 하고 싶다. 😃😃
okhttp를 보자 마자 맘이 넘어갔다. 단순해서 좋았다. 이미 내 마음에선 바꿔야할 이유가 스물 스물 올라왔다. 안 바꿀 이유는 저 멀리 사라졌다. 이미 내 손은 새로운 프로젝트를 만들고 코딩을 하고 있다.
장애의 원인은 OkHttp의 버그였다. OkHttp가 Proxy를 통해 요청하면 가끔 에러가 났다. 매번 났다면 진작 알았을거다. 이런 버그가 참 힘들다. 마치 내가 방심하길 기다렸다가 짠~ 하고 나타나는 느낌이다. 버그픽스 됐다는 릴리즈노트를 확인하고 패치를 했다.
여기서 끝나면 이야기거리가 안 된다. 장애가 다시 발생했다. 같은 문제다. 우리 상황이 특별한 경우인 거 같다. 결국 Apache HttpClient로 다시 코딩했다. 아쉬웠지만 새벽에 장애 알람을 받는 일을 다시 겪고 싶지 않다.
다시 장애가 발생했다. 설마 또 같은 문제인가 막막했다. 순간 더 시도해 볼 아이디어가 떠오르지 않았다.
다행히 OkHttp의 Proxy 문제는 아니다. OOM ( Out of Memory) 발생!!
머리에 지진이 났다. 메모리 릭이 있는 건가, 라이브러리 문제인가. 프로파일링을 하고 싶어도 라이브 상태와 맞추기가 어려웠다. 모니터링을 해보니 OkHttp가 메모리를 너무 많이 써서 문제가 발생했다.
엄청 많이 쓰는 오픈소스인데 그럴리가 없었다. 내가 멀 잘 못 사용했나 의심했다. 구글링, 공식 문서 샅샅이 찾아봤다. 단서가 안 보인다.
비슷한 조건을 만들어서 프로파일링을 해봤다. 이상한 점을 발견했다. 힙 메모리는 큰 문제가 없는데 프로세스 메모리가 너무 컸다. NIO Direct Buffer가 떠올랐다. NIO Direct Buffer는 heap에 안 잡혀서, 프로파일링을 해도 찾기가 매우 힘들다. OkHttp 소스코드를 들여다 봤다.
OkHttp 핵심 코드를 보니 내부에 NIO Direct Buffer를 한번 얼로캐이션해서 재사용한다. 생성 오버헤드를 없애니 빠를 수밖에 없다. 게다가 다이렉트 버퍼는 JVM Heap을 쓰는 게 아니고 메모리를 직접 할당하는 거라 빠르다. C/C++과 비슷하다. 좋은 아키텍쳐다. 대신 완성도를 올리기 힘들다. 자원을 재사용을 하면 코드가 Stateful 해지고 버그도 늘어난다.혹시 Direct Buffer Pool을 슛다운 할 수있나 찾아봤지만 못 찾았다. http request 갯수를 적절히 조절해서 메모리 풀이 늘어나지 않게 할 수밖에 없다. 우리는 대용량 요청을 하는 상황이다. 난감했다. 아무리 OkHttp Client를 여러개 생성해도 버퍼 메모리는 한 곳으로 모인다. 내부에 Direct Buffer Pool이 싱글턴 클래스라 인스턴스가 오직 하나다. 메모리가 증가만 하는 상황이다.
결국 OkHttp의 Direct Buffer Pool은 증가만한다. GC를 해서 메모리 회수를 할 수 없는 구조다. 내려가는 방법이 없다. 내리는 방법은 프로세스를 중지하는 것뿐이다. OkHttp를 클라이언트에 사용하면 문제는 없다. 어차피 프로세스가 죽었다 살았다 한다. 죽을 때 메모리가 회수 된다.
걱정마시라. OkHttp가 웬만한 요청수라면 비교적 적은 메모리만 사용한다. 이런 문제가 생기려면 시스템이 다운 될 정도, 노트북의 Wifi가 다운 될 정도로 Request를 보내야 한다. 이 테스트를 할 때는 다른 작업을 할 수 없다. 핑계로 쉬는 시간이다!!
이미 OkHttp를 사용한 소스코드가 너무 불어나서 뒤로 돌아가기 힘들었다. OkHttp 소스코드를 보며 이런 저런 옵션을 사용해서 임시방편으로 땜질해 놨다. 마음 한켠이 불편하다. 좀만 더 하면 될 거 같았다. 그러나 착각이다. 잘못 건드리면 몇달 고생할 수도있다. 릴리즈는 해야 하는데 멋진 코드는 아니다. 인생이 언제 아름다운 적이 있는가. 최선은 아니라도 전진했으면 됐다며 위안을 삼아본다. 근데 가만 보니 코드가 후진한 거 같다.
서버의 CPU, Memory를 쥐어 짜서 최대 성능을 내도록 만들었다. 미봉책으로 다운 튜닝해서 올려놨다. 결국 Apache HttpClient와 OkHttp를 함게 사용하고 있다. 동시 사용하면서 가끔 애먹었다. 경험은 늘었지마 생명이 줄어든 거 같다.
OkHttp
- 초당 요청수에 비해 적은 메모리를 사용
- 한번 생성하고 나면 매우 빠르다.
- 웬만한 요청수에 적합
- 프로세스가 실행했다 죽었다 하는 클라이언트에 적합
- 다이렉트 버퍼를 쓰는 부분은 프로파일링 불가능
Apache HttpClient
- 요청수에 비례하여 메모리 사용량도 증가한다. (정비례 아님)
- 힙을 사용하기 때문에 GC가 된다.
- 대용량 처리 가능
- 힙을 쓰기 때문에 프로파일링 가능
- 실행하고 오래 떠있는 경우에 적합
정리하면 이렇다. 상황에 맞는 적절한 선택을 하길 바란다. 팍팍 늙는 경험은 나 하나면 충분하다. 그래도 꼭 고생을 하고 싶다면 응원한다. 직접 닥쳐 보는 게 공부는 잘 된다.
.
.
.
.
얼마 지나서 OkHttp 다이렉트 버퍼 풀을 해결할 단서를 찾았다.
다음에….😎
'coding' 카테고리의 다른 글
Meta programming (0) 2015.07.21