본문 바로가기

패스트캠퍼스/50일의 기적 AI 환급반

패스트캠퍼스 환급챌린지 45일차 : 대규모 채팅 플랫폼으로 한 번에 끝내는 실전 대용량 트래픽 커버 완전판 강의 후기

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

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) 동작 검증 시나리오

  1. 🐳 Docker DB 두 노드 기동 → 두 DB 모두 빈 상태 확인
  2. 🚀 서버 기동 → message.sql이 node1/node2에 각각 적용되어 테이블 생성
  3. 🧪 통합 테스트: 채널 1,2,3에 메시지 전송
    • node1: 채널 1, 3(홀수) 데이터 저장
    • node2: 채널 2(짝수) 데이터 저장
  4. 🔁 테스트 반복 시에도 항상 동일한 분배 결과 확인

 

⚖️ 트레이드오프 & 운영 팁

  • ❌ ddl-auto, ❌ 자동 init: 직접 관리 필요
    • 노드별 스키마가 다를 수 있으므로, 노드 맞춤 스키마 파일을 준비/적용
  • 🧯 디폴트 타깃 DS 미설정: 라우팅 실패를 조기에 노출 → 데이터 섞임 예방
  • 🧼 ThreadLocal 누수 방지: 항상 try-with-resources로 감싸고, 이벤트 시점에는 clear()
  • 🧵 비동기 처리(스레드풀) 시: 작업 투입부에서 반드시 ShardContext.Scope 설정
  • 📈 샤딩 규칙은 도메인 키 기반으로 확장 (예: 해시 범위, 모듈러, 지역/조직 단위 등)