JDK Vector API를 활용 (새 탭에서 열림)
넷플릭스는 추천 시스템의 핵심 로직인 '비디오 참신성 점수(serendipity scoring)' 계산 과정에서 발생하는 과도한 CPU 점유율(7.5%) 문제를 해결하기 위해 대대적인 최적화를 수행했습니다. 개별 벡터의 유사도를 반복 계산하던 기존 방식을 행렬 연산 기반의 배치 처리로 전환하고, 메모리 레이아웃 최적화와 JDK Vector API를 도입함으로써 연산 효율을 극대화하고 클러스터 유지 비용을 절감하는 성과를 거두었습니다. **기존 구현의 성능 병목 현상** * 후보 영화군(M)과 사용자의 시청 기록(N)을 비교할 때 $O(M \times N)$의 중첩 루프 구조로 코사인 유사도를 계산하여 순차적 작업 부하가 컸습니다. * 파편화된 메모리 접근 방식과 반복적인 임베딩 조회로 인해 캐시 지역성이 떨어졌으며, 이는 서비스 전체 CPU 프로파일링에서 주요 핫스팟으로 나타났습니다. * 특히 대량의 배치 요청이 들어올 경우 계산량이 기하급수적으로 늘어나 전체 서비스의 응답 속도에 악영향을 주었습니다. **행렬 연산으로의 전환 및 배치화** * 수많은 작은 도트 곱(dot product) 연산을 하나의 행렬 곱셈($M \times D$와 $D \times N$ 행렬의 곱)으로 재설계하여 수학적 최적화의 기반을 마련했습니다. * 모든 행을 단위 벡터로 정규화한 후 행렬 연산을 수행하여 한 번에 모든 유사도 점수를 산출하는 방식으로 알고리즘을 개선했습니다. * 단일 요청과 배치 요청을 모두 지원하도록 인터페이스를 확장하여 하위 호환성을 유지하면서도 처리 효율을 높였습니다. **메모리 레이아웃 최적화와 객체 재사용** * 다차원 배열(`double[][]`) 사용 시 발생하는 가비지 컬렉션(GC) 압박과 메모리 비연속성 문제를 해결하기 위해 1차원 평면 버퍼(`double[]`) 구조를 도입했습니다. * `ThreadLocal<BufferHolder>`를 활용해 각 스레드에서 연산용 버퍼를 재사용함으로써 매 요청마다 발생하는 메모리 할당 비용을 제거했습니다. * 데이터 레이아웃을 행 우선(row-major) 순서의 연속된 메모리로 배치하여 CPU 캐시 효율을 비약적으로 향상했습니다. **네이티브 라이브러리(BLAS)의 한계와 대안** * 고성능 선형 대수 라이브러리인 BLAS 도입을 검토했으나, 자바와 네이티브 코드 간의 JNI(Java Native Interface) 전환 오버헤드로 인해 실질적인 성능 이득이 크지 않았습니다. * 또한 자바의 행렬 레이아웃과 네이티브 라이브러리 요구 사양 간의 차이로 인해 추가적인 데이터 복사 비용이 발생하여 기대 성능에 미치지 못했습니다. * 이를 해결하기 위해 자바 환경 내에서 하드웨어의 SIMD 기능을 직접 활용할 수 있는 JDK Vector API가 최종적인 최적화 도구로 선택되었습니다. 알고리즘의 시간 복잡도를 개선하는 것만큼이나 메모리 배치와 CPU의 하드웨어 가속(SIMD)을 고려한 저수준 최적화가 중요합니다. 특히 대규모 트래픽을 처리하는 자바 기반 마이크로서비스라면 JDK Vector API를 통해 네이티브 라이브러리 호출 없이도 고성능 연산을 구현할 수 있습니다.