pile·
백엔드·마켓컬리마켓컬리 Hello World·

컬리 공통 암호화 모듈의 동시성 이슈 해결하기

컬리 공통 암호화 SDK 모듈에서 간헐적으로 발생한 ByteBuffer 버퍼 오류를 추적해 동시성 이슈로 규명하고 해결한 디버깅 기록이다. AWS KMS 봉투 암호화와 Caffeine 캐시 구조에서 왜 thread-safe 문제가 생겼는지 코드 레벨로 파고든다.

핵심 포인트
  • 개인정보 암호화에 AWS KMS 봉투 암호화를 쓰고, KMS API 호출 비용을 줄이려 Data Key를 Caffeine 캐시에 보관해 재사용한다.
  • 1:1 문의 게시판에서 마스킹 전화번호가 빈칸으로 뜨는 newPosition greater than limit Buffer 오류가 일부 요청에만 간헐적으로 발생했다.
  • 원인은 여러 스레드가 같은 캐시된 ByteBuffer를 공유하며 position이 오염된 것으로, Buffer는 thread-safe하지 않다.
  • synchronized(성능 손해) 대신 duplicate()로 스레드별 지역 버퍼를 만들어 해결했다(asReadOnlyBuffer 권장과 동일 원리).
상세 정리
  • 구조: 캐시 미사용 시 매 요청마다 KMS 클라이언트를 호출해야 하므로, GenerateDataKey로 받은 Data Key를 일정 시간 Caffeine 캐시에 보관해 API 호출·비용을 줄였다.
  • 증상: EncryptService가 캐시에서 Data Key를 꺼내 SecretKey.createKey로 암호화 키를 만드는데, createKey 내부 getByteArray의 b.get(byteArray)에서 오류가 났다.
  • 코드 추적: get(byte[])는 get(dst,0,dst.length)를 거쳐 HeapByteBuffer.get으로, 다시 position(pos+length)를 호출한다.
  • 예외 지점: Buffer.position은 newPosition이 limit보다 크거나 음수면 createPositionException을 던지는데, pos+length가 간헐적으로 버퍼 limit인 32를 넘겼다.
  • 간헐성 분석: 캐시 유지 시간 내 같은 키인데도 여러 번 오류가 났고, 키 교체 시점 가설을 배제하니 동시성 이슈가 의심됐다.
  • 재현 테스트: 100개 고정 스레드풀로 동시에 암호화를 수행하며 버퍼의 limit·position·length를 출력했다.
  • 근거 확인: clear() 후 position은 0이어야 하는데 일부 스레드에서 32로 찍혀 메소드가 thread-safe하지 않음을 확인했고, Java Buffer 문서도 다중 스레드 동시 사용은 안전하지 않아 동기화가 필요하다고 명시한다.
  • 해결책 후보: 첫째 synchronized 추가는 성능을 trade-off해야 하고, 둘째 duplicate()로 단일 스레드용 지역 버퍼를 생성한다.
  • 최종: getPlaintext() 자바독이 thread-safe를 위해 asReadOnlyBuffer 사용을 권장하며, duplicate()가 같은 원리라 스레드마다 새 객체를 만들어 동시성 이슈를 해결했다.
왜 읽나캐시된 공유 객체(ByteBuffer)의 동시성 버그를 코드 레벨로 추적·해결하는 과정을 보고 싶은 백엔드 개발자에게.
마켓컬리
마켓컬리 Hello World 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·github-engGitHub Engineering·

    조기 종료를 없애야 벡터화된다 — 메모리 속도 소스 코드 케이스 폴딩

    GitHub의 코드 검색 엔진 Blackbird는 480TB 이상의 소스 코드를 인덱싱하기 전 모든 바이트에 case folding을 적용한다. 이 글은 Rust로 구현한 case folding을 메모리 대역폭 한계(45+ GiB/s)까지 끌어올린 두 가지 반직관적 최적화를 상세히 다룬다. 핵심은 루프 조기 종료(break) 제거로 LLVM 벡터화를 유도하고, UTF-8을 디코딩하지 않고 바이트 공간 산술만으로 fold를 수행하는 것이다.

    #rust#unicode#simd+2
  2. 백엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    트랜잭션 스크립트에서 숙소 메타 + 가격 계산 모듈로 — 전시 아키텍처 개선기 (2/3)

    여기어때 전시개발팀이 숙소 상세(PDP) API를 해부한 결과, 코드상으로는 DB 호출 3번처럼 보이던 요청이 실제로는 MongoDB $lookup 체인으로 컬렉션을 19회 접근하는 구조였다. 이 트랜잭션 스크립트 방식의 핵심 문제는 "aggregation이 I/O를 가린다"는 점으로, 독립적인 쿼리 10개가 단일 파이프라인에 직렬화되어 병렬화 기회를 잃고, 가격 때문에 거의 안 바뀌는 이미지까지 매 요청마다 읽어야 하는 읽기 증폭이 발생했다. V3에서는 "조회 시점 조립"을 "쓰기 시점 사전 조립"으로 전환하고, 화면별로 복제되던 가격 계산 로직을 goodsprice 단일 모듈로 수렴했다. 4개 API(PLP/PDP/RDP/ILP)의 반복 마이그레이션은 Claude Code skill로 절차를 고정하고 쉐도잉 + 동일성 검증으로 안전망을 마련하는 방식으로 진행됐다.

    #architecture#migration#caching+2