본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
https://fastcampus.info/4n8ztzq
안녕하세요 :)
오늘은 "50일의 기적 AI 환급반_ 대규모 채팅 플랫폼으로 한 번에 끝내는 실전 대용량 트래픽 커버 완전판" 챌린지에 도전하는 마흔 다섯번째 날입니다.
마흔 네번째 날에는 Apache ShardingSphere를 사용하여 샤딩 구현에 대해 배웠었는데요. 오늘은 과연 어떤 내용들을 배울 수 있을지 공부하고 포스팅 하도록 하겠습니다.
오늘 학습할 내용의 제목은,
Part 5. 모놀리틱 서비스 분해 > Ch 1. 데이터베이스의 고가용성과 확장성 확보를 위한 샤딩 >
03. 스프링을 사용하여 샤딩 직접 구현
입니다.
🧩 스프링을 사용하여 샤딩 직접 구현
- ShardingSphere 의존성을 제거하고, Spring + AbstractRoutingDataSource + ThreadLocal 컨텍스트만으로 간단한 샤딩을 직접 구현합니다.
- 예제는 이전에 만들었던 프리뷰-샤딩(구: 프리뷰-샤딩-스피어) 프로젝트를 베이스로 진행합니다.
🗺️ 이번에 구현하는 것 (TL;DR)
- 🔌 ShardingSphere 제거: build.gradle 및 sharding.yml 정리
- 🧭 라우팅 데이터소스 직접 구현: AbstractRoutingDataSource 상속
- 🧠 ThreadLocal 기반 샤딩 컨텍스트: 채널 ID로 노드 선택(짝/홀 예시)
- 🧱 DDL/초기화 직접 처리: ddl-auto/자동 init 끄고, message.sql로 각 노드 초기화
- 🧪 통합 테스트 검증: 1·3채널 → 노드1, 2채널 → 노드2 분산 저장 확인
🧽 1) 의존성/설정 정리
✅ 프로젝트 리네이밍 & 의존성 제거
- 프로젝트명: 프리뷰-샤딩으로 변경
- build.gradle에서 ShardingSphere 관련 의존성 제거
- sharding.yml 파일 삭제
🧾 application.yml 핵심 포인트
- 각 노드를 개별 데이터소스로 선언 (예: spring.datasource.node1, spring.datasource.node2)
- JPA DDL 및 자동 스키마 초기화 비활성화
- 이유: 다중 데이터소스 환경에서 하이버네이트의 스키마 검증/초기화 전제(단일 DS)가 깨짐
- 특히 채팅 프로젝트처럼 노드별 스키마 구성이 다른 경우 ddl-auto가 실패할 수 있음
🧠 2) ThreadLocal 샤딩 컨텍스트
- 샤딩 키(여기서는 channelId)를 현재 쓰레드에 바인딩하여 라우팅 기준으로 사용합니다. try-with-resources로 자동 해제(누수 방지).
🧭 3) 라우팅 데이터소스 구현
- AbstractRoutingDataSource를 상속받아 현재 쓰레드의 channelId를 읽고 노드 키를 결정합니다.
예시: 홀수 → node1, 짝수 → node2 (샤딩 규칙은 프로젝트 상황에 맞게 교체).
⚠️ 디폴트 데이터소스는 의도적으로 지정하지 않습니다.
라우팅 키를 못 찾을 때 조용히 다른 노드로 가면 데이터 섞임을 초래할 수 있어요.
🧩 4) DataSource & 초기화 구성(@Configuration)
- node1, node2 각각의 HikariDataSource 빈 생성
- 수동 초기화용 DataSourceInitializer 생성: message.sql 실행
- 메타정보 조회 등 초기 연결 시 라우팅 고정을 위해 일시적으로 channelId=1 설정
- 컨텍스트 초기화 이후 ThreadLocal 정리 이벤트 리스너 등록
🧱 5) 비즈니스 로직에서의 라우팅 주입
- save() 직전 샤드 컨텍스트를 세팅하여 해당 쓰레드의 모든 DB 호출이 올바른 노드로 라우팅되게 합니다.
💡 변수명을 ignored로 두면 IDE의 "unused" 경고를 피하면서,
"일부러 사용하지 않는다" 는 의도가 명확해집니다.
🔬 6) 동작 검증 시나리오
- 🐳 Docker DB 두 노드 기동 → 두 DB 모두 빈 상태 확인
- 🚀 서버 기동 → message.sql이 node1/node2에 각각 적용되어 테이블 생성
- 🧪 통합 테스트: 채널 1,2,3에 메시지 전송
- ✅ node1: 채널 1, 3(홀수) 데이터 저장
- ✅ node2: 채널 2(짝수) 데이터 저장
- 🔁 테스트 반복 시에도 항상 동일한 분배 결과 확인
⚖️ 트레이드오프 & 운영 팁
- ❌ ddl-auto, ❌ 자동 init: 직접 관리 필요
- 노드별 스키마가 다를 수 있으므로, 노드 맞춤 스키마 파일을 준비/적용
- 🧯 디폴트 타깃 DS 미설정: 라우팅 실패를 조기에 노출 → 데이터 섞임 예방
- 🧼 ThreadLocal 누수 방지: 항상 try-with-resources로 감싸고, 이벤트 시점에는 clear()
- 🧵 비동기 처리(스레드풀) 시: 작업 투입부에서 반드시 ShardContext.Scope 설정
- 📈 샤딩 규칙은 도메인 키 기반으로 확장 (예: 해시 범위, 모듈러, 지역/조직 단위 등)