題幹與適用場景
設計類似 Google Calendar 的事件衝突處理系統。使用者可以建立、修改、刪除事件;事件包含參與者、開始結束時間、時區、重複規則、提醒、可見性和會議連結。建立或修改時要判斷衝突,並支援拒絕、警告後繼續或允許覆寫。假設空閒時間查詢遠多於寫入,常見請求需要數十毫秒級回應;私有行事曆只能暴露 busy 狀態。
面試官考察點
強回答會先定義衝突語意,再談資料庫。核心訊號包括:使用半開區間避免首尾相接誤報;把規範事件模型與重疊查詢索引分開;明確 RRULE、例外和滾動物化邊界;說明時區規則如何轉成絕對時間;為衍生索引選擇 outbox 或 CDC;並處理兩個寫入者同時預訂同一資源的競態。只說「查資料庫是否重疊」無法涵蓋這些失敗路徑。
回答前需要釐清的問題
- 首尾相接的兩個事件是否衝突?若不衝突,應使用半開區間
[start, end)。 - 重複系列需要展開多遠?無限重複必須採用滾動窗口或依查詢展開。
- 多位參與者中只檢查必要參與者,還是任一 busy 參與者都阻止建立?這決定查詢扇出與策略。
- 重複預訂是拒絕、警告或允許覆寫?會議室等硬資源與個人行事曆可能不同。
- 查看他人行事曆時回傳完整細節、busy 區間還是完全隱藏?這決定 ACL 與回應模型。
30 秒回答框架
「我會保留規範事件和參與者表,再維護按行事曆分區的空閒時間索引。衝突使用半開區間:只有 existing.start < new.end 且 new.start < existing.end 才重疊;free 事件不佔用時間。重複規則按時區保存 RRULE,在有限時間窗物化並記錄例外。寫入透過版本檢查或資源鎖保持一致,事件提交後由 outbox 更新衍生索引。查詢只回傳呼叫者有權限看到的 busy 資訊,並監控索引漂移、重建與通知延遲。」
分步驟深入解答
1. 定義時間與衝突謂詞
把每次發生實例表示為 [startutc, endutc),保留原始時區和本地規則供顯示與重複展開。兩個實例衝突,當且僅當 a.start < b.end 且 b.start < a.end。因此 10:00–11:00 與 11:00–12:00 不衝突;零時長或結束早於開始的輸入應拒絕。全天事件先按行事曆時區轉換為邊界,再使用同一謂詞。
2. 分離規範模型與可用性索引
規範層保存事件、參與者、RRULE、例外、ACL 和版本號,適合編輯與稽核。可用性索引只保留 calendar_id、實例起訖、佔用狀態、事件 ID 和可見性標籤,按行事曆與開始時間排序;衝突查詢掃描目標時間窗,而不是讀取完整事件 JSON。索引可按月份或租戶分區,熱門行事曆需要快取或專用分片。
3. 處理重複規則與例外
保存 iCalendar 風格的 RRULE,查詢未來窗口時展開實例。滾動物化例如只產生未來 90 天,接近窗口時延伸;查詢更遠範圍時按需展開並快取。this occurrence、this and future 和整個系列修改必須產生例外紀錄或新的系列版本,不能直接改寫過去實例。每個實例都要獨立參與衝突判定與通知。
4. 寫入流程與並行控制
建立或修改先驗證時間、ACL 和參與者策略,再在同一交易中檢查目前索引版本。個人行事曆可用樂觀版本號,衝突時讓客戶端重試;嚴格互斥的會議室或預約資源可按資源加鎖或使用資料庫排他約束。提交事件後寫 outbox 紀錄,由消費者更新索引並送出通知;索引暫時落後時,讀取路徑可回源規範表或標記結果不確定,不能靜默回傳沒有衝突。
5. 隱私、查詢與規模
查詢回應按 ACL 分層:有權限者看到標題和參與者,其他人只看到 busy 時段,私有事件顯示為不可辨識的佔用區塊。自由、暫定、外出狀態按產品策略映射為佔用或提示。讀多寫少時可對常用時間窗快取;快取鍵包含行事曆版本或失效水位。跨組織 free/busy 查詢應設定逾時和部分結果策略,避免一個外部行事曆拖慢所有參與者。
6. 一致性、修復與失敗場景
規範事件是事實來源,索引是可重建的衍生物。outbox 或 CDC 確保提交成功後最終一定產生索引更新;消費者依事件版本冪等處理,舊版本訊息不能覆蓋新版本。定期比較規範實例與索引的雜湊或計數,發現漂移後按行事曆重建並記錄影響範圍。通知送出失敗可重試,但不能回滾已成功提交的事件。
高品質示範回答
我會先約定 [start, end) 的衝突規則:只有兩個區間嚴格相交才算衝突,首尾相接不衝突,free 事件不佔用時間。規範事件表保存原始時區、RRULE、例外、參與者和 ACL;另外維護按行事曆分區的實例索引,只存起訖時間、佔用狀態和事件 ID,以便在時間窗內快速掃描。RRULE 按事件時區保存,未來 90 天滾動物化,遠期按查詢展開,例外分別表示單次、後續或整個系列修改。
寫入時先做權限和策略檢查,再用樂觀版本或資源鎖保護同一行事曆的並行修改。事件和 outbox 在同一交易提交,消費者依版本冪等更新索引並送出通知;索引是衍生物,可從規範表重建。回傳衝突時按 ACL 只給可見資訊。若是個人行事曆,我會允許警告後繼續;若是會議室這類硬資源,則使用排他約束或鎖。監控重點是索引漂移、版本落後、查詢延遲和外部 free/busy 逾時。
常見錯誤
- 錯誤表現:用
start <= other.end判斷重疊 → 首尾相接被誤報,提醒和空閒時間錯誤 → 明確採用半開區間並拒絕非法零時長事件。 - 錯誤表現:每次查詢都展開無限重複系列 → 複雜度和延遲沒有上限 → 設定滾動物化窗口,遠期按需展開並快取。
- 錯誤表現:把完整事件 JSON 直接當衝突索引 → 讀取路徑掃描大欄位且洩漏隱私 → 建立只含時間和佔用狀態的衍生索引,並按 ACL 脫敏。
- 錯誤表現:索引更新失敗就回滾使用者事件 → 事實來源與通知、搜尋耦合,恢復困難 → 交易寫 outbox,非同步重試並提供索引重建。
- 錯誤表現:用全域鎖解決所有並行 → 熱門行事曆拖慢全系統 → 只在嚴格互斥資源按資源鎖,普通行事曆使用版本檢查和重試。
追問及應對
如何實作「找出所有參與者都空閒的時間」?
先為每個必要參與者查詢目標窗口的 busy 區間,再在統一時間軸上做區間合併與交集的補集。參與者越多,查詢扇出越大,應並行執行、限制窗口長度,並對無權限或逾時的行事曆回傳部分結果和不確定標記,而不是暴露隱私資訊。
夏令時間切換後,重複會議只在部分日期衝突,如何排查?
檢查 RRULE 的時區識別、原始本地時間、展開器版本和例外紀錄;不要把本地時間先轉 UTC 後遺失時區再重複。用跨 DST 邊界的固定樣例重放,比較每次實例的本地顯示時間、UTC 邊界和衝突謂詞,確認是規則展開還是索引物化錯誤。
嚴格禁止會議室雙重預訂時,版本檢查不夠怎麼辦?
兩個請求可能同時讀到空閒。對同一資源的時間範圍使用資料庫排他約束、可序列化交易或短時資源鎖,並讓失敗請求明確回傳衝突實例。鎖只能涵蓋提交窗口,不能依賴快取;高熱點資源可按資源分片並限制重試,避免鎖佇列拖垮其他行事曆。