原子資料集發布
Publish session 是給「整份資料必須一次對讀者可見」的批次通道。Moderator 先開啟 session,把一個或多個經驗證的 chunk 暫存到所有 record read 都看不到的區域,再派送 commit。Commit 會在同一個資料庫 transaction 裡軟刪除選填的 replace 集合,並新增所有 staged rows:不是完整新資料集一次落地,就是讀者繼續看到完整舊資料集。
這不是 bulk-insert、bulk-update、bulk-delete 的部分成功語意。三種保證的並排比較見批次操作。
Scope 與權限
Publish session 只存在於 department 與 company table:
| Scope | Base path |
|---|---|
| Department | /private/module/custom_tables/department/{department_id}/tables/{table_id}/publish-sessions |
| Company | /private/module/custom_tables/company/tables/{table_id}/publish-sessions |
沒有 chatroom 路由。每個操作都要求目前仍具 table moderator 權限。開啟 session 的 moderator 擁有變更權:只有他能 stage chunk、commit 或 abort;任何目前的 moderator 都能讀取 status,讓共同管理者看出是誰占用 active publish slot,以及 commit 為何失敗。
資料表帶有 require_approval 或 channel 規則時不能開啟 session;write_policy 為 commands_only 時,則以 403 governed_context_required(operation: "publish"、surface: "publish_session")拒絕。Publish commit 無法先 hold 等待日後覆核,而 background worker 也沒有 acting chatroom 可通過 channel floor。
1. 開啟唯一 session
對 department 或 company base path 送出相同 payload:
{
"idempotency_key": "catalog-2026-08-20",
"replace_filter": [
{
"column": "col_55555555_5555_4555_8555_555555555555",
"op": "eq",
"value": "current"
}
]
}兩個欄位都可省略。replace_filter 省略或為空時是 append-only publish;提供時最多包含 20 個已儲存欄位條件,並使用內部欄位鍵。Server 會在開啟時立即編譯,因此未知或 computed column 會在任何資料暫存前就失敗。Chunk 暫存時會把每一列對照存活資料列,而不是對照 replace 的結果:與「這次 replace 會刪除的資料列」衝突的資料列——相同的 unique 值,或同一分割內重疊的 date_interval_exclusion 期間——會在暫存時以 400 invalid_rows 被拒絕。請把這樣的資料列改放到空閒的值或區間,或先更動存活資料列。
idempotency_key 最長 128 字,空字串視同省略。對同一張表與同一位 actor,只要原 session 尚未過期,且狀態為 open、committing 或 committed,以同一 key 重送就會回傳原 session 並標記 replayed: true,不會再開一個。已 aborted 的 session 即使原 expiry 尚未到,也不會被重播。
其他任何 top-level key、超長的 key,或超過 20 個 predicate,都會在儲存任何資料前以 422 拒絕。開啟時若表格不在路由的 scope 內,會回 404 Table not found in this department(或 company);schema 操作正持有 table lock 時則回 423 Locked(見遷移)。
{
"session_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-21T00:00:00Z",
"replayed": false
}硬性到期時間是建立後 24 小時。到期採惰性處理:到了 expires_at 本身不會發生任何事。下一個碰到這個 session 的請求——讀取 status、chunk、commit 或 abort,或同一張表的另一次開啟——才會把它標為 expired、清掉已 staged 的資料列並釋放 slot。從 expires_at 起,同一個 idempotency key 不會再重播它。每張表同一時間只能有一個 open 或 committing session;在 owner abort、session 過期,或 commit 完成釋放 slot 之前,第二次開啟會收到 409 session_conflict。
2. 暫存可安全重試的 chunks
每個 chunk 送到:
POST {base}/publish-sessions/{session_id}/chunks{
"seq": 0,
"records": [
{ "訂單編號": "A-1001", "狀態": "current" },
{ "訂單編號": "A-1002", "狀態": "current" }
]
}Chunk contract 有明確上限:
seq是0到63的整數,因此一個 session 最多有 64 個 chunk slot。超出範圍的seq、空的records清單或未知的 top-level key 都是422。- 每個 chunk 可放 1–5,000 列;整個 session 的所有 sequence slots 合計最多 5,000 staged rows。
- 每列使用一般 create body shape:顯示名稱或內部鍵,以及單筆建立路徑接受的 link 與 attachment shape。不要送
id。Validator 內部可能暫用一個 ID,但真實 record ID 只由 commit 的 create path 產生。 - 重送既有
seq會原子取代該整個 chunk。遇到回應不明時請重用同一 sequence number,不要以新號碼附加一份重複資料。 - Schema validation 與 advisory rule precheck 會檢查完整 chunk;任一列失敗,整個 chunk 都拒絕,回傳 zero-based
row_indexerrors——最多列出前 100 筆失敗資料列,error_count仍然精確——原本該 slot 的內容保持不變。 - 每一列也必須低於單筆 record 大小上限(
Record data is <n> bytes; the limit is <limit> bytes),且不得內嵌二進位內容;兩者都以 row error 回報。 - 驗證一次只看一個 chunk。Rule precheck 會拿一列與 live records 比較,並且——僅限
unique與unique_tuple的 key——與同一個 chunk 的其他列比較,絕不與其他seq已 staged 的列比較,因此拆在兩個 chunk 裡的重複uniquekey 會通過暫存,要到 commit 才以rule_violation失敗,並標出該列的seq與row_index。其他每一種 rule(no_overlap、count_limit、exists/not_exists、date_interval_exclusion)都只讀 live 資料列,所以彼此衝突的暫存列都會通過暫存,並以同樣方式在 commit 時失敗。 - 對其他表格的 session 做 chunk、commit、abort 或讀取 status,會回
404 Publish session not found on this table。 - Chunk、commit 或 abort 碰到已超過 24 小時期限的 session 時,會當場讓它過期,並回
409 {"error": "session_expired", "message": "Publish session expired; open a new session", "session_id": …};已 staged 的資料列會被清掉。
接受的列只存在 staging table。一般 record list、search、saved view、public read、callback 與 agent tool 都看不到。成功回應的 accepted count 與 staged_total 只代表已暫存,不代表已發布。
3. 派送原子 commit
確認 staged_count 後呼叫:
POST {base}/publish-sessions/{session_id}/commitSession 必須是 open,且至少已有一列;table lock 會回 423 Locked,session 仍保持 open。選填的 worker query parameter 用來選擇 lane——legacy、batch、sqs 或 pubsub——預設為部署的 DEFAULT_BACKGROUND_WORKER_TYPE(該變數未設定或無效時為 legacy)。成功回應是非同步派送:
{
"ticket_id": "ct-publish-44444444-4444-4444-8444-444444444444",
"replayed": false
}這不代表 commit 已完成。Session 已是 committing 或 committed 時重試,只會回既有 ticket 並標記 replayed: true,絕不重複派送同一次 commit。
提交給 batch、sqs 或 pubsub worker 時若拋出錯誤,呼叫會回 500,detail 為 Failed to submit task to AWS Batch: … 或 Failed to submit task to SQS-ECS: …,ticket 也會寫成 failed。這次提交仍可能已送進佇列,所以 session 會刻意停在 committing:重試 commit 會重播那張 failed ticket,abort 則以 session_not_open 拒絕,直到 24 小時期限過後,下一個請求才會讓 session 過期。提交開始前的失敗則會重新開啟 session。決定下一步之前,請先讀 session status。
Worker 會在一個 transaction 內完成整次 flip:
- 重新載入並鎖住 session 與 table,重新檢查 opener 現在的權限。
- 若有
replace_filter,將它與 opener 目前的 row ACL 及 scope floor 以 AND 合併;不在授權集合內的列永遠不會成為刪除目標。 - Live replacement set 最多 5,000 列。更廣的結果會以
replace_target_cap_exceeded失敗;請縮小 filter 或拆分發布。 - 經由真正的 delete core 軟刪除所有獲授權 replacement rows。
- 經由真正的 create core 新增全部 staged rows,包括 schema(因此
decimalcell 會以正規形式儲存)、rules(含same_dimension_reference完整性檢查與date_interval_exclusion期間)、links、attachments 與 scope enforcement。 - 只 commit 一次,接著清掉 staging 並把 session 標為
committed。
最後 commit 前的任何失敗都會回滾完整 flip。讀者永遠看不到刪到一半或新增到一半的資料集。
同時輪詢兩個來源
以 generic task ticket 追蹤 worker 進度:
GET /public/task/{ticket_id}並同時讀取 session:
GET {base}/publish-sessions/{session_id}Generic ticket 告訴你 background work 是 submitting、processing、completed 或 failed。它一開始為 processing(legacy)或 submitting(其他 lane),execution_environment 為 fastapi、aws_batch 或 sqs_ecs,並帶有 progress、total_records、inserted_records、deleted_records 與 current_step,每新增 500 列改寫一次;新增的列在最後唯一一次 commit 前都看不到。completed 結束時 progress 為 100。failed 帶 progress 0、結構化的 error 與 error_message。Ticket 在最後一次寫入後保留 24 小時,請在那之前讀取結果。只有 commit 該 session 的使用者能輪詢這張 ticket;其他呼叫者得到 404 {"detail": "Task not found"},與未知 id 的回應相同(見誰可以輪詢哪種 ticket)。Session 則提供 publish lifecycle(open、committing、committed、aborted、expired)、staged count、到期時間、ticket、replace filter 與最近一次 structured commit error。共同 moderator 可以讀這份 status,但不能變更 session。
在不暴露部分資料的前提下重試或放棄
可重試的 validation、row、link、permission 或 rule 失敗會回滾 transaction、記錄 structured error、讓 session 回到 open,並保留所有 chunks。請以相同 seq 重送來修正 staged chunk,或修好 live dependency,再次 commit;新的 attempt 會得到新 ticket。不要只看 generic ticket failed 就認定 session 已中止,必須讀 session status。
Session 的 error.error_code 會告訴你屬於哪一種情況:
error_code | 之後的 session |
|---|---|
table_not_found(表格已刪除)、session_scope_mismatch(表格已換公司) | aborted;清掉 staging |
session_expired(worker 執行前已過期) | expired;清掉 staging |
actor_not_authorized(開啟者已不是 moderator 或管理者) | open;保留 staging,直到 owner 恢復權限或 abort |
approval_conflict、channel_rule_conflict(session 開啟後才加上 require_approval 或 channel rule) | open;移除該 rule 或 abort |
governed_context_required(資料表在 session 開啟後才變成 commands_only) | open;把 write_policy 改回 any 或 abort |
replace_target_cap_exceeded(附 matched 與 limit) | open;縮小 filter 或拆分發布 |
rule_violation、permission_denied、validation_error、commit_failed | open;修正該列或 live dependency 後再次 commit |
由某一筆 staged 列造成的失敗,會在 error 中加上該列的 seq 與 row_index,message 最多 2,000 字元。Session 一旦 committed,就沒有 publish-session rollback;被軟刪除的舊列改走一般 record history 與 restore 語意。
若要放棄尚未 commit 的發布,由 owner 呼叫:
POST {base}/publish-sessions/{session_id}/abort只有 open session 可以 abort。它會刪除所有 staged rows 並釋放 active slot;已進入 committing 的 commit 不能在這裡取消,已完成的 commit 也不能靠 abort 還原。
Client checklist
- 只在 department/company table 且確實需要一次可見時使用這條 lane。
- 將 open 回應的
session_id、expires_at與穩定 idempotency key 一起保存。 - 以 deterministic 方式編號 chunks;回應不明時用相同
seq重試。 - 絕不提供 record IDs,讓 commit 產生它們。
- Commit 前先確認 status 的
staged_count。 - 把 commit
200視為派送,接著同時輪詢 task 與 session。 - 可重試失敗時修復或取代問題 chunk 再 commit;確定放棄時趁 session 仍 open 執行 abort。
Endpoint 卡片與精確 request/response models 見批次操作參考。