Skip to content

[Demo][Reliability] RLS 환경에서 Demo Seed tenant context·transaction 경계 호환 #204

Description

@krestar

한 줄 목표

PostgreSQL RLS가 활성화된 제한 Runtime DB Role 환경에서도
DEMO_SEED_ENABLED=true를 유지한 채 Demo/Test Seed가 최초 기동과 동일
DB 재기동 모두 정상·멱등하게 수행되도록 Demo Seed의 tenant context와
transaction 경계를 정리합니다.

현재 배포 전제

이 이슈는 아직 배포되지 않은 신규 환경을 준비하는 작업이 아닙니다.

  • Server·PostgreSQL DB·Infra는 이미 dev 환경에 배포되어 외부 URL로
    서비스 중입니다.
  • Server는 SPRING_PROFILES_ACTIVE=dev로 실행 중입니다.
  • 배포 환경은 DEMO_SEED_ENABLED=true이며 애플리케이션 기동·재기동 시
    Demo Seed가 실행됩니다.
  • 기존 PostgreSQL PVC에는 현재 Demo/Test fixture가 저장되어 있습니다.
  • 이 변경은 기존 fixture를 삭제하거나 새 DB로 교체하지 않고, 기존
    DB/PVC를 그대로 유지한 재기동에서도 호환성을 보장해야 합니다.
  • PR feat: PostgreSQL RLS 기반 사업장 2차 격리 활성화 #135 배포 전 실제 Runtime Role이 Migration Role과 분리되어 있고
    RLS를 우회하지 않는지는 현재 dev 배포 DB의 live catalog 기준으로
    별도 확인합니다.

왜 필요한가요?

PR #135는 PostgreSQL RLS를 활성화하여 애플리케이션의 company_id 범위
조건에 더해 DB가 다른 사업장의 행을 한 번 더 차단하도록 합니다.

현재 배포 환경은 DEMO_SEED_ENABLED=true이며 Demo/Test fixture를
startup seed로 유지하고 있습니다. 하지만 현재 Demo Seed runner는 RLS가
활성화된 제한 Runtime Role과 호환되지 않습니다.

PR #135에서 수행한 수동 검증에서는 RLS 활성화 후 Seed를 켠 상태로
재기동할 때 다음 흐름으로 실패했습니다.

  1. tenant context 없이 기존 company를 조회
  2. RLS에 의해 기존 Demo/Test company 행을 조회하지 못함
  3. Seed가 company가 없는 것으로 판단하고 신규 insert 시도
  4. RLS WITH CHECK에 의해 SQLSTATE 42501로 차단
  5. startup Seed 실패로 애플리케이션 기동 실패

특히 RLS Flyway migration은 Seed runner보다 먼저 commit되므로, 이
상태에서 Pod가 기동에 실패해도 DB에는 RLS가 활성화된 상태가 남습니다.
단순 image rollback만으로 migration을 되돌릴 수 없습니다.

현재의 임시 우회책은 #135 배포 전에 DEMO_SEED_ENABLED=false로 변경하는
것이지만, 이 경우 기존 데이터는 유지되더라도 재기동 시 Demo/Test
fixture의 자동 복구·보충 기능을 사용할 수 없습니다.

따라서 배포 환경에서 Demo Seed를 계속 활성화하려면 PR #135보다 먼저 Seed
자체를 RLS-aware하게 만드는 별도 호환 변경이 필요합니다.

현재 대상

현재 startup Demo Seed의 주요 단계는 다음 세 runner입니다.

  • DemoAuthSeedRunner
    • Demo/Test 회사·사용자 등 인증 fixture
  • DemoWorkerSeedRunner
    • Demo/Test 근로자 fixture
  • DemoOperationalSeedRunner
    • 업무·문서·승인 등 운영 fixture

현재 구조에서는 Demo/Test 두 회사에 대한 Seed가 각 runner의 큰
transaction 경계 안에서 함께 처리될 수 있어, RLS 활성 환경에서 회사별
tenant context를 안전하게 적용하기 어렵습니다.

작업 범위

1. Demo/Test 회사별 transaction 분리

  • 각 runner의 Demo/Test Seed 처리를 회사별 transaction으로
    분리합니다.
  • 한 회사의 transaction에서 다른 회사의 fixture를
    조회·생성·수정하지 않습니다.
  • transaction 실패 시 해당 회사 Seed 단위로 rollback되도록 합니다.
  • 앞선 회사 transaction이 성공한 뒤 다음 회사 transaction이
    실패하면, 성공한 회사의 commit은 유지하고 실패한 회사만 rollback한
    뒤 startup 전체를 실패 처리합니다.
  • 기존 runner 실행 순서와 fixture 의미는 유지합니다.

2. transaction-local tenant context 적용

  • 각 회사 Seed transaction 시작 직후 해당 company_id를 DB tenant
    context에 설정합니다.
  • company 존재 여부 조회·생성처럼 RLS 적용 대상에 접근하기
    전에도 필요한 tenant context가 설정되어 있어야 합니다.
  • tenant context는 connection pool에 남지 않도록 transaction-local
    방식으로 적용합니다.
  • 기존 애플리케이션의 app.company_id transaction-local tenant DB
    context 계약을 재사용합니다.
  • Seed 전용으로 RLS를 우회하는 별도 권한이나 전역 bypass 경로를
    만들지 않습니다.

3. transaction 실행 구조 정리

  • 같은 Bean 내부의 @Transactional self-invocation에 의존하지
    않습니다.
  • 회사별 transaction을 확실히 분리할 수 있도록 별도 transaction
    Bean, TransactionTemplate 또는 동등한 구조를 사용합니다.
  • transaction 시작 → tenant context 설정 → 해당 회사 Seed 실행
    순서를 테스트로 보장합니다.
  • Seed 종료 후 tenant context가 다음 transaction/connection으로
    누출되지 않음을 검증합니다.

4. 기존 Seed 멱등성 유지

  • 빈 DB의 최초 Seed에서 Demo/Test fixture가 정상 생성됩니다.
  • 동일 DB를 재기동해도 기존 fixture를 다시 찾을 수 있습니다.
  • 재기동으로 company/user/worker/task/document 등 기존 Seed
    데이터가 중복 생성되지 않습니다.
  • 일부 fixture가 이미 존재하는 DB에서도 기존 Seed의 보충 동작을
    유지합니다.
  • DEMO_SEED_ENABLED=false일 때 Seed가 실행되지 않는 기존 동작은
    변경하지 않습니다.

구현 원칙

이번 작업은 RLS를 약화시키지 않고 Seed를 기존 tenant 계약에 맞추는
방향으로 구현합니다.

다음 방식으로 문제를 우회하지 않습니다.

  • Runtime DB Role에 SUPERUSER 또는 BYPASSRLS 부여
  • Runtime Role을 tenant table owner로 변경하여 RLS 우회
  • Seed 실행 중 임시로 DISABLE ROW LEVEL SECURITY
  • 모든 tenant를 볼 수 있는 Seed 전용 unrestricted Repository 추가
  • connection session 전체에 tenant context를 남기는 방식
  • #135의 RLS policy 범위를 축소하여 Seed를 통과시키는 방식

Seed도 실제 Runtime과 같은 tenant isolation 원칙을 따라야 합니다.

완료 조건

자동 테스트

  • 기존 전체 테스트 통과
  • PostgreSQL 16 기반 Demo Seed 통합 테스트 추가
  • RLS 비활성 상태에서 기존 Demo Seed 회귀 없음
  • RLS 활성 + 제한 Runtime Role + DEMO_SEED_ENABLED=true 최초
    기동 성공
  • 동일 조건에서 같은 DB 재기동 성공
  • Demo/Test 두 회사의 Seed 데이터가 각 회사 context에서 정상
    조회됨
  • 다른 회사 context에서는 상대 회사 Seed 데이터가 노출되지 않음
  • 재기동 후 Seed 대상 주요 엔티티 중복 0건
  • transaction 종료 후 tenant context 누출 없음
  • Seed 실패 시 실패한 회사 transaction의 부분 반영이 없고, 이미
    성공한 다른 회사 transaction의 결과는 다음 재기동에서 멱등하게
    재사용됨

PostgreSQL 제한 Role 검증

다음 검증은 table owner/superuser가 아닌 실제 제한 Runtime Role로
수행합니다.

  • Flyway migration Role과 Runtime Role이 분리되어 있음
  • Runtime Role은 rolsuper=false, rolbypassrls=false이고 tenant
    table owner가 아님
  • Runtime Role에는 애플리케이션에 필요한 최소
    DML·sequence·bootstrap 함수 권한만 부여됨
  • RLS 활성 상태에서 Demo company Seed 성공
  • RLS 활성 상태에서 Test company Seed 성공
  • context 없는 tenant table 접근이 기존 RLS 계약대로 차단/비가시
    처리됨
  • Seed가 RLS bypass 권한 없이 성공함
  • SQLSTATE 42501로 startup이 실패하지 않음

재기동 Smoke Test

  1. RLS 활성 PostgreSQL에 DEMO_SEED_ENABLED=true로 서버를 기동합니다.
  2. Demo/Test 로그인과 주요 화면 데이터를 확인합니다.
  3. 같은 DB/PVC를 유지한 채 서버 Pod를 재기동합니다.
  4. 두 번째 기동도 정상 완료되는지 확인합니다.
  5. Demo/Test 회사·사용자·근로자·주요 운영 fixture의 중복이 없는지
    확인합니다.
  6. Demo/Test tenant 간 데이터 격리가 유지되는지 확인합니다.

권장 검증·배포 순서

이 이슈의 호환 변경과 #135를 한 번에 배포하지 않습니다.

  1. 이 이슈 구현 PR merge
  2. RLS가 아직 활성화되지 않은 현재 환경에 먼저 배포
  3. DEMO_SEED_ENABLED=true 상태의 최초/재기동 회귀 확인
  4. #135를 최신 main에 갱신하고 RLS 활성화 migration을 포함한 임시
    통합 branch 또는 동일 commit 조합을 준비
  5. 운영 DB가 아닌 격리된 새 PostgreSQL DB에서 다음 리허설 완료
    • Flyway 전체 migration 적용 및 validate
    • 제한 Runtime Role 사용
    • DEMO_SEED_ENABLED=true 최초 기동
    • 동일 DB 재기동
    • Demo/Test 로그인·fixture 멱등성·tenant 격리 확인
  6. 위 리허설이 통과한 경우에만 feat: PostgreSQL RLS 기반 사업장 2차 격리 활성화 #135 merge 및 RLS migration 배포
  7. 배포 직후 DEMO_SEED_ENABLED=true + 제한 Runtime Role + RLS enabled
    상태에서 재기동 Smoke Test
  8. 이상이 없으면 Seed 활성 상태를 유지

PR #135가 먼저 배포되어야 하는 긴급 상황에서는
DEMO_SEED_ENABLED=false를 임시 운영 우회책으로 사용할 수 있지만, 이
이슈가 배포·검증되기 전에는 Seed를 다시 true로 전환하지 않습니다.

이번 이슈에서 하지 않는 것

  • #135의 RLS policy 자체 변경
  • 새로운 tenant isolation 모델 도입
  • Runtime DB Role 권한 완화
  • Flyway RLS 활성화 migration 추가/변경
  • Demo fixture 내용의 대규모 개편
  • Demo/Test 데이터 초기화 또는 재생성 정책 변경
  • 기존 PostgreSQL PVC 데이터 삭제
  • production profile에서 Demo Seed 활성화
  • Infra Secret 원문 또는 DB credential 변경

연계

Metadata

Metadata

Assignees

Labels

area:serverSpring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외priority:P0MVP 진행을 막는 최우선 핵심 작업status:in-progress담당자가 현재 구현 중인 작업type:bug재현 가능한 오류를 수정하는 작업

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions