분산 웹 캐시 ( Wcache ) 의 개선 과정 - Part 2 
 web - cache , storage 
 Part 1 : 분산 웹 캐시 에서 는 카카오 의 트래픽 을 처리 하 고 있 는 Wcache 에 대한 간략 한 소개 를 하 였 습니다 . 이전 버전 의 Wcache 는 기본 적 으로 준수 한 응답 속도 를 보이 고 있 었 지만 , metadata 를 집중 된 DB 에 저장 하 는 방식 및 기타 구조 상 의 문제 로 인한 성능 안정 성 문제 , 그리고 기능 상 의 문제 들 이 잠재 되 어 있 었 습니다 . 본 포스트 에서 는 이전 버전 의 Wcache 에서 어떠 한 구조 적 문제 가 있 었 는지 , 또한 이 를 어떻게 해결 하 였 는지 를 다룹니다 . 
 Part 2 : Wcache 저장 구조 변경 
 저장 구조 변경 - 성능 안정 화 
 기존 BigFile 저장 방식 의 문제점 
 이전 포스트 에 나와 있 듯이 Wcache 는 metadata 정보 를 컨텐츠 와 는 별도 로 DB 에 저장 해 두 고 있 었 습니다 . 메모리 에 올라와 있 는 LRU hashtable 에 원하 는 컨텐츠 가 캐 싱 되 어 있 지 않 다면 , SQLite DB 에 컨텐츠 키 를 가지 고 어떤 BigFile 에 있 는지 , 헤더 의 길이 는 얼마나 되 는지 등 의 정보 들 을 조회 를 해야 하 는 것 이 죠 . 
 이러 한 중앙 집중 적 인 SQLite 구조 에 는 한계 가 있 다고 파악 하 였 고 , 근본 적 으로 저장 구조 를 개선 해야 된다고 결론 내렸 습니다 . 
 Journaling BigFile ( JBF ) 
 중앙 집중 적 인 SQLite 대신 각 BigFile 에 metadata 를 함께 저장 하 는 방식 을 취하 기 로 하 였 습니다 . 이 를 위해 SQLite 처럼 journaling 이 가능 하 면서 안정 적 으로 metadata 와 컨텐츠 를 저장 하 기 위해 자체 적 으로 Journaling BigFile , 일명 JBF 를 개발 해 적용 하 였 습니다 . 
 JBF 는 위 그림 과 같이 BigFile 내부 에 block - level journal ( write - ahead log ) , space map , B - Tree 등 을 가지 고 있 어 BigFile 개별 로 복구 , 조회 , 포맷 이 가능 합니다 . 또한 , Metadata 정보 를 JBF 내부 B - Tree 에 적재 하 고 , 컨텐츠 body 는 JBF 의 일반 영역 에 저장 합니다 . JBF 는 BigFile 별 포맷 이 매우 빠르 게 이루 어 지 기 때문 에 기존 버전 과 는 다르 게 가득 찬 BigFile 을 비울 때 발생 하 는 성능 하락 이슈 가 없 습니다 . 컨텐츠 를 여러 block 에 걸쳐 쓰 는 것 이 가능 하 기 때문 에 저장 가능 한 컨텐츠 의 최대 크기 제한 역시 BigFile 전체 크기 로 늘어나 게 됩니다 . 
 JBF 를 이용 한 저장 구조 
 기존 SQLite 에 의존 했 던 탐색 로직 을 개편 하 기 위해서 는 어떠 한 JBF 에 컨텐츠 가 존재 하 는지 를 캐 시 key 만 으로 알아내 야 합니다 . 이 를 위해 JBF 들 을 몇 개 의 그룹 으로 나누 고 , 캐시 key 를 그룹 개수로 modular 연산 하 여 특정 그룹 을 정하 고 , 해당 그룹 내부 의 있 는 JBF 들 에서 순차 적 으로 탐색 하 도록 구성 하 였 습니다 . 
 전체 JBF 들 을 동시 에 사용 하 지 않 고 그룹 으로 묶 어 사용 하 는 이유 는 효율 적 인 디스크 용량 활용 이 가능 해 지 기 때문 입니다 . JBF 는 BigFile 단위 로 포맷 을 하 게 되 는데 , 그룹 을 만들 지 않 고 모든 JBF 에 골고루 저장 하 게 되 면 JBF 들 이 가득 차 게 되 는 시점 이 비슷 해 지 고 , 이후 오래 된 캐시 를 비우 는 작업 시 전체 JBF 들 을 비슷 한 시기 에 비워야 하 는 이슈 가 생깁니다 . 그렇게 되 면 순간 적 으로 hit 율 이 낮 아 짐 은 물론 새롭 게 컨텐츠 를 캐 싱하 는 양 도 많 아 져 소스 서버 의 부하 까지 발생 하 게 됩니다 . 반면 그룹 을 묶 어서 사용 하 게 되 면 그룹 별로 하나 씩 만 비울 수 있 게 되 어 순간 적 인 캐시 hit 율 하락 을 완화 시킬 수 있 습니다 . 
 또한 각 그룹 별 로 write 가 이루어지 는 JBF 들 의 순번 이 정해져 있 고 컨텐츠 가 쌓이 는 속도 도 일정 하 기 때문 에 이 점 을 활용 해 디스크 locality 도 증가 시킬 수 있 습니다 . Wcache 초기 세팅 시 각 그룹 에서 같 은 순번 에 있 는 JBF 들 을 물리 적 디스크 상 에서 인접 하 게 생성 시키 면 ( group 1 의 1 번 JBF , group 2 의 1 번 JBF , … group 1 의 2 번 JBF , … ) 컨텐츠 를 디스크 에 캐 싱 할 때 인접 한 구역 을 쓰 기 때문 에 HDD 를 사용 하 는 경우 디스크 의 seek time 을 줄일 수 있 는 중요 한 요인 이 됩니다 . 
 Bloom filter 를 통한 JBF 성능 최적화 
 JBF 들 의 그룹 을 만드 는 구조 는 한 가지 치명 적 인 단점 이 있 습니다 . 바로 중복 된 JBF 탐색 입니다 . 위 그림 에서 볼 수 있 듯이 캐 싱 된지 오래 된 컨텐츠 를 읽 기 위해서 는 쓸모없 는 JBF 탐색 이 들어가 야 하 는데 , 이 는 개편 후 초기 테스트 시 Wcache 에 쌓이 는 컨텐츠 가 많 아 질수록 성능 을 저하 시키 는 주요 원인 이 되 었 습니다 . 
 이 를 해결 하 기 위해 각 JBF 앞단 에 bloom filter 1 를 적용 하 게 됩니다 . Bloom filter 는 어떤 원소 가 집합 에 속하 였 는지 확률 적 으로 알 수 있 게 하 는 함수 입니다 . 약간 의 false positive 만 가지 고 있 고 false negative 는 없 기 때문 에 해당 필터 를 통과 하 는 경우 에 만 JBF 조회 를 하 도록 변경 을 함 으로써 기존 대비 2 ~ 5 배 의 응답 속도 를 향상 시켰 습니다 . 
 Metadata 저장 구조 변경 - Vary Object 관리 개선 
 HTTP 헤더 중 에 는 ‘ Vary ’ 라는 항목 이 있 습니다 . 동일 한 URL 에 대해 요청 을 하 더라도 요청 한 사용자 의 특징 ( User Agent , Accept Encoding , Origin 등등 ) 에 따라 서로 다른 응답 을 해 주 기 위해서 존재 하 는 헤더 입니다 . 따라서 웹 캐시 에서 는 vary 헤더 를 확인 하 고 해당 헤더 에서 명시 하 는 조건 에 따라 동일 URL 이 라 하 더라도 다른 종류 의 컨텐츠 를 캐 싱 하고 , 제공 해야 합니다 . 
 기존 Wcache 에서 는 이런 vary object 에 대한 고려 가 충분 하 지 않 았 습니다 . 아래 그림 처럼 vary object 저장 은 가능 했 지만 , 리스트 형식 으로 원 하 는 vary object 가 나올 때 까지 탐색 해야 했 고 그 마저 도 vary 헤더 가 없 는 normal object 와 동시 에 캐 싱 을 할 수 없 는 이슈 가 있 었 습니다 . 
 이 를 해결 하 기 위해 저장 구조 설계 시 처음 부터 vary object 의 존재 를 고려 하 였 습니다 . Metadata 를 두 개 의 타입 ( Meta / Info ) 으로 나누 어 , Meta 에 는 현재 Wcache 에 캐 싱 되 어 있 는 vary object 의 종류 와 info 의 포인터 를 , Info 에 는 실제 컨텐츠 의 정보 ( offset , header 등 ) 를 담 도록 설계 하 였 습니다 . 이 구조 를 통해 여러 개 의 vary object 가 있 어도 , normal object 와 공존 해도 문제 없이 사용 자 가 원 하 는 컨텐츠 를 제공 할 수 있 게 됩니다 . 
 Lock 구조 개선 - 동일 컨텐츠 접근 시 생기 는 병목현상 제거 
 Wcache 는 느슨 한 구조 의 actor model 2 을 가지 고 있 습니다 . 사용자 의 요청 이 들어오 면 다수 의 actor ( HTTP 요청 수신 / 응답 / 컨텐츠 처리 / 소스 서버 통신 ) 를 돌아다니 며 처리 됩니다 . 각 actor 는 I / O multiplexing 형식 으로 동시 에 여러 개 의 요청 을 다룰 수 있 게 하 여 최대한 의 성능 을 낼 수 있 도록 설계 되 었 습니다 . 그러나 디스크 I / O 를 하 는 actor 는 내부 적 으로 blocking read / write 를 수행 하 게 되 어 단일 thread 로 는 원활 한 성능 을 얻 을 수 없 기에 내부 적 으로 multi thread 모델 을 채택 하 였 습니다 . 
 그러 다 보 니 해당 구조 에서 는 캐 싱 된 컨텐츠 를 처리 하 는 actor 에서 컨텐츠 를 조회 하 고 검증 하 는 동안 불가피 하 게 캐 시 key 에 대해 광범위 한 lock 처리 를 해야 했 습니다 . 이 lock 은 컨텐츠 가 새로 갱신 되 거나 삭제 ( Purge ) 될 때 를 위해 필요 하 지만 , 이전 구조 에서 는 read / write lock 구분 이 없 었 습니다 . 이 는 동일 컨텐츠 를 집중 적 으로 요청 할 때 해당 컨텐츠 의 초당 처리 수 가 제한 되 는 문제 를 야기 합니다 . 일반 적 으로 특정 이벤트 3 로 인해 트래픽 이 증가 하 는 경우 , 소수 의 인기 있 는 컨텐츠 들 이 집중 적 으로 요청 되 는 경우 가 많 으므로 이러 한 성능 제한 은 급작스런 트래픽 대응 시 잠재 적 인 문제 가 될 수 있 습니다 . 
 이 를 해결 하 기 위해 먼저 read / write lock 을 분리 하 여 평시 에 컨텐츠 가 집중 적 으로 요청 되 는 경우 동시 에 접근 이 가능 할 수 있 도록 하 였 습니다 . 더 나아가 lock 의 범위 를 줄이 고 추후 유지 보수 를 쉽 게 하 기 위해 순수 하 게 디스크 I / O 부분 과 캐시 컨텐츠 를 검증 하 는 부분 을 별도 의 actor 로 분리 해 조금 더 효율 적 인 처리 를 할 수 있 도록 변경 하 였 습니다 . 이 조치 는 동일 컨텐츠 요청 에 대해 기존 대비 약 3 배 의 성능 향상 을 이루 어 냅니다 . 
 이번 개편 은 개발 부터 성능 안정 화 까지 약 1 년 에 걸쳐 진행 되 었 습니다 . 기존 Wcache 구조 의 핵심 부분 들 을 전부 뜯어고치 는 대 작업 이 었 습니다 . 이번 개편 을 통해 더 안정 적 인 성능 을 이끌 어 낼 수 있 었 고 , 기능 적 인 측면 들 - vary object 저장 유연 화 , 컨텐츠 크기 제한 해제 , regular expression 기반 대량 캐시 퍼지 등 - 에서 큰 발전 을 이룰 수 있 게 되 었 습니다 . 
 Actor Model ↩ 
 다음 앱 에서 의 뉴스 속보 및 날씨 알림 등 의 푸시 , 카카오톡 채널 탭 뱃지 등 . . ↩ 
 sirius . nam ' s profile image 
 sirius . nam 
 분산 웹 캐시 ( Wcache ) 의 개선 과정 - Part 1