서로 다른 두 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 문제를 피해야 합니다. - 적용 여부는 페이지 크기, 후보 식별자 수, 전체 데이터 규모를 기준으로 판단해야 합니다.
특정 기술을 적용했다는 사실보다 현재 데이터 규모와 운영 조건에서 왜 이 선택이 적합한지 설명하는 것이 중요하다는 점을 배웠습니다.