feat(req-002): implement portfolio tagging and tag filters
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Persistence and Migrations
|
||||
|
||||
- Related REQ: `REQ-001`, `REQ-003`, `REQ-004`
|
||||
- Related REQ: `REQ-001`, `REQ-002`, `REQ-003`, `REQ-004`
|
||||
|
||||
## 목표
|
||||
DB 기반 영속성 경로를 안정적으로 운영하고, 스키마 변경 이력과 문서 동기화를 일관되게 유지한다.
|
||||
@@ -257,6 +257,48 @@ Conditional migration behavior:
|
||||
- application layer cascade:
|
||||
- 포트폴리오 삭제 시 연관 `portfolio_comments`를 hard delete 한다.
|
||||
|
||||
## REQ-002 Tagging Storage Design
|
||||
|
||||
목표:
|
||||
|
||||
- 포트폴리오별 태그 연결을 중복 없이 저장한다.
|
||||
- 앞뒤 공백 제거 기반 정규화와 검색용 include/exclude 조건을 DB 계층에서 일관되게 지원한다.
|
||||
|
||||
구현 저장 모델:
|
||||
|
||||
- 신규 테이블 `portfolio_tags`
|
||||
- 복합 PK(`portfolio_id`, `tag_value`) 사용
|
||||
- `portfolio_id`: 대상 포트폴리오
|
||||
- `tag_value`: 앞뒤 공백 제거 후 저장되는 태그 값
|
||||
- `created_at`: 연결 생성 시각
|
||||
- 동일 포트폴리오 내 중복 태그는 PK 자체로 방지한다.
|
||||
|
||||
정규화 책임:
|
||||
|
||||
- API/CLI 입력 계층에서 앞뒤 공백 제거를 수행한다.
|
||||
- repository는 이미 정규화된 `tag_value`를 기준으로 insert/delete/query를 수행한다.
|
||||
- DB는 `UNIQUE (portfolio_id, tag_value)`로 중복 insert를 방지하고 application은 이를 `409 Conflict`로 매핑한다.
|
||||
|
||||
검색 전략:
|
||||
|
||||
- `includeTags`와 `excludeTags`는 모두 정규화된 태그 값 기준으로 비교한다.
|
||||
- `matchMode=AND`는 `GROUP BY portfolio_id` + `HAVING COUNT(DISTINCT tag_value) = <includeTags 개수>` 전략을 기본안으로 둔다.
|
||||
- `matchMode=OR`는 `WHERE tag_value IN (...)` + distinct portfolio projection을 기본안으로 둔다.
|
||||
- `excludeTags`는 anti-join 또는 `NOT EXISTS` 전략을 사용한다.
|
||||
|
||||
권장 인덱스:
|
||||
|
||||
- `UNIQUE (portfolio_id, tag_value)`
|
||||
- `INDEX (tag_value, portfolio_id)`
|
||||
- owner-scoped 목록 필터와 결합될 수 있으므로 `portfolios.owner_user_id` 인덱스와 함께 실행 계획을 검토한다.
|
||||
|
||||
적용 마이그레이션 단계:
|
||||
|
||||
1. `20260414_0012_portfolio_tags.py`에서 `portfolio_tags` 테이블 생성
|
||||
2. `fk_portfolio_tags_portfolio` FK와 `idx_portfolio_tags_tag_portfolio` 인덱스 생성
|
||||
3. API/CLI/repository가 동일 revision 기준으로 태그 mutation 및 검색을 사용
|
||||
4. 필요 시 exact-match 중심 추가 인덱스를 검토
|
||||
|
||||
## Validation Notes
|
||||
- 마이그레이션은 빈 DB와 기존 DB 모두에서 연속 적용 가능해야 한다.
|
||||
- 파괴적 변경(drop/rename)은 백필 또는 호환 단계 계획 없이 직접 반영하지 않는다.
|
||||
@@ -276,7 +318,9 @@ Conditional migration behavior:
|
||||
| 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 |
|
||||
| 20260413_0010 | migrations/versions/20260413_0010_portfolio_owner_user_id.py | Add `portfolios.owner_user_id`, backfill owner, create owner index, and add owner FK |
|
||||
| 20260414_0011 | migrations/versions/20260414_0011_bot_principal_owner_user_id.py | Restore `bot_principals.owner_user_id`, backfill owner, create owner index, and add owner FK |
|
||||
| 20260414_0012 | migrations/versions/20260414_0012_portfolio_tags.py | Add `portfolio_tags`, portfolio FK, and tag lookup index for REQ-002 |
|
||||
|
||||
## Open Implementation Points
|
||||
- `portfolio_ledger`의 Timescale hypertable 강제 여부와 보존 정책(retention)을 확정한다.
|
||||
- `REQ-002` 태그 검색 구현 시 인덱스 전략(B-tree/GiN 등)과 마이그레이션 단계를 추가 정의한다.
|
||||
- owner-scoped 조회와 태그 필터를 함께 사용할 때 결과 집합이 ownership 경계를 넘지 않아야 한다.
|
||||
|
||||
Reference in New Issue
Block a user