본문으로 건너뛰기

public 스키마

목적

public 스키마는 Lumie의 주 런타임 스키마입니다. 여기에는 플랫폼 범위 테이블과 테넌트 범위 테이블이 모두 포함됩니다. 테넌트 격리는 별도 스키마로 모델링하지 않으며, tenant_id 컬럼, 백엔드 테넌트 컨텍스트, PostgreSQL RLS policy로 강제합니다.

이 페이지는 persistence를 변경하거나 어떤 테이블이 플랫폼 범위인지 테넌트 범위인지 검토하는 백엔드 개발자를 위한 레퍼런스 문서입니다.

소스 경로

경로역할
V1__create_platform_tables.sqltenantstenant_settings를 생성합니다
V13__create_event_publication.sqlSpring Modulith event publication 저장소입니다
V14__create_shedlock.sql스케줄 작업 lock 테이블입니다
V16__create_idempotency_keys.sql요청 idempotency 테이블입니다
V18__rls_baseline.sqlRLS와 함께 public에 테넌트 범위 도메인 테이블을 다시 만듭니다
V70__create_exam_question_types.sql테넌트 범위 시험 문항 유형 설정을 추가합니다
V81__assignment_redesign_expand.sqlassignment 워크플로 플래그, assignment_targets, 시험 연계 submission 필드를 추가합니다
V82__assignment_redesign_tenant_safe_indexes.sql시험 연계 assignment 데이터용 복합 인덱스와 테넌트-safe foreign key를 추가합니다
V83__drop_assignment_title_description.sql레거시 assignment title 및 description 컬럼을 제거합니다
V84__drop_assignment_type_and_draft.sqlassignment type과 draft 상태를 제거합니다
V85__assignment_exam_only_active_guard.sqlactive assignment를 linked-exam 수동 답안 과제로 제한합니다
V86__assignment_exam_fk_restrict.sqlassignment가 참조하는 exam 삭제를 제한합니다
V89__require_assignment_due_date.sql레거시 null assignment 마감일을 백필하고 assignments.due_date를 필수화합니다
V90__assignment_overdue_close_index.sql마감이 지난 active 과제 종료 쿼리를 위한 partial index를 추가합니다
V27__langgraph_schema.sql별도 langgraph 스키마와 role을 생성합니다
lumie-backend/libs/common/src/main/java/com/lumie/common/tenant/런타임 테넌트 컨텍스트 헬퍼입니다

범위 결정

테이블 유형public에 속함RLS 필요일반적인 소유자
플랫폼 카탈로그아니요Tenant, billing, app platform 모듈
테넌트 도메인 데이터exam, content, attendance 같은 제품 모듈
Outbox와 lock보통은 아니요애플리케이션 인프라
워커 checkpoint 데이터아니요아니요langgraph 같은 전용 스키마

플랫폼 테이블

플랫폼 테이블은 전역 제어 평면 레코드입니다. Lumie가 테넌트 전반에서 이 레코드를 관리해야 하므로 테넌트 RLS로 필터링되지 않습니다.

테이블목적
tenants테넌트 식별자, slug, 수명주기, 연락처, 브랜딩 필드
tenant_settingstenant별 플랫폼 설정
plans제품 요금제 카탈로그
subscriptions테넌트 구독 상태
billing_keysToss billing key 메타데이터
event_publicationSpring Modulith outbox 테이블
shedlock스케줄 작업 lock
idempotency_keysAPI idempotency 레코드

Public 안의 Tenant 범위 테이블

테넌트 데이터 테이블은 물리적으로는 여전히 public에 있지만 tenant_id를 가지고 있으며 RLS로 보호됩니다. 예시는 다음과 같습니다.

  • identity 및 사용자: users, students, staff, staff_permissions
  • 교육 모델: classes, lectures, assignments, assignment_targets, assignment_submissions, textbooks
  • 시험 및 채점: exams, exam_question_types, exam_results, question_results, omr_grading_jobs
  • 커뮤니케이션 및 콘텐츠: announcements, qna_boards, sms_messages, ai_chat_messages
  • 출결: attendance_sessions, attendance_records
  • 파일 메타데이터 및 링크: file_metadata, file_links, file_download

과제 재설계 테이블

assignment redesign은 데이터를 public에 그대로 유지하지만, 대상자, 연계 시험, 학생에게 보이는 채점 결과를 모델링하는 방식을 바꿉니다.

테이블마이그레이션 소스비고
assignmentsV18, V81, V82, V83, V84, V85, V86, V89, V90여전히 class_id를 저장하지만, 시험 연계 워크플로 필드, 필수 마감일, active/closed 상태, active 행의 필수 linked_exam_id, 그리고 정답 공개만 설정 가능한 결과 boolean을 저장합니다
assignment_targetsV81, V82새 테넌트 범위 대상자 테이블입니다. CLASS 또는 STUDENT 대상당 한 행을 가집니다
assignment_submissionsV18, V81, V82인라인 linked-exam 채점을 위해 answers jsonb, 선택적 exam_result_id, pass/fail 결과 저장을 추가합니다

여기서 중요한 호환성 규칙은 두 가지입니다.

  • assignments.class_id는 기존 수업 중심 조회를 위해 그대로 유지되지만, 학생 가시성에 대한 기준은 이제 assignment_targets입니다.
  • linked_exam_idexam_result_id는 같은 public 스키마 안의 테넌트 범위 테이블을 연결하므로 tenant_id를 포함한 복합 foreign key를 사용합니다.
  • V83V84는 assignment 소유 title, description, type, draft 상태를 제거합니다. 학생에게 보이는 assignment 이름은 연결된 exam에서 옵니다.
  • V85는 비시험 레거시 행을 닫고 active assignment가 EXAM_MANUALEXAM_AUTO를 사용하도록 요구합니다.
  • V89는 null assignment 마감일을 audit timestamp로 백필한 뒤 assignments.due_dateNOT NULL을 강제합니다.
  • V90은 스케줄된 마감 과제 종료 쿼리가 테넌트 경계와 마감일 조건을 효율적으로 사용할 수 있도록 active 행에 대한 partial (tenant_id, due_date, id) index를 추가합니다.

소스 앵커: lumie-backend/app/src/main/resources/db/migration/public/V81__assignment_redesign_expand.sqllumie-backend/app/src/main/resources/db/migration/public/V82__assignment_redesign_tenant_safe_indexes.sql

create table if not exists assignment_targets (
assignment_id bigint not null,
target_type text not null,
target_id bigint not null,
tenant_id bigint not null
);

alter table assignments
add constraint assignments_linked_exam_tenant_fkey
foreign key (linked_exam_id, tenant_id)
references exams(id, tenant_id)
on delete restrict;

V81은 또한 기존의 모든 assignments.class_id 행을 CLASS 대상으로 assignment_targets에 백필합니다. 그래서 호환성 컬럼을 삭제하지 않아도 기존 데이터가 새 대상자 모델을 통해 보이게 됩니다.

RLS 계약

테넌트 범위 테이블은 다음과 같은 대표 형태를 따릅니다.

ALTER TABLE <table_name> ENABLE ROW LEVEL SECURITY;
ALTER TABLE <table_name> FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON <table_name>
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint)
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint);

백엔드 코드는 이 테이블을 조회하기 전에 활성 트랜잭션 안에서 app.tenant_id를 설정해야 합니다. 테넌트 컨텍스트 없이 실행되는 쿼리는 fail closed 되거나 행을 반환하지 않아야 합니다.

검증

public schema를 변경할 때는 다음 검사를 사용하세요.

rg -n "ENABLE ROW LEVEL SECURITY|FORCE ROW LEVEL SECURITY|tenant_isolation" \
lumie-backend/app/src/main/resources/db/migration/public
rg -n "assignment_targets|submission_mode|evaluation_mode|linked_exam_id|show_score|exam_result_id|due_date" \
lumie-backend/app/src/main/resources/db/migration/public/V81__assignment_redesign_expand.sql \
lumie-backend/app/src/main/resources/db/migration/public/V82__assignment_redesign_tenant_safe_indexes.sql \
lumie-backend/app/src/main/resources/db/migration/public/V85__assignment_exam_only_active_guard.sql \
lumie-backend/app/src/main/resources/db/migration/public/V89__require_assignment_due_date.sql \
lumie-backend/app/src/main/resources/db/migration/public/V90__assignment_overdue_close_index.sql
rg -n "assignments_linked_exam_tenant_fkey|assignment_targets_assignment_tenant_fkey|assignment_submissions_exam_result_tenant_fkey" \
lumie-backend/app/src/main/resources/db/migration/public/V82__assignment_redesign_tenant_safe_indexes.sql \
lumie-backend/app/src/main/resources/db/migration/public/V86__assignment_exam_fk_restrict.sql
rg -n "TenantContextHolder|app.tenant_id|SET LOCAL" lumie-backend

예상 성공 신호:

  • 첫 번째 명령은 여전히 공통 RLS 패턴을 찾아야 합니다
  • 두 번째 명령은 redesign 컬럼과 assignment_targets를 찾아야 합니다
  • 세 번째 명령은 V82가 추가한 테넌트-safe foreign-key 이름과 V86의 linked-exam delete restriction을 찾아야 합니다
  • 테넌트 컨텍스트 명령은 해당 RLS 보호 행이 초기화된 테넌트 범위 안에서만 보이게 만드는 런타임 메커니즘을 여전히 보여줘야 합니다