본문으로 건너뛰기

서로 다른 두 DB를 조인할 수 없을 때: 인메모리 조인과 트레이드오프

메타데이터와 발송 이력이 서로 다른 DB에 있을 때, 한 화면의 조회 결과를 어떻게 구성할 수 있을까?

레거시 SMS·알림톡 관리자 서비스를 마이그레이션하면서 하나의 관리자 서버가 서로 다른 두 DB를 함께 조회해야 했습니다. 이 글에서는 메타데이터 중앙화와 애플리케이션 레벨 조인을 선택한 기준, 구현 방식, 적용 가능한 데이터 규모를 정리합니다.


1. 상황​

서버는 하나인데, 서비스별로 DB가 나뉘어 있었습니다.

  • MariaDB: 회사 / 부서 / 채널 / 사용자 등 메타데이터
  • Oracle: 실제 발송 이력 (외부 발송 시스템이 쌓는 데이터)

발송 이력 화면에는 발송 사용자와 소속 회사 정보가 함께 필요했습니다. 그러나 두 데이터가 물리적으로 다른 DB에 있어 단일 SQL JOIN으로 조회할 수 없었습니다.


2. 두 가지 선택지​

선택지 A. 각 DB에 메타데이터를 중복 저장 (기존 방식)​

기존 시스템은 회사와 사용자 정보를 양쪽 DB에 중복 저장했습니다. 이 방식에서는 Oracle 내부에서 발송 이력과 사용자 정보를 바로 조인할 수 있습니다.

  • 장점: 조회가 단순함 (DB 레벨 조인 가능)
  • 단점: 저장할 때마다 두 DB의 정합성을 맞춰야 함

기존에는 관리자가 SQL로 데이터를 직접 등록하고 결과를 확인했습니다. 이를 웹 기능으로 전환하면 등록과 수정 과정에서 여러 DB의 트랜잭션 정합성을 애플리케이션이 보장해야 합니다. 한쪽 DB만 반영되는 부분 실패까지 고려하면 저장 흐름이 복잡해집니다.

선택지 B. 메타데이터를 한 DB에 중앙화​

메타데이터는 MariaDB 한 곳에서만 관리하고, 발송 이력을 조회할 때 필요한 사용자 정보를 애플리케이션이 따로 가져와서 붙이는 방식입니다.

  • 장점: 저장이 단순함 (한 DB만 신경 쓰면 됨)
  • 단점: 조회할 때 DB 레벨 조인이 안 되므로, 애플리케이션에서 직접 조인해야 함

저는 선택지 B를 택했습니다. 저장의 복잡성과 정합성 리스크를 줄이는 것이 우선이라고 판단했기 때문입니다.


3. 인메모리 조인 구현​

DB가 조인을 못 해주니, 애플리케이션이 그 역할을 대신합니다. 흐름은 이렇습니다.

1. MariaDB에서 회사·사용자 조건에 맞는 후보와 기존 식별자 조회
2. 후보 식별자를 IN 절로 전달해 Oracle 발송 이력 페이지 조회
3. 메모리에서 (발송 이력 ↔ 사용자 정보) 를 매핑

핵심은 조회 조건에 맞는 사용자 후보를 먼저 제한하고, 후보별로 Oracle을 반복 조회하지 않는 것입니다. 후보 식별자를 IN 절로 묶어 발송 이력을 한 번에 조회했습니다.

// 1) Oracle: 발송 이력 조회 (LEGACY_ID 기준)
List<SendHistory> histories = sendHistoryRepository.findPage(condition, pageable);

// 2) 발송 이력에 등장하는 사용자 식별자만 추출 (중복 제거)
List<String> legacyIds = histories.stream()
.map(SendHistory::getLegacyId)
.distinct()
.toList();

// 3) MariaDB: IN 절로 사용자 정보 한 번에 조회 → Map 으로 변환
Map<String, User> userMap = userRepository.findByLegacyIdIn(legacyIds).stream()
.collect(Collectors.toMap(User::getLegacyId, Function.identity()));

// 4) 메모리에서 매핑
List<SendHistoryView> result = histories.stream()
.map(h -> SendHistoryView.of(h, userMap.get(h.getLegacyId())))
.toList();

LEGACY_ID는 외부 발송 시스템이 참조하는 기존 ID를 보존하기 위한 연결 필드입니다. 신규 ID 체계를 도입하면서도 외부 시스템에 영향을 주지 않으려고 둔 다리(Bridge) 역할을 합니다.


4. 왜 이 규모에서는 괜찮은가 (그리고 언제 피해야 하는가)​

IN 절에 식별자를 잔뜩 넣는 방식은 무한정 통하지 않습니다. 식별자가 수만 개가 되면 쿼리도 무거워지고 메모리 부담도 커집니다.

이 프로젝트에서 사용자 후보는 회사당 최대 300건 내외, 전체 1,000건 미만이었습니다. 이 범위에서 후보 식별자를 제한해 IN 절로 전달하는 비용이 허용 가능하다고 판단했습니다.

기준인메모리 조인(중앙화)DB 분산 저장(중복)
저장 복잡도낮음 (단일 DB)높음 (다중 DB 정합성)
조회 복잡도보통 (앱이 조인)낮음 (DB 조인)
데이터 규모 적합성소~중 규모대규모

데이터가 수만 건 이상으로 증가한다면 조회 후보 제한, 별도 통합 저장소 또는 데이터 동기화 구조를 다시 검토해야 합니다. 현재 규모에서는 저장 정합성을 단순화하고 조회 시 애플리케이션에서 결합하는 편이 더 적합했습니다.


5. 마무리​

  • 물리적으로 분리된 DB는 애플리케이션 레벨에서 조회 결과를 결합할 수 있습니다.
  • 식별자를 모아 IN 절로 한 번에 조회함으로써 N+1 문제를 피해야 합니다.
  • 적용 여부는 페이지 크기, 후보 식별자 수, 전체 데이터 규모를 기준으로 판단해야 합니다.

특정 기술을 적용했다는 사실보다 현재 데이터 규모와 운영 조건에서 왜 이 선택이 적합한지 설명하는 것이 중요하다는 점을 배웠습니다.