4.4 KiB
4.4 KiB
T501 — mongo-indexes-radar-crm
Status:
done(2026-07-31)
Milestone:M0
Kind:feat
Est. change:~120 lines
Goal
完成後系統應:make init 可重複執行並建立本 run 全部 collection 索引,含去重唯一鍵。
Depends on
- —
Inputs
- Spec:
../spec.md§8(實體與索引) - 既有索引初始化:
apps/backend/cmd/init/main.go
Outputs
程式變更(預期路徑)
| 路徑 | 動作 | 說明 |
|---|---|---|
apps/backend/generate/database/mongo/000014_demand_radar_indexes.up.json |
add | 全部 15 個索引,含兩個 unique |
apps/backend/generate/database/mongo/000014_demand_radar_indexes.down.json |
add | 對應 dropIndexes |
apps/backend/cmd/init/main.go |
edit | 七個 collection 的非 unique 索引(名稱與 spec 與 migration 完全一致) |
行為變更
- 建立索引:
radar_service_profiles:_id = owner_uid(不需額外索引)radar_watches:(owner_uid, status)、(owner_uid, created_at)radar_sweeps:(owner_uid, created_at)、(watch_id, created_at)、job_idradar_opportunities:(owner_uid, created_at)、(owner_uid, intent_band, status)、(owner_uid, external_id)uniqueradar_replies:(owner_uid, opportunity_id, variant)crm_contacts:(owner_uid, source_platform, author_handle)unique、(owner_uid, stage, last_touch_at)、(owner_uid, needs_follow_up)crm_touches:(owner_uid, contact_id, created_at)crm_followups:(status, due_at)、(owner_uid, contact_id)
- 重複執行不報錯(沿用既有 idempotent 建法)。
Out of scope
- domain struct 與 repository(各 M 的 model task)
- 資料遷移(本 run 無既有資料需搬)
Acceptance
make init連跑兩次皆成功(乾淨 DB 驗證,兩次都database indexes initialized)- Mongo
getIndexes()可見上列索引:migrate 路徑 15 個且 unique 恰為radar_opportunities.owner_opportunity_external、crm_contacts.owner_contact_identity;init 路徑 13 個非 unique - 唯一鍵語意實測:同 owner 同
external_id第二筆被擋(11000)、不同 owner 可共存;同 owner 同平台同 handle 第二筆被擋、跨平台可共存 migrate-down 1→ 再up可逆- 指令(radar/crm 部分;完整鏈路受既有缺陷阻塞,見下):
cd apps/backend && make migrate-up MONGO_URL=<scratch> && MONGO_DATABASE=<scratch> make init
Notes
(owner_uid, external_id) 是 SW-06 去重的硬保證,不能只靠應用層判斷。
落點分工(沿用 growth-loop 先例)
唯一鍵放 migration、operational 索引放 cmd/init。radar/crm 兩邊的索引名稱與 spec 完全一致,因此同一顆 DB 先 migrate 再 init 不會衝突(已實測)。
Mongo 的衝突規則(實測 Mongo 7):
| 情境 | 結果 |
|---|---|
同名同鍵、unique 不同 |
IndexKeySpecsConflict(86) |
| 同名同鍵同 spec 重建 | OK(idempotent) |
| 異名同鍵同 spec | IndexOptionsConflict(85)"already exists with a different name" |
| 異名同鍵但 spec 不同 | 允許(會多一個索引) |
既有缺陷(非本 task 造成,未修)
make migrate-up && make init 在新 DB 上無法連跑:多個既有 collection 的 migration 與 init 用同鍵不同名,init 會在第一個衝突處 panic。已確認的衝突點:
| Collection | 鍵 | migration 名 | init 名 |
|---|---|---|---|
notifications |
(owner_uid, created_at) |
owner_notifications_recent |
owner_notifications_created |
growth_outcomes |
(owner_uid, source_type, source_id) |
owner_outcome_source(unique) |
同名但非 unique |
growth_checkups |
(owner_uid, created_at) |
owner_checkups_recent |
owner_checkups_created |
growth_workspaces |
(owner_uid, archived) |
owner_workspaces |
owner_workspaces_archived |
growth_invite_rewards |
(inviter_uid, created_at) |
inviter_rewards |
inviter_rewards_created |
growth_invite_rewards |
(invitee_uid) |
invitee_reward_once |
invitee_reward |
未修的原因:要決定「以哪邊的名字為準」會影響既有部署——若某環境的索引是 init 建的,把 init 改成 migration 的名字會讓該環境從此報 85。這是需要產品/維運決定的收斂方向,不該塞在本 task 裡。建議另開 task 處理(含各環境現況盤點)。