同步演算法
Mindwtr 採用在地優先同步,並以確定性方式處理衝突。
本頁是供維護與除錯使用的技術合併參考。面向使用者的後端設定、復原步驟與操作指南,請參閱資料與同步。
輸入與輸出
- 輸入 A:本機快照(
tasks、projects、sections、areas、settings) - 輸入 B:遠端快照(相同形狀)
- 輸出:合併快照 + 合併統計(
conflicts、clockSkew、timestampAdjustments、futureTimestampClamps、conflictIds、conflictReasonCounts、conflictSamples、timestampAdjustmentIds、futureTimestampClampIds),另加有界同步診斷日誌。
快照式傳輸
Mindwtr 目前透過合併完整快照來同步。這是刻意採用的設計,不是尚未建置的 delta 層。
- ADR 0003 與 ADR 0007 定義在快照承載資料上執行的修訂版本感知合併行為。
- ADR 0008 記錄保留快照同步、延後 append-only delta 日誌的傳輸決策。
- ADR 0009 記錄 SQLite 至 JSON 橋接合約:SQLite 是主要本機儲存區,
data.json則是同步/備份快照。 - 以目前規模而言,相較於重播及壓縮各裝置的操作日誌,這能讓同步保持不可分割,也更容易推理。
只有在快照檔案經常超過 5 MB、一般網路上的同步往返超過 5 秒,或 Mindwtr 需要即時多裝置串流時,才應重新檢視 ADR 0008。屆時 delta 設計應擴充現有 rev 與 revBy 中繼資料,而不是另建一套平行序列機制。
合併規則
- 依
id比對實體。 - 實體只存在一側時,予以保留。
- 兩側皆存在時,使用修訂版本感知 LWW 合併:
- 有修訂版本中繼資料時,先比較
rev(較高者勝出)。rev是每個實體的編輯計數器,不是 vector clock,因此一部裝置上的多次離線編輯,可能勝過另一部裝置上一次較新的編輯。 - 修訂版本相同時,比較
updatedAt(較新者勝出)。 - 時間戳記相同時,若兩側有不同裝置 ID,依字典順序比較
revBy,再以正規化內容簽章進行確定性平手裁決。revBy步驟不是裝置優先順序,而是在內容簽章備援執行前,讓每個 peer 對相同修訂版本、相同時間戳記的衝突收斂至相同勝方。 - 沒有修訂版本中繼資料的舊版實體,會將 5 分鐘時鐘偏差閾值內的
updatedAt視為模糊平手,並採用確定性簽章勝方。超出該範圍則由較新的updatedAt勝出。
- 有修訂版本中繼資料時,先比較
- 軟刪除使用操作時間:
- 刪除標記的操作時間 =
max(updatedAt, deletedAt)。 - 使用中與已刪除的衝突,由較新的操作時間勝出。
- 若刪除與使用中操作時間相差不超過 30 秒,且修訂版本號相同,Mindwtr 會保留使用中項目,而不讓刪除標記立刻勝出。這是刻意設計的模糊時間範圍規則,可能讓剛刪除的任務在另一部裝置同時編輯後再次出現。
- 在該 30 秒範圍內,若修訂版本不同,仍由較高修訂版本勝出。
- 沒有修訂版本中繼資料的舊版記錄,在同一時間範圍內以刪除標記優先。
- 備份還原是模糊時間範圍之外唯一刻意設計的例外:若使用中一側的
revBy = "backup-restore",且其操作時間不早於刪除標記,則由還原的使用中項目勝出。 - 刪除勝過使用中編輯時,Mindwtr 會輸出一筆有界
syncConflictDiscarded診斷項目,包含實體類型、ID、操作時間與修訂版本中繼資料。 - 保留模糊時間範圍內的使用中項目時,Mindwtr 會輸出一筆有界
Preserved live item during ambiguous delete-vs-live merge診斷項目,並在同步歷史/設定中儲存衝突中繼資料。
- 刪除標記的操作時間 =
- 無效
deletedAt會退回使用updatedAt,採用保守操作時間。 - 附件依附件
id,採用相同 LWW 規則合併。 - 專案歸檔還原中繼資料是不透明的同步中繼資料:
- 歸檔專案時,會在歸檔動作所變更的任務與分區上記錄暫時還原中繼資料。
- 可比較與確定性同步簽章會忽略此中繼資料,因此單是歸檔簿記不會產生內容衝突或確定性勝方。
- 取消歸檔只會還原仍符合歸檔所產生變更的記錄。已刪除、手動修改或移至其他專案的任務會原樣保留,且可能保有不透明中繼資料,直到下一次因真正的使用者或同步變更而重寫該記錄。
- 領域使用刪除標記:
- 刪除領域時只會為領域本身建立刪除標記。該領域中仍使用中的專案與任務會解除連結(以新的修訂版本清除
areaId)並保持使用中。刪除領域永遠不會刪除子項目。 - 還原領域不會重新連結先前已解除的子項目;它們會保持使用中但沒有領域。
- 若傳入快照參照缺少或已刪除的領域,同步修復會清除過時的
areaId參照並標記修復修訂版本。 - 同步修復也會在刪除標記上執行,因此已刪除專案/任務日後還原時,不會保有過時的領域連結。
- 缺少的領域順序值會在合併時合成,並標記
revBy: "sync-repair",避免修復持續遭 peer 覆寫。
- 刪除領域時只會為領域本身建立刪除標記。該領域中仍使用中的專案與任務會解除連結(以新的修訂版本清除
- 依同步偏好合併設定:
- 外觀/語言/GTD 排程/外部行事曆/AI/已儲存篩選條件可個別合併。
- 衝突解決使用群組層級時間戳記(
appearance、language、gtd、externalCalendars、ai、savedFilters)。 - 同一群組內不同欄位的同時編輯,仍可能收斂至較新的群組更新。
- 已儲存篩選條件依篩選條件
id合併。使用中與使用中的已儲存篩選條件衝突,會嚴格使用篩選條件的updatedAt;只有時間戳記相同或無法使用時,才以確定性方式平手裁決。 - 某群組在本機
syncPreferences中選擇不參與同步,會雙向生效:Mindwtr 不會將該群組傳至遠端,也不會接受傳入的遠端變更。 - 密鑰(API 金鑰、本機模型路徑)永遠不會同步。
- 遠端寫入復原是明確流程:
- 本機資料會先寫入並帶有
pendingRemoteWriteAt。 - 遠端寫入成功後清除旗標。
- 遠端寫入失敗後,會以指數退避安排重試,從 5 秒增加至最多 5 分鐘。
- 遠端寫入重試失敗 12 次後,Mindwtr 會將同步標示為
error並顯示後端失敗,而不是無限重試。 - 裝置本機同步診斷會保留在本機,遠端寫入前會移除。
- 時鐘偏差遙測:
- 合併統計會記錄觀察到的最大偏差。
- 偏差超過 5 分鐘時顯示警告。
- 超出合併時時鐘 5 分鐘以上的未來
updatedAt值,會在比較時限制上限,並計入futureTimestampClamps。 - 同一筆記錄的兩側皆受未來時間限制時,Mindwtr 會輸出有界
Both merge candidates had future updatedAt timestamps clamped診斷項目,包含記錄 ID 與限制時間。
- 同步期間的本機編輯不會採用硬性鎖定:
- 桌面版與行動版會偵測本機狀態是否在同步寫入階段發生變更。
- 發生時會中止目前週期並排入全新同步,而不是覆寫較新的本機快照。
虛擬碼
read local
read remote
validate payload shape
normalize entities (timestamps, revision metadata)
for each entity type in [tasks, projects, sections, areas, people]:
index local by id
index remote by id
for each id in union(localIds, remoteIds):
if only one side exists: keep it
else:
winner = resolveWinner(localItem, remoteItem)
mergedItem = mergeConflict(localItem, remoteItem, winner) // attachments/settings-specific logic
push mergedItem
merge settings by sync preferences
validate merged payload
write local
write remote
record sync history and diagnostics衝突範例
範例 1:使用中與已刪除
- 本機:任務
t1於10:01更新,未刪除 - 遠端:任務
t1於10:03刪除 - 結果:已刪除版本勝出(
10:03操作時間較新)
範例 1b:模糊的刪除與使用中衝突
- 本機:任務
t1於10:00:05編輯,仍為使用中 - 遠端:任務
t1於10:00:20刪除 - 兩筆記錄的修訂版本號相同
- 結果:兩項操作只相差 15 秒,且修訂版本中繼資料在模糊時間範圍內相同,因此使用中項目勝出
範例 2:相同修訂版本與時間戳記
- 本機與遠端的
rev=4、updatedAt=10:00 - 內容不同(
title、tags等) - 結果:確定性簽章比較會在所有裝置上選出同一勝方
範例 3:無效 deletedAt
- 本機刪除標記的
deletedAt="invalid-date",且updatedAt=09:30 - 遠端使用中項目的
updatedAt=10:00 - 結果:無效刪除會退回使用
updatedAt(09:30),因此使用中項目勝出
附件
- 中繼資料合併會在檔案傳輸協調之前執行。
- 勝方附件的 URI/本機狀態可用時會予以保留。
- 若勝方沒有可用本機 URI,合併可退回另一側的 URI/狀態。
- 附件刪除與使用中的競爭會採用和任務/專案相同的合併與
syncConflictDiscarded診斷,因此已刪除附件勝過同時進行的中繼資料編輯時,可在診斷中看見。 - 缺少的本機檔案會在之後由附件同步/下載處理。
settings.attachments.pendingRemoteDeletes記錄本機刪除附件後,仍須從遠端刪除的檔案。- 待遠端刪除項目會保留到遠端刪除成功,不會隨時間清除,因為成功前丟棄項目可能讓已刪除檔案孤立在後端。
- Mindwtr Cloud 也提供經驗證的孤立項目清理端點,刪除目前快照未參照的附件檔案。
雲端伺服器合併
對 /v1/data 而言,Mindwtr Cloud 不是單純的物件儲存區。收到經驗證的 PUT /v1/data 時,伺服器會讀取現有命名空間快照、對傳入快照執行相同合併演算法、驗證合併結果,再寫入該結果。
操作影響:
- 推送完整快照不等於強制覆寫。修訂版本較高、操作時間較新或刪除標記勝出的現有遠端記錄,都可能在 PUT 後保留。
- 伺服器端參照修復可能產生連鎖更新,例如為已刪除專案下的分區建立刪除標記。
- 伺服器產生的修復時間戳記使用伺服器實際時鐘,避免快了的用戶端時鐘提前伺服器修復中繼資料。
- 成功的
PUT /v1/data回應包含{ ok: true, stats, clockSkewWarning },讓用戶端與測試檢查伺服器採用的合併結果。
快速未變更檢查
雲端用戶端可在下載完整快照前發出 HEAD /v1/data。伺服器會回傳 ETag、Last-Modified 與 Content-Length 中繼資料,但不含回應本文。用戶端將該中繼資料與上一次成功同步比較;遠端命名空間未變更時,即可略過完整 GET /v1/data 路徑。
伺服器會依檔案 stat 中繼資料快取 SHA-256 ETag,因此重複進行未變更的 HEAD 檢查時,不必重新雜湊整份快照。
排程背景同步
行動版背景同步採用保守的 15 分鐘最短間隔。背景任務只在需要時才延遲載入匯入程式/同步程式碼,再執行上述相同的快照合併與遠端寫入重試邏輯。
背景同步是否執行取決於環境:作業系統可能延遲或略過一次執行,因此連線或憑證變更後,手動同步與前景同步仍是可靠的復原方式。
重試復原
- 遠端寫入失敗不會默默丟棄剛合併的本機狀態。
pendingRemoteWriteAt、pendingRemoteWriteRetryAt與pendingRemoteWriteAttempts儲存在本機。- 下一次同步會暫停至重試時間範圍屆滿,再使用保留的本機快照與任何較新的本機編輯重試。
- 重試 12 次後,同步狀態會變成
error。保留的本機快照仍留在本機,狀態介面應引導使用者檢查後端連線或憑證。
刪除標記清除界限
刪除標記只有在保留期間內能保護刪除操作。目前的保留政策受 tombstoneRetentionDays 限制。
在實際操作上,離線時間超過保留範圍的裝置,可能重新帶入其他裝置上已清除刪除標記的記錄。Mindwtr 將此視為快照同步已有文件記載的一致性界限。使用者在依賴長期離線的舊本機資料前,應先同步該裝置;若未來協定需要更嚴格的保證,則應拒絕上次成功同步早於清除範圍的快照。
可檢查的診斷資訊
- 衝突數量與 ID
- 衝突原因數量與有界衝突範例
- 觀察到的最大時鐘偏差
- 時間戳記正規化調整
- 時間戳記已正規化的記錄 ID
- 未來時間戳記限制數量與 ID
- 使用中一側在刪除與使用中衝突中遭捨棄時的
syncConflictDiscarded項目 - 設定中的上次同步狀態/歷史
- 設定 → 同步中的個別衝突詳情:受影響項目名稱、不同的欄位,以及哪部裝置的版本勝出(限制為最近的範例)
- 即使除錯日誌關閉,也會寫入
mindwtr.log的衝突摘要(只含 ID 與欄位名稱,絕不包含記錄內容)
相關文件
疑難排解
若反覆出現衝突或偏差警告:
- 檢查裝置時鐘(啟用網路自動校時)。
- 檢查同步後端連線/驗證。
- 查看應用程式設定與日誌中的同步診斷資訊。