Files
portfolio-hub/docs/technical/persistence-and-migrations.md
T

12 KiB

Persistence and Migrations

  • Related REQ: REQ-001, REQ-003

목표

DB 기반 영속성 경로를 안정적으로 운영하고, 스키마 변경 이력과 문서 동기화를 일관되게 유지한다.

저장소 선택 규칙

  • PORTFOLIOHUB_DB_ENABLED=true이면 Postgres 리포지토리를 사용한다.
  • DB 비활성 모드는 개발/부트스트랩 목적의 제한적 동작으로 간주한다.

비활성 동작 기준:

  • PORTFOLIOHUB_DB_ENABLED=false이면 미구현 placeholder repository가 FeatureNotReadyError를 발생시킨다.

데이터 모델 핵심 영역

테이블 역할
portfolios 현재 포트폴리오 상태
portfolio_ledger 거래/자금이동 이력 스냅샷
portfolio_comments 포트폴리오 코멘트 이력 (REQ-001)
strategy_sets 전략 설정 스냅샷
users 운영 사용자
auth_tokens 세션/클라이언트/봇 토큰
bot_principals 외부 봇 주체

스키마 문서 기준

  • Source of truth: Alembic revisions in migrations/versions
  • Current baseline revision: 20260410_0009
  • 상세 테이블/컬럼/인덱스 제약은 이 문서가 Alembic revision과 동기화된 기준 문서다.

마이그레이션 운영 흐름

  1. 신규 스키마 변경을 Alembic revision으로 추가한다.
  2. migrations/versions/에 revision 파일을 저장한다.
  3. 본 문서에 revision ID와 스키마 변경 내역을 반영한다.
  4. portfoliohub migrate-db 또는 Alembic upgrade head로 적용한다.

명령 기준

  • 초기 DB 준비: portfoliohub init-db
  • 버전 업그레이드: portfoliohub migrate-db
  • 상태 점검: portfoliohub health-check

명령 동작:

  • init-db: target DB가 없으면 생성 후 Alembic head 적용
  • migrate-db: 기존 target DB에 Alembic head 적용

스키마 기준

Conventions

  • 데이터 타입 표기는 SQLAlchemy/Alembic 선언명을 따른다.
  • NULL은 nullable을 의미한다.
  • Default는 DB-level server_default를 의미한다.
  • FK 제약이 없어도 logical relationship column은 문서화한다.

Tables

users

Purpose:

  • 운영 사용자 레코드와 향후 identity mapping 기준 저장
Column Type Null Default Constraints / Notes
id Text NO - Primary Key
username Text NO - Unique username
email Text YES - Unique when present
permission Text NO read User permission level
is_active Boolean NO TRUE Whether login is allowed
password_hash Text YES - Scrypt password hash
created_at DateTime(timezone=True) NO NOW() Creation timestamp
updated_at DateTime(timezone=True) NO NOW() Last update timestamp
last_login_at DateTime(timezone=True) YES - Last successful login timestamp

Constraints:

  • Primary key: id
  • Unique: uq_users_username on (username)
  • Unique: uq_users_email on (email)

auth_tokens

Purpose:

  • DB 발급 session/client/bot token 저장
Column Type Null Default Constraints / Notes
id Text NO - Primary Key
user_id Text NO - Logical user reference
name Text NO - Human-readable token name
token_prefix Text NO - Display prefix
token_hash Text NO - Unique SHA-256 token hash
permission Text NO - Effective token permission
token_type Text NO - session, client, or bot
bot_principal_id Text YES - Logical reference to bot_principals.id
expires_at DateTime(timezone=True) YES - Expiration time
last_used_at DateTime(timezone=True) YES - Last successful use
revoked_at DateTime(timezone=True) YES - Revocation time
created_at DateTime(timezone=True) NO NOW() Creation timestamp

Constraints:

  • Primary key: id
  • Unique: uq_auth_tokens_token_hash on (token_hash)

bot_principals

Purpose:

  • 외부 bot identity와 권한 ceiling 관리
Column Type Null Default Constraints / Notes
id Text NO - Primary Key
name Text NO - Unique bot name
permission Text NO - Bot permission ceiling
description Text YES - Optional description
is_active Boolean NO TRUE False면 bot token 거부
created_at DateTime(timezone=True) NO NOW() Creation timestamp
updated_at DateTime(timezone=True) NO NOW() Last update timestamp
deactivated_at DateTime(timezone=True) YES - Deactivation time

Constraints:

  • Primary key: id
  • Unique: uq_bot_principals_name on (name)

strategy_sets

Purpose:

  • 정규화된 전략 설정 snapshot 저장
Column Type Null Default Constraints / Notes
id Text NO - Primary Key
name Text NO - Strategy name, unique pair 일부
config Text NO "" Strategy config payload
nick Text NO - Unique nickname
created_at DateTime(timezone=True) NO NOW() Creation timestamp

Constraints:

  • Primary key: id
  • Unique: uq_strategy_sets_name_config on (name, config)
  • Unique: uq_strategy_sets_nick on (nick)

portfolios

Purpose:

  • 현재 포트폴리오 상태와 strategy linkage 저장

Current baseline note:

  • 아래 컬럼은 revision 20260403_0008까지의 마이그레이션 기준이다.
Column Type Null Default Constraints / Notes
portfolio_id Text NO - Primary Key
broker Text NO kis Broker code
status Text NO enabled Portfolio status
name Text NO - Asset name
symbol Text NO - Trading symbol
cash_balance Float NO - Current cash balance
asset_quantity Float NO - Current asset quantity
asset_price Float NO 0 Current price snapshot
profit_buffer Float NO 0 Profit buffer amount
active_strategy_id Text YES - Logical reference to strategy_sets.id
avabile_strategies Text[] YES - Nullable array of strategy_sets.id
created_at DateTime(timezone=True) NO NOW() Creation timestamp
updated_at DateTime(timezone=True) NO NOW() Last update timestamp
last_trade_at DateTime(timezone=True) YES - Last trade time

Constraints:

  • Primary key: portfolio_id
  • Foreign key constraints: none in baseline

portfolio_ledger

Purpose:

  • 포트폴리오 액션별 append-like ledger snapshot 저장
Column Type Null Default Constraints / Notes
portfolio_id Text NO - Logical reference to portfolios.portfolio_id
recorded_at DateTime(timezone=True) NO - Event timestamp
action Text NO - Action type
price Float NO - Trade/valuation unit price
quantity Float NO - Quantity delta
fee_amount Float NO 0 Fees
tax_amount Float NO 0 Taxes
cash_balance_after Float NO - Post-action cash
asset_quantity_after Float NO - Post-action asset quantity
total_value_after Float NO - Post-action total value
profit_buffer_after Float NO 0 Post-action profit buffer
strategy_set_id Text NO legacy Logical strategy set reference
note Text NO "" Operator/system note

Constraints:

  • Primary key: none in baseline
  • Foreign key constraints: none in baseline

portfolio_comments

Purpose:

  • 포트폴리오별 multiline comment history 저장 (REQ-001)
Column Type Null Default Constraints / Notes
id Text NO - Primary Key (UUID)
portfolio_id Text NO - Logical reference to portfolios.portfolio_id
content Text NO - Multiline comment text
created_at DateTime(timezone=True) NO NOW() Creation timestamp
updated_at DateTime(timezone=True) NO NOW() Last update timestamp

Constraints:

  • Primary key: id
  • Foreign key constraints: none

Indexes

Index Table Columns/Expressions Unique Intent
ix_users_created_at users created_at no recency listing
idx_auth_tokens_user_created auth_tokens user_id, created_at no current user token listing
idx_auth_tokens_hash_active auth_tokens token_hash no bearer token lookup
idx_auth_tokens_bot_created auth_tokens bot_principal_id, created_at no bot token listing
idx_bot_principals_active_created bot_principals is_active, created_at no active/inactive bot filtering
idx_portfolio_ledger_portfolio_time portfolio_ledger portfolio_id, recorded_at DESC no recent ledger query
idx_portfolios_broker_status_updated portfolios broker, status, updated_at DESC no broker/status recency filtering
idx_portfolio_comments_portfolio_created portfolio_comments portfolio_id, created_at DESC no reverse chronological comments

Conditional migration behavior:

  • timescaledb extension 존재 시 create_hypertable('portfolio_ledger', 'recorded_at', if_not_exists => TRUE) 실행
  • 이 경우 portfolio_ledger는 hypertable로 동작한다.

Integrity Notes

현재 baseline은 DB-level FK constraint를 의도적으로 두지 않는다.

  • logical relation은 application/repository logic이 관리한다.
  • 향후 FK constraint를 추가하면 migration ID와 함께 본 문서를 갱신한다.
  • application layer cascade:
    • 포트폴리오 삭제 시 연관 portfolio_comments를 hard delete 한다.

Validation Notes

  • 마이그레이션은 빈 DB와 기존 DB 모두에서 연속 적용 가능해야 한다.
  • 파괴적 변경(drop/rename)은 백필 또는 호환 단계 계획 없이 직접 반영하지 않는다.
  • 문서 동기화 게이트(scripts/check_docs_sync.py)를 통과해야 한다.

Revision Mapping

Revision ID File Summary
20260403_0001 migrations/versions/20260403_0001_initial_portfolio_schema.py Initial schema for strategy sets, portfolios, ledger, indexes, optional hypertable
20260403_0002 migrations/versions/20260403_0002_strategy_set_columns.py Rename strategy_name to name, strategy_config to config, add unique nick
20260403_0003 migrations/versions/20260403_0003_portfolio_strategy_refs.py Remove embedded strategy fields, add active_strategy_id and avabile_strategies
20260403_0004 migrations/versions/20260403_0004_users_table.py Add users table with unique username/email and created_at index
20260403_0005 migrations/versions/20260403_0005_auth_user_fields_and_tokens.py Add user auth fields and auth_tokens table
20260403_0006 migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py Add bot_principals and bot token linkage
20260403_0007 migrations/versions/20260403_0007_bot_principal_permission_only.py Remove owner_user_id and enforce bot-local permission model
20260403_0008 migrations/versions/20260403_0008_portfolio_name_column.py Add non-null portfolios.name and backfill from symbol
20260410_0009 migrations/versions/20260410_0009_portfolio_comments_table.py Add portfolio_comments table for per-portfolio memo history

Open Implementation Points

  • portfolio_ledger의 Timescale hypertable 강제 여부와 보존 정책(retention)을 확정한다.
  • REQ-002 태그 검색 구현 시 인덱스 전략(B-tree/GiN 등)과 마이그레이션 단계를 추가 정의한다.