Skip to content

DB 스키마 관리

truloop-core의 데이터베이스 스키마 관리 전략, Exposed ORM 테이블 정의, 마이그레이션 방식을 설명합니다.


스키마 관리 도구: Atlas

정보

스키마 변경은 별도의 database_schema Repository에서 관리합니다. truloop-core 코드에서 직접 DDL을 실행하지 않습니다.

선언적 스키마 관리

truloop은 전통적인 마이그레이션 파일 방식이 아닌, Atlas를 사용한 선언적(Declarative) 스키마 관리를 채택합니다.

항목설명
도구Atlas
스키마 파일../database_schema/schema.pg.hcl
방식선언적 (원하는 상태 정의 → 자동 마이그레이션 SQL 생성)
Repositorydatabase_schema (별도 저장소)

작업 흐름

스키마 수정

database_schema/schema.pg.hcl 파일에서 원하는 테이블/컬럼 변경을 작성합니다.

차이 확인

Atlas가 현재 DB 상태와 HCL 파일을 비교하여 마이그레이션 SQL을 자동 생성합니다.

적용

생성된 SQL을 검토 후 적용합니다.

위험

절대 규칙: DB에 직접 DDL을 실행하지 않습니다. 모든 변경은 schema.pg.hcl을 통해야 합니다.


Exposed ORM 테이블

truloop-core는 Exposed ORM (JetBrains)을 사용하여 DB에 접근합니다. 테이블 정의는 :adapter:persistence 모듈에 위치합니다.

테이블 구조 예시

kotlin
// adapter/persistence/user/UsersTable.kt
object UsersTable : LongIdTable("users") {
    val eid = varchar("eid", 32).uniqueIndex()
    val username = varchar("username", 50).uniqueIndex()
    val name = varchar("name", 100)
    val phoneNumber = varchar("phone_number", 50).uniqueIndex()
    val profileImageUrl = varchar("profile_image_url", 2048).nullable()
    val socialLinks = jsonb<Map<String, String>>("social_links", Json).default(emptyMap())
    val email = varchar("email", 255).nullable()
    val emailVerifiedAt = timestampWithTimeZone("email_verified_at").nullable()
    val locale = varchar("locale", 35).default("en-US")
    val birthDate = date("birth_date").nullable()
    val isProfilePrivate = bool("is_profile_private").default(false)
    val isActive = bool("is_active").default(true)
    val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
    val updatedAt = timestampWithTimeZone("updated_at").defaultExpression(CurrentTimestampWithTimeZone)
    val deletedAt = timestampWithTimeZone("deleted_at").nullable()
}

테이블 타입

Exposed ORM 테이블 정의 시 세 가지 베이스 클래스를 사용합니다:

베이스 클래스용도PK 방식
LongIdTable대부분의 엔티티 테이블Auto-increment id: Long
CompositeIdTable복합 PK가 필요한 테이블복합 키 (.entityId())
TableJunction 테이블 (좋아요, 북마크 등)수동 PrimaryKey 지정

FK 분리 원칙

정보

테이블 간 FK 참조에 Exposed의 reference()를 사용하지 않고 long() primitive를 사용합니다. FK 무결성은 Atlas 스키마에서 DB 레벨로 관리합니다. 이를 통해 persistence 어댑터 간 의존성을 제거하고 독립적인 컴파일을 가능하게 합니다.

전체 테이블 목록

테이블Exposed 클래스베이스설명
usersUsersTableLongIdTable사용자 정보
loopsLoopsTableLongIdTable룹 (핵심 비즈니스 엔티티). availability_deadline nullable timestamptz 컬럼 포함 (가용시간 응답 마감 시각, null이면 전원 응답 시에만 COLLECTED)
loop_participantsLoopParticipantsTableCompositeIdTable룹 참여자 (PK: loopId + userId)
loop_mediaLoopMediaTableLongIdTable룹에 첨부된 미디어. uq_loop_media_loop_eid 복합 유니크 인덱스 (loop_id + eid)
loop_invite_tokensLoopInviteTokensTableLongIdTable룹 초대 토큰
loop_guest_participantsGuestParticipantsTableLongIdTable비회원 참여자
loop_commentsLoopCommentsTableLongIdTable룹 댓글
storiesStoriesTableLongIdTable리캡 (내부명: Story)

DB 연결

Connection Pool

HikariCP 7.0.2를 사용하여 커넥션 풀을 관리합니다. 설정은 AppConfig의 DB 섹션에서 로드됩니다. Pool 이름은 truloop-hikari-pool이며, Micrometer를 통한 메트릭 모니터링이 활성화되어 있습니다.

SSM 터널을 통한 로컬 접속

bash
# Dev DB 터널 시작 (localhost:54320)
make db-start-dev

# Dev DB 터널 종료
make db-stop-dev
bash
# 터널 상태 확인
make db-status

필수 도구: AWS CLI, Session Manager Plugin, Pulumi, jq

정보

SSM Jump Service는 truloop-infra에서 환경별로 상시 관리됩니다.


트랜잭션 관리

UnitOfWork 패턴

Exposed의 트랜잭션을 UnitOfWork Port로 추상화합니다. Use Case 레벨에서 트랜잭션 경계를 설정합니다.

kotlin
// application/shared/port/UnitOfWork.kt
interface UnitOfWork {
    suspend fun <T> transactional(
        readOnly: Boolean = false,
        block: suspend () -> T,
    ): T
}
  • readOnly = true를 지정하면 읽기 전용 트랜잭션으로 실행됩니다
  • 기존 트랜잭션이 진행 중이면 재사용합니다

롤백 규칙:

  • 블록이 정상 완료되면 커밋 (sealed result 실패 반환도 커밋)
  • 예외가 throw되면 롤백
  • 비즈니스 실패 시 롤백이 필요하면 sealed result 대신 예외를 throw

주의

규칙:

  • Exposed 작업은 반드시 UnitOfWork.transactional로 감싸야 합니다
  • 오류는 예외를 throw하여 처리합니다 (Either 패턴 미사용)
  • Use Case 레벨에서 트랜잭션을 관리합니다
  • 중첩 트랜잭션은 지양합니다

DB 공유

truloop-ai-server는 동일한 Aurora PostgreSQL 인스턴스를 읽기 전용으로 공유합니다. 주요 공유 테이블:

  • loops - 룹 정보
  • loop_media - 미디어 정보
  • content_templates - 콘텐츠 템플릿
  • stories - 리캡 정보
  • story_creation_history - 리캡 생성 이력

변경 이력

날짜내용
2026-03-12이미지 그룹핑 테이블 추가: loop_grouping_results (그룹핑 결과 캐시), loop_group_representative_overrides (대표 사진 오버라이드). loop_mediauq_loop_media_loop_eid 복합 유니크 인덱스 추가
2026-03-11loops 테이블에 availability_deadline 컬럼 추가 (nullable timestamptz, 가용시간 응답 마감 시각)
2026-03-10소스 코드 기준으로 전면 검증: UsersTable 예시를 실제 스키마로 교체, 전체 테이블 목록을 탭별로 재구성 (누락 테이블 20개 이상 추가), 테이블 타입/FK 분리 원칙 섹션 추가, UnitOfWork readOnly 파라미터 반영, HikariCP 버전 7.0.2로 수정, DB 공유 테이블 목록 수정