포스트

[TIL] MSA 프로젝트를 시작하며 마주한 DB 분리와 협업 구조 고민

MSA 아키텍처 PostgreSQL 스키마 분리 및 논리적 DB 분리 장단점, 효율적인 데이터베이스 설계 방향

For the English version of this post, see here.
[TIL] MSA 프로젝트를 시작하며 마주한 DB 분리와 협업 구조 고민

오늘 한 일

  • SA 피드백을 바탕으로 PostgreSQL의 schema 분리와 논리 DB 분리 방식 정리

  • MSA 구조에서 서비스별 DB 분리 기준과 UUID 참조 방식 학습

  • GitHub 레포지토리 생성 및 IntelliJ에 clone

  • dev 브랜치 생성 및 기본 브랜치 설정

  • 팀원 collaborator 초대

  • MSA 서비스별 기본 폴더 구조 생성

  • Issue / PR 템플릿 작성

  • GitHub Ruleset을 통해 dev 브랜치 보호 규칙 설정

  • Gradle 멀티모듈 구조와 루트/모듈별 build.gradle 역할 정리


배운 내용

PostgreSQL에서 schema 분리와 논리 DB 분리는 다르다

처음에는 하나의 PostgreSQL 인스턴스 안에 하나의 DB를 만들고, 그 안에서 서비스별 schema를 나누는 구조를 생각했다.

1
2
3
4
5
6
7
PostgreSQL 인스턴스 1개
└── delivery_db
    ├── user_schema
    ├── hub_schema
    ├── company_schema
    ├── order_schema
    └── slack_schema

→ 하지만 튜터님 피드백을 통해 MSA 구조에서는 서비스별 논리 DB로 나누는 방식이 더 적절할 수 있다는 점을 알게 되었다.

1
2
3
4
5
6
PostgreSQL 인스턴스 1개
├── user_db
├── hub_db
├── company_db
├── order_db
└── slack_db
  • 둘 다 PostgreSQL 서버는 하나일 수 있지만, 나누는 단위가 다르다.
    • schema 분리는 하나의 DB 안에서 구역을 나누는 것이고, 논리 DB 분리는 PostgreSQL 인스턴스 안에서 database 자체를 서비스별로 나누는 방식이다.

논리 DB 분리는 서비스 간 직접 참조를 줄이기 위한 구조

schema를 나누더라도 같은 DB 안에 있기 때문에 다른 schema의 테이블을 직접 조회하거나 JOIN할 수 있다.

  • 이렇게 되면 주문 서비스가 사용자 서비스의 테이블 구조를 직접 알게 된다.

  • 사용자 테이블의 컬럼이나 구조가 바뀌면 주문 서비스에도 영향이 갈 수 있고, 결국 서비스 간 결합도가 높아진다.

→ 반면 논리 DB로 나누면 각 서비스가 자신의 DB에만 접근하도록 만들 수 있다.

1
2
3
4
5
User Service → user_db만 접근
Hub Service → hub_db만 접근
Company Service → company_db만 접근
Order Service → order_db만 접근
Slack Service → slack_db만 접근
  • 이렇게 하면 다른 서비스의 테이블을 직접 JOIN하기 어려워지고, 각 서비스가 자신의 데이터만 책임지는 구조가 된다.

UUID는 식별자일 뿐, DB 구조를 분리해주지는 않는다

처음에는 “같은 DB나 schema에 둬도 UUID로 참조하면 되는 것 아닌가?”라는 의문이 있었다.

  • 기능적으로는 가능하다.

  • 예를 들어 주문 테이블에 user_id, product_id 같은 UUID 값을 저장하면 해당 주문이 어떤 사용자와 상품을 참조하는지 알 수 있다.

→ 하지만 UUID는 데이터를 식별하기 위한 값일 뿐, DB 구조 자체를 분리해주는 개념은 아니다.

1
2
UUID = 데이터를 식별하는 방법
논리 DB = 서비스별 데이터 공간을 분리하는 방법
  • schema 분리 방식에서도 “다른 schema 직접 JOIN 금지”, “다른 서비스 데이터는 API로 조회” 같은 규칙을 정하면 가능은 하다.

  • 하지만 이는 결국 약속에 의존하는 방식이다

같은 서비스 내부 테이블끼리는 참조할 수 있다

논리 DB 분리는 다른 서비스의 DB를 직접 참조하지 않게 하려는 것이지, 같은 서비스 내부 테이블 간 참조를 막는 것은 아니다.

예를 들어 hub_db 안의 p_hubp_hub_to_hub는 둘 다 허브 서비스가 소유하는 데이터이므로 서로 참조할 수 있다.

1
2
3
4
hub_db
└── public
    ├── p_hub
    └── p_hub_to_hub

즉, 같은 서비스 내부 테이블끼리는 FK나 JOIN을 사용할 수 있다. 다만 다른 서비스의 DB와 직접 FK를 걸거나 JOIN하는 것은 지양해야 한다.

Gradle 멀티모듈 구조를 이해했다

팀 프로젝트는 하나의 GitHub 레포 안에서 여러 마이크로서비스를 관리하는 구조로 진행하기로 했다.

따라서 Gradle 멀티모듈 구조를 사용하는 방향이 자연스럽다고 판단했다.

  • 멀티모듈 구조에서는 루트에 settings.gradle, build.gradle을 두고, 각 서비스 폴더에도 개별 build.gradle을 둔다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      hub-delivery-system
      ├── build.gradle
      ├── settings.gradle
      ├── common-module
      │   └── build.gradle
      ├── eureka-server
      │   └── build.gradle
      ├── gateway-service
      │   └── build.gradle
      ├── user-service
      │   └── build.gradle
      └── ...
    
    • 루트 build.gradle은 Java 버전, Spring Boot 버전, 공통 repository, 테스트 설정 등 전체 공통 설정을 관리한다.

    • 반면 각 모듈의 build.gradle은 해당 서비스에 필요한 의존성을 따로 관리한다.


트러블슈팅 및 고민한 점

1. schema 분리와 논리 DB 분리가 계속 헷갈렸다

처음에는 schema를 나누고 UUID로만 참조하면 충분하지 않을까 생각했다. 하지만 schema 분리는 같은 DB 안에서 구역만 나누는 것이기 때문에, 다른 schema의 테이블을 직접 JOIN할 수 있다는 점이 문제였다.

결국 핵심은 “UUID를 쓰느냐”가 아니라, “서비스 간 DB 접근을 구조적으로 얼마나 분리할 수 있느냐”였다. 논리 DB 분리는 각 서비스가 자신의 DB에만 접근하도록 만들 수 있기 때문에, MSA의 데이터 소유권을 더 명확하게 표현할 수 있다.

2. 초기 프로젝트 세팅 범위를 어디까지 해야 하는지 고민했다

처음에는 폴더 구조만 만들면 되는지, 아니면 build.gradle, settings.gradle, Dockerfile, application.yml까지 만들어야 하는지 헷갈렸다.

정리해보니 초기 세팅 담당자가 바로 모든 파일을 만드는 것은 위험할 수 있었다. 각 서비스의 Spring Boot 프로젝트, Dockerfile, application.yml은 담당자별 설정이 다를 수 있기 때문이다.

그래서 우선은 다음 범위까지 진행하는 것이 적절하다고 판단했다.

  • 레포 생성

  • 팀원 collaborator 초대

  • dev 브랜치 생성 및 기본 브랜치 설정

  • 기본 폴더 구조 생성

  • .gitkeep 추가

  • PR/Issue 템플릿 작성

  • GitHub Ruleset 설정

  • 멀티모듈 기본 구조를 위한 settings.gradle, 루트 build.gradle, 각 모듈별 build.gradle 기본 틀 작성

반면 src, application.yml, Dockerfile, 실제 서비스 코드는 각 서비스 담당자가 이후에 추가하는 방식으로 정리했다.


오늘의 정리

오늘은 단순히 레포를 만드는 작업을 넘어, MSA 프로젝트에서 초기 구조를 어떻게 잡아야 하는지 많이 고민했다.

특히 DB 분리 방식에서 schema 분리와 논리 DB 분리의 차이를 이해한 것이 가장 큰 수확이었다. schema 분리도 가능하지만, 이는 직접 JOIN하지 않겠다는 약속에 가깝다. 반면 논리 DB 분리는 서비스별 데이터 경계를 구조적으로 더 강하게 나누는 방식이다.

또한 GitHub 레포 생성, dev 브랜치 설정, 폴더 구조 생성, PR/Issue 템플릿, GitHub Ruleset 설정을 진행하면서 팀 프로젝트에서 초반 협업 환경을 잡는 과정도 경험했다.

오늘 느낀 점은 초기 세팅은 단순히 “폴더를 만드는 일”이 아니라, 이후 팀원들이 어떤 방식으로 개발하고 협업할지를 결정하는 중요한 작업이라는 점이다. 초반에 기준을 잘 잡아두면 이후 개발 과정에서 충돌과 혼란을 줄일 수 있을 것 같다.

댓글

궁금한 점, 피드백, 오류 제보를 남겨 주세요.