zero-downtime-migration

2 개의 포스트

line5분 읽기큐레이션 요약

초당 100만 건, LINE 앱에 Apache Kafka 종단 간 암호화 적용기

LINE의 대규모 Kafka 환경에서는 TLS·인증·인가만으로는 브로커에 저장된 평문 데이터를 충분히 보호하기 어렵기 때문에, 프로듀서부터 컨슈머까지 메시지 페이로드를 암호화하는 종단 간 암호화를 도입했다. 레코드 단위 암호화와 DEK-KEK 구조를 결합해 Kafka 표준 확장성을 유지하면서 성능 오버헤드를 줄였고, 공유 KEK·평문 폴백·점진적 배포로 초당 최대 100만 건 규모의 토픽에 무중단 적용했다. ## Kafka 기본 보안 모델의 한계 - TLS/SSL은 프로듀서·컨슈머와 브로커 사이의 전송 구간만 보호한다. - SASL 인증은 클라이언트의 신원을 확인하고, ACL 인가는 토픽 접근 권한을 통제한다. - 그러나 브로커에 저장된 메시지 페이로드 자체는 평문일 수 있다. - 따라서 접근 권한이 우회되거나 브로커 내부 데이터가 노출되면 민감 정보가 보호되지 않는다. - 데이터 생성 시점부터 컨슈머의 복호화 시점까지 암호화 상태를 유지하는 심층 방어 전략이 필요하다. ## 레코드 단위 암호화 - 배치 단위 암호화는 압축 효율과 처리 성능이 좋지만, Kafka 클라이언트 내부 동작을 수정해야 한다. - Kafka의 인터셉터와 같은 공식 확장 포인트는 레코드 단위로 동작한다. - 레코드 단위 암호화는 압축 효율이 낮고 메시지 크기가 다소 증가하지만 다음 장점이 있다. - 표준 Kafka API를 활용할 수 있다. - Kafka 버전 업그레이드 시 호환성과 안정성이 높다. - 기존 프로듀서·컨슈머 클라이언트 코드를 직접 수정하지 않아도 된다. - 이러한 이유로 레코드 단위 암호화를 선택했다. ## DEK-KEK 이중 키 구조 - **DEK(Data Encryption Key)** - 메시지 페이로드 암호화에 사용하는 AES 대칭 키다. - AES-GCM을 사용해 빠른 암·복호화와 무결성 검증을 제공한다. - **KEK(Key Encryption Key)** - DEK를 암호화하는 ECC 기반 비대칭 키 쌍이다. - KMS가 키를 관리하며, 프로듀서는 공개 키를 사용하고 컨슈머는 인가된 비공개 키를 사용한다. - DEK 암호화에는 ECIES와 `secp521r1` 곡선을 사용한다. - 대용량 데이터는 빠른 대칭 키로 처리하고, 짧은 DEK에만 비대칭 암호화를 적용해 성능 부담을 줄인다. - 프로듀서는 페이로드를 한 번만 암호화하므로 컨슈머 수가 늘어도 메시지 크기를 크게 늘리지 않는다. - 공개 키를 이용한 암호화 권한과 비공개 키를 이용한 복호화 권한을 분리해 최소 권한 원칙을 적용한다. ## 암호화 메시지 구조 - **키** - Kafka 파티션을 결정하는 기존 메시지 키를 그대로 유지한다. - **헤더** - 컨슈머가 사용할 KEK ID와 KEK로 암호화된 DEK를 저장한다. - **바디** - DEK로 암호화된 실제 페이로드를 담는다. - 외부 DB나 캐시 없이 메시지 자체에 복호화 메타데이터를 포함해 시스템 의존성을 줄였다. - 컨슈머는 헤더의 KEK ID를 확인한 뒤 DEK를 복호화하고, 복호화한 DEK로 바디의 페이로드를 복호화한다. ## 프로듀서 암호화 처리 - Kafka 인터셉터가 전송 직전 DEK를 생성하고 KEK 공개 키로 암호화한다. - 암호화된 DEK는 메시지 헤더에 삽입한다. - 기존 시리얼라이저를 감싼 래퍼가 직렬화된 페이로드를 DEK로 암호화한다. - 인터셉터와 시리얼라이저가 같은 실행 스레드를 공유한다는 점을 활용해 DEK를 `ThreadLocal`로 전달한다. - 매 메시지마다 DEK를 새로 만들지 않고 일정 시간 캐싱해, 반복적인 비대칭 키 연산을 줄였다. ## 컨슈머 복호화 처리 - 컨슈머는 KMS에서 인가된 KEK 비공개 키를 조회한다. - 디시리얼라이저가 헤더에서 암호화된 DEK를 추출하고 비공개 키로 복호화한다. - 복호화된 DEK로 페이로드를 복호화한 후 기존 역직렬화를 수행한다. - 여러 프로듀서가 생성한 암호화 DEK와 복호화된 DEK의 쌍을 캐싱한다. - 동일한 암호화 DEK가 반복되면 비공개 키 연산을 생략해 컨슈머 성능을 높인다. ## KMS 기반 키 관리 - 토픽 오너가 KEK 키 쌍을 생성하고 KMS에 등록한다. - 프로듀서는 공개 키를, 승인된 컨슈머는 비공개 키를 KMS에서 조회한다. - 신규 컨슈머는 비공개 키 접근 권한을 요청하고 토픽 오너의 승인을 받아야 한다. - KEK의 생성·배포·접근 제어·교체를 KMS를 통해 일관되게 관리한다. ## 공유 KEK로 메시지 크기 제어 - 컨슈머마다 별도의 KEK를 사용하면 컨슈머 수에 비례해 헤더 메타데이터가 증가한다. - 메시지 크기 증가는 배치당 레코드 수 감소, 네트워크 대역폭 증가, CPU·메모리 사용량 증가로 이어진다. - 특히 초당 최대 100만 건의 토픽에서는 컨슈머 추가에 따른 헤더 증가가 큰 성능 문제가 된다. - 여러 컨슈머가 하나의 KEK를 공유하면 헤더에는 하나의 메타데이터만 포함되어 메시지 크기를 일정하게 유지할 수 있다. - 대신 키 격리 수준은 낮아지므로 다음 보완책을 함께 적용한다. - KMS 기반 비공개 키 접근 인가 - 주기적인 KEK 교체 - 토픽 오너 중심의 키 관리 ## 평문 폴백을 이용한 무중단 마이그레이션 - 암호화 도입 과정에서는 기존 평문 메시지와 새로운 암호화 메시지가 함께 존재할 수 있다. - 디시리얼라이저가 헤더의 암호화 메타데이터 유무를 확인해 처리 방식을 결정한다. - 헤더가 있으면 복호화 후 역직렬화한다. - 헤더가 없으면 기존 평문 역직렬화만 수행한다. - 안전한 전환 순서는 다음과 같다. - 평문과 암호화 메시지를 모두 처리할 수 있는 컨슈머를 먼저 배포한다. - 모든 컨슈머가 준비된 뒤 프로듀서 암호화를 활성화한다. - 모니터링을 통해 평문 메시지 비중이 0%가 되었는지 확인한다. - 프로듀서 암호화 비율도 한 번에 100%로 변경하지 않고 점진적으로 높여 성능 저하나 암·복호화 오류에 대응한다. ## 실용적인 결론 Kafka의 TLS·인증·인가를 대체하기보다, DEK-KEK 기반 페이로드 암호화를 추가 보안 계층으로 적용하는 것이 적절하다. 대규모 환경에서는 레코드 단위 암호화, DEK 캐싱, 컨슈머 측 DEK 캐싱, 공유 KEK, 평문 폴백과 점진적 배포를 함께 설계해야 보안성과 성능, 무중단 운영을 동시에 확보할 수 있다.

원문 읽기(새 탭에서 열림)
naver원문

스마트스토어센터 Oracle에서 MySQL로의 무중단 전환기 (새 탭에서 열림)

네이버 스마트스토어센터는 비즈니스 성장에 따른 Oracle DBMS의 리소스 경합과 라이선스 비용 문제를 해결하기 위해 오픈소스인 MySQL로의 무중단 마이그레이션을 단행했습니다. 10년 이상의 레거시 시스템을 안정적으로 전환하기 위해 '이중 쓰기(Dual Write)' 전략을 채택했으며, 이를 통해 데이터 손실 없는 실시간 동기화와 즉각적인 롤백 가능성을 확보했습니다. 결과적으로 서비스 중단 없이 DB 환경을 성공적으로 전환하며 운영 효율성을 높였습니다. ### 이중 쓰기(Dual Write)를 통한 무중단 전환 전략 * **3단계 전환 프로세스**: 전환 전에는 Oracle을 메인으로 사용하며 MySQL에 백그라운드 쓰기를 수행하고, 데이터 마이그레이션 후에는 MySQL을 메인으로 전환하되 Oracle에 백그라운드 쓰기를 지속하여 정합성을 유지합니다. * **롤백 안정성 확보**: 신규 시스템 배포 후 치명적인 성능 저하나 장애가 발생하더라도, Oracle에 실시간으로 데이터가 쌓이고 있으므로 별도의 복구 작업 없이 즉시 이전 환경으로 복구가 가능합니다. ### JPA 환경에서의 기술적 대응 * **Proxy DataSource 활용**: `datasource-proxy` 라이브러리를 사용하여 Oracle에서 수행되는 쿼리를 가로챈 뒤 MySQL DataSource에서도 동일하게 실행하는 구조를 구축했습니다. * **트랜잭션 분리 및 동기화**: MySQL 쿼리 실패가 메인 트랜잭션(Oracle)에 영향을 주지 않도록 `TransactionSynchronizationManager`를 사용했습니다. Oracle 커밋이 성공한 시점(`afterCommit`)에 모아둔 MySQL 쿼리를 일괄 실행하여 정합성을 맞춥니다. * **엔티티 및 PK 전략 변경**: Oracle의 Sequence 전략을 MySQL의 Identity(Auto-increment)로 변경하고, `columnDefinition` 설정을 통해 Oracle의 VARCHAR2, CLOB 등을 MySQL의 TEXT, LONGTEXT 타입에 맞게 조정했습니다. ### MyBatis 기반의 중앙 집중형 이중 쓰기 구현 * **SqlSession Proxy 적용**: 수천 개의 비즈니스 로직을 수정하는 대신, MyBatis의 `SqlSession`을 프록시로 감싸 쓰기 작업(CUD)이 발생할 때 Oracle과 MySQL 쿼리를 동시에 호출하도록 구현했습니다. * **DBMS별 쿼리 매핑**: Oracle과 MySQL의 SQL 문법 차이를 해결하기 위해 별도의 MySQL용 쿼리 파일을 작성하고, 실행 시점에 Query ID에 접두사(예: `mysql.`)를 붙여 적절한 쿼리를 찾아 실행하는 방식을 사용했습니다. ### 데이터 정합성 검증 및 최종 전환 * **배치 기반 검증**: 두 DB 간의 레코드 카운트와 데이터 해시값을 주기적으로 비교하는 배치 프로그램을 운영하여 미세한 데이터 불일치를 식별하고 수정했습니다. * **기능 토글을 이용한 전환**: ZooKeeper 등을 활용한 설정 변경만으로 메인 DB(Read/Write 주체)를 즉시 교체할 수 있는 환경을 구성하여 배포 없이 안정적으로 전환을 완료했습니다. 이와 같은 전략은 대규모 레거시 시스템에서 DB를 교체해야 할 때, 코드 수정을 최소화하면서도 서비스 안정성을 최우선으로 고려하는 개발자들에게 실무적인 가이드라인을 제공합니다. 특히 트랜잭션 동기화와 프록시 패턴을 활용한 중앙 집중식 제어는 복잡한 시스템 마이그레이션의 위험 부담을 낮추는 핵심 기술 요소입니다.