前言

「把一部分查詢搬去 MongoDB,真的會比較快嗎?」「Cassandra、ScyllaDB 為什麼能存那麼多聊天訊息?」「NoSQL 是不是為了效能,接受寫入成功後資料還會消失?」這幾個問題其實連在一起:資料庫用什麼方式組織資料,以及它回覆成功時,到底承諾了什麼。

這篇從實際選型出發,先整理各類資料庫擅長的工作,再深入 MongoDB、Cassandra、ScyllaDB 與 Discord 的遷移案例,最後回到資料遺失、讀寫一致性和 CQRS(命令與查詢職責分離)讀模型。重點是找出哪個工作負載值得換工具,以及換了之後要承擔哪些責任。

先分清楚:資料模型、交易保證與擴展方式

NoSQL 是一個範圍很廣的分類,包含文件、鍵值、寬列、圖等不同模型。**ACID 描述交易的原子性、一致性、隔離性與持久性,和是否使用 SQL 是不同維度。**例如 MongoDB 支援單文件原子操作,也支援跨文件與分片叢集的交易,不能直接把它歸類成「沒有交易保證」。MongoDB 原子性與交易

同樣地,「有多台機器」也可能代表不同事情:有些副本是保存相同資料、分擔讀取;有些分片則各自保存不同資料、分攤寫入。能否擴展,取決於資料能不能切開、查詢是否能定位到少量分區,以及交易需要協調多少參與者。

本文依 2026 年 10 月 16 日查閱的官方資料整理。MongoDB 的機制以 WiredTiger 的一般副本集/分片部署為主;Cassandra 以官方 5.0 文件描述的一般 CQL 與 LWT 路徑為主。產品的新版本、特殊託管架構與實驗功能,仍須依採用的部署方式確認。

各類資料庫的價值,在於優化哪一種操作

如果要問「有什麼不可取代性」,我會把它改成:**在特定查詢、資料量與可靠性要求下,哪個工具能用較合理的成本完成工作?**很多功能能用別種資料庫實作,差別會出現在模型、索引、延遲與維護工作。

要解決的工作 可評估的資料庫/能力 選型時要一起看的代價
熱點快取、計數、排行榜、限流 Redis 的資料結構與命令 記憶體容量、淘汰政策、持久化與故障切換語義
一起讀寫的巢狀物件、多態商品與設定 MongoDB 文件模型 文件成長、索引、重複資料更新與交易邊界
依明確的 key/排序鍵存取大量資料 DynamoDB 的 key-value/document 模型 存取模式、分區熱點,以及額外索引的寫入與容量成本
依分區鍵持續寫入、查詢時間範圍明細 Cassandra、ScyllaDB 分區大小、熱點、compaction、repair 與一致性設定
全文搜尋、相關性排序、搜尋條件與聚合 Elasticsearch 搜尋能力 索引建置與更新、搜尋可見性,以及資料同步管線
關係路徑、多跳遍歷與關聯探索 Neo4j 圖模型 出度、遍歷範圍、資料建模與結果量;深層遍歷仍可能昂貴
依時間保存、彙整與淘汰監控資料 TimescaleDB 的時間分區與資料生命週期能力 標籤/分組數、保留政策、明細與彙整的取捨
向量相似搜尋與語意檢索 Qdrant 的向量索引、pgvector 召回率、過濾條件、索引記憶體、更新與延遲的平衡

這張表是按工作負載整理,分類不互斥,也不全是 NoSQL:TimescaleDB 是 PostgreSQL 擴充,pgvector 也在 PostgreSQL 中提供向量搜尋。選擇時應比較自己需要的能力,而非看到「時序」或「向量」就認為一定要多建一套資料庫。

接下來聚焦最常被拿來比較讀寫壓力的三個產品:MongoDB、Cassandra 與 ScyllaDB。

MongoDB、Cassandra、ScyllaDB,效能差異從哪裡來?

三者都能用於大型系統,但它們讓應用程式付出的建模與查詢成本不同。下面比較的是設計方向,沒有將不同硬體或不同保證下的 benchmark 排成通用名次。

面向 MongoDB Cassandra ScyllaDB
資料組織 文件、巢狀物件、陣列與索引 partition key、clustering key 與查詢導向資料表 CQL/分區模型,另有自身的資料分布與執行架構
常見擴展方式 分片;各分片由副本集承接讀寫 資料分區與多副本;節點可協調一般讀寫 分散式資料分布,加上節點內 shard-per-core
寫入成本要觀察 索引更新、文件更新、journal、複寫與交易 commit log、memtable、複寫、後續 flush/compaction 類似的 LSM 工作,加上 CPU/I/O 排程與跨 shard 溝通
適合先評估 商品、設定、內容與文件讀模型 已知查詢路徑的大量明細與分散式儲存 相近工作負載,且重視資源利用與尾延遲
主要風險 shard key、跨分片查詢、文件膨脹與同步策略 熱分區、讀取放大、墓碑、repair 與時間戳 同樣要處理資料分布與維護工作,也要確認功能及 driver 相容性

MongoDB:讓一起使用的資料靠近,減少讀取時的組裝

假設商品詳情需要名稱、規格、圖片與部分展示資訊,把適合一起維護的資料組成文件,可以減少每次讀取時的跨集合組裝。文件是否嵌入、哪些資料保留參照,要依更新頻率、關係型態與資料成長決定;把所有評論或歷史紀錄無限塞進同一份文件,會帶來另一種負擔。MongoDB 資料建模

彈性欄位仍需要結構管理。MongoDB 提供 schema validation,可以檢查型別與值的範圍;應用也要安排欄位版本、舊資料相容與回填,讓彈性不至於變成每個讀取端各自猜測資料形狀。MongoDB Schema Validation

這個優勢來自資料布局與查詢設計,不代表讀一份文件就保證只做一次磁碟 I/O。MongoDB 的一般索引使用 B-tree,索引數量也會增加寫入工作;查詢仍受命中率、掃描量與結果大小影響。MongoDB 索引

水平分片也需要合適的路由條件。查詢能定位到少量 shard,與需要廣播到多個 shard 再彙整,對系統的壓力不同。因此「支援 sharding」應該接著問:我們的主要查詢能帶上什麼鍵?mongos 查詢路由

Cassandra:用分區與預先建模,換取可分散的工作

聊天紀錄常見的讀法是「取得某頻道最近一段訊息」。可以用頻道加時間桶界定分區,再依訊息時間/ID 排列。這讓資料庫能定位到相關範圍;代價是後來若要改成按作者、關鍵字或其他條件查詢,必須重新評估索引或維護其他查詢模型。Cassandra 不提供一般 SQL JOIN,反正規化是重要的建模手段。Cassandra 建模差異

其 LSM(Log-Structured Merge-Tree)儲存路徑包含 commit log、記憶體中的 memtable、落成不可變的 SSTable,以及背景合併。這將一部分整理成本延後到 flush(寫出資料)與 compaction(合併整理);讀取則可能需要整合多個來源。寫入路徑較直接,並不代表維護工作消失,也不代表每種寫入都完全不用讀取或協調。Cassandra 儲存引擎

新的查詢條件也不一定只能靠全表掃描。Cassandra 5.0 提供 Storage-Attached Indexing(SAI);是否適合仍要看選擇性、跨節點工作量與實際負載,不能把它理解成任意查詢都和指定 partition key 一樣便宜。Cassandra SAI

ScyllaDB:除了叢集擴展,也重做單機的執行方式

ScyllaDB 以 C++ 和 Seastar 建立 shard-per-core 模型,讓核心管理各自的資料與資源,透過明確訊息傳遞處理跨核心工作,減少共用狀態的鎖競爭。它不依賴 JVM 的垃圾回收(GC),但仍有排程、記憶體、I/O 和背景維護成本,不能據此保證尾端延遲永遠穩定。ScyllaDB shard-per-core 架構

如果同一分區持續收到大量請求,換成 ScyllaDB 後也要處理熱點、背壓與查詢範圍。評估時應比較相同資料、硬體與讀寫保證下的結果,並把 repair、compaction 與重新分布期間的表現納入。

版本差異也值得留意:ScyllaDB 2026.3 的官方說明仍將以每 tablet Raft group 實作的 Strongly Consistent Tables 標為實驗功能。因此,不能把所有版本與表格都概括為同一套「無主、最終一致」語義,也不能直接把實驗功能當作既有部署的保證。ScyllaDB 2026.3 發行說明

Discord 案例:值得學的是如何縮小問題

Discord 在 2017 年的文章回顧,早期使用單一 MongoDB 副本集;約一億則訊息時,資料與索引超出 RAM,延遲開始不穩。他們依頻道與時間桶重做訊息存取模型,也曾因大量刪除留下的墓碑遇到問題。這是當年的部署與負載經驗,並非 MongoDB 的通用容量上限。Discord:Billions of Messages

2023 年公開的後續文章,記錄了 2022 年遷移至 ScyllaDB 的結果。P99 表示 99% 請求耗時落在該數值以內:

指標 Cassandra ScyllaDB
節點數 177 72
每節點磁碟容量 平均約 4 TB 9 TB
歷史訊息讀取 P99 40~125 ms 約 15 ms
訊息寫入 P99 5~70 ms 約 5 ms

團隊同時加入 Rust data services、請求合併與按頻道路由,降低重複查詢對熱分區的衝擊。這些是原文報告的整體系統成果;硬體配置也有差異,不能換算成固定的資料庫效能倍率。Discord:Trillions of Messages

我的解讀是:**先找出存取模式、熱點與背景維護造成的壓力,再選引擎與流量治理方式。**這比只記住最後換了哪一套產品,更容易套用到自己的系統。

「寫成功卻查不到」,先辨認是哪一種情況

應用程式沒有收到例外,不等於資料庫承諾了所有可能的持久性保證;反過來,短暫查不到也不一定是資料被刪掉。排查時先把現象拆開:

現象 可能機制 優先確認
請求送出後沒有檢查結果 未要求寫入確認、未等待非同步操作,或忽略回傳錯誤 driver 設定、await、寫入結果與錯誤處理
故障切換後,已確認的資料不見 確認範圍未涵蓋足夠副本/持久化,或故障超出保證範圍 write concern、日誌同步、複寫與切換紀錄
稍後再查就出現 副本落後、讀模型延遲或搜尋 refresh 讀取節點、投影進度、搜尋可見性
新值被另一個值取代 併發更新、LWW(依時間戳決定勝出值)、版本檢查缺失或事件亂序 寫入時間戳、版本與更新條件
過一段時間被移除 TTL、快取淘汰、保留政策或應用刪除 到期設定、eviction 指標與刪除紀錄

MongoDB:w: 1 的成功,不包含多數副本確認

以三個資料節點的副本集為例,某筆寫入只要求 w: 1,primary 確認後便可回覆。若它在其他副本取得這筆寫入前失去主節點資格,新 primary 的歷史可能沒有該筆資料;舊節點重新加入時,這段未被保留的歷史可能被 rollback。MongoDB rollback 機制

這和資料量大小沒有直接的必然關係。高負載可能擴大複寫落後,但需要追查的是寫入確認與故障時序。資料庫也不會回頭撤銷已結束的 API 回應,應用端當時沒有例外,無法證明之後不會回滾。

需要較強保存保證時,可以明確檢視這類 write concern。以下是參數示意,逾時值需依服務目標決定:

1
{ w: "majority", j: true, wtimeout: 5000 }

w 決定要等待的副本確認範圍,j 關聯日誌持久化;只設 j: true 而仍是單副本確認,無法單獨防止 failover rollback。MongoDB 5.0 起多數部署的隱含預設是 majority,但包含 arbiter 的特定拓撲仍有例外;連線與操作層的覆寫也要查清楚。Write Concern 官方文件

若發生 write concern timeout,寫入可能已在 primary 執行,不能當作確定沒寫入而盲目重送。這類「結果尚未確定」的狀態需要穩定識別碼、冪等與後續確認。同樣地,想從 secondary 立即讀到自己的寫入,要一起處理 read concern、read preference 與因果一致 session;寫入達到 majority 不代表任意副本的任意查詢都已跟上。逾時語義、因果一致性組合

Cassandra:quorum 的交集,與交易和落盤要分開看

假設同一副本集合有 N = 3,寫入等待 W = 2,讀取等待 R = 2,兩個集合至少有一個共同成員。這就是 W + R > N 的用途:讓讀取能接觸到確認過寫入的副本。LOCAL_QUORUM 的範圍則是本地資料中心,不能直接推成另一地已同步。Cassandra 一致性與複寫

它不是一張「永不遺失、任意跨列交易都正確」的證書。一般寫入使用時間戳進行衝突解析;時間戳排序和業務請求真正發生的先後可能不同。需要條件式 compare-and-set 時,Cassandra 的輕量級交易(LWT)使用共識協調;應按交易範圍與競爭情況評估,不能把一般寫入的吞吐直接套上去。Cassandra 交易保證

一致性等級也不直接等於將日誌同步到儲存裝置的 fsync 政策。以 Cassandra 文件中的 commit log 設定為例,periodic 可以在週期同步前回覆,batch 則等待日誌 fsync;是否能承受同時斷電,還要連同副本所在的故障範圍一起看。Commit log 同步方式

另外,ONE 是等待一個副本回覆,不是只對一個副本發送資料。一般寫入仍會送往對應副本;Gossip 傳播的是叢集成員與狀態資訊,不能把它當成業務資料的複寫管線。Cassandra 一般寫入與 Gossip

Redis 與 Elasticsearch:保存、複寫、查詢可見性各有設定

Redis 的非同步複寫可能在 failover 時失去尚未保留到新主節點的寫入,WAIT 也不會直接把整個部署變成強一致系統。若採用 AOF everysec,還存在日誌同步窗口;TTL 或 maxmemory-policy 淘汰則是另一種預期刪除行為。Redis WAIT 保證、持久化、Key eviction

Elasticsearch 要區分 refresh 讓搜尋看得到,與 translog 保存已執行的操作。官方目前的 translog durability 預設是 request,會等待相應日誌 fsync 後才報成功;「預設每五秒才落盤」混淆了 async 模式與同步週期。寫入成功後暫時搜不到,不能直接當成持久性失敗。Translog 設定、Refresh 說明

這些取捨也存在於關聯式部署。PostgreSQL 的非同步串流複寫在主節點故障時,同樣可能失去尚未傳到可用 standby 的交易;資料庫分類本身無法替故障切換政策背書。PostgreSQL 複寫與資料遺失窗口

墓碑與資料復活,是另一種維運問題

Cassandra 的刪除會留下帶時間戳的 tombstone,TTL 到期也會形成墓碑;實體清除還有賴 compaction 與安全條件。若離線副本未取得刪除資訊,而其他副本已清除相關墓碑,舊資料可能在同步時重新出現。因此 repair、節點離線時間與墓碑清除政策要一起規劃,不能只為省空間調低 gc_grace_seconds。Cassandra 墓碑與 zombies

對 ScyllaDB 也應依其版本與墓碑回收機制安排維護,不能把 Cassandra 某個歷史預設值直接當成通用設定。真正要監控的是墓碑掃描、compaction backlog、修復進度與節點健康狀態。

把部分讀取搬到 MongoDB,什麼時候值得?

這是一種可以評估的 CQRS 做法:寫入模型維護業務事實,讀模型則依畫面需要預先組裝資料。CQRS 本身不要求不同資料庫,讀模型也可以先放在原本的關聯式資料庫(RDBMS)。

假設訂單列表需要會員摘要、商品摘要、付款狀態與配送資訊,而且每次查詢都重複組裝。若可以接受短暫的投影延遲,可以評估把列表需要的資料預先整理成 MongoDB 文件,並為實際篩選、排序建立索引。

以下是架構示意:業務資料與 Outbox 事件在同筆本地交易提交,再由 CDC(變更資料擷取)或 relay 送到下游,避免應用直接雙寫兩套資料庫時,只成功其中一邊。Debezium Outbox Event Router

flowchart TD
    W["寫入 API"] --> T[("主資料庫<br/>業務資料與 Outbox 同筆交易")]
    T --> C["CDC 或 Outbox Relay"]
    C --> Q["事件串流"]
    Q --> P["投影處理器<br/>去重、版本檢查、重試"]
    P --> M[("MongoDB 讀模型")]
    R["查詢 API"] --> M
    T -. "全量快照與重播,用於重建" .-> P

省下讀取時的 JOIN 與組裝工作之後,會新增以下責任:

新增的情況 讀模型需要的處理
同一事件重送 穩定事件 ID、冪等更新;去重狀態與投影更新要有一致的提交方式
版本 12 比版本 11 先到 完整狀態事件可做條件式版本更新;增量事件需保序、補缺或重建,不能直接丟掉舊版本
主資料更新成功,投影持續失敗 重試、失敗事件保存、同步延遲告警與對帳修補
需要重新產生整個讀模型 定義快照與事件接續位置、刪除處理與重播程序,避免切換期間漏資料

這裡的版本比較應以同一個業務實體為範圍。若一份投影由訂單、付款與配送等不同來源組成,還需要各自的版本或明確的組裝規則;把不同來源的版本號直接相比,可能丟掉合法更新。

訂單寫入後立即查詢時,也要安排讀模型尚未跟上的行為,例如先顯示寫入 API 回傳的摘要。付款、庫存等必須依最新權威狀態判斷的操作,應回到負責該業務事實的服務,避免以落後的展示投影作為判斷依據。

若只是少數熱門查詢壓力高,先評估索引、快取、讀副本或同庫預先聚合,通常更容易控制複雜度。若需求只是存幾個動態欄位,PostgreSQL 的 jsonb 與索引也值得先評估;需要同一筆交易更新的資料,可以繼續留在相同交易邊界內。PostgreSQL JSON/JSONB

MongoDB 的價值要由「新的讀模型能省下什麼工作」來證明。把相同的關聯模型原封不動複製過去,再由應用發出一串查詢,不一定能得到預期的改善。

選型前,先回答五個具體問題

我會用下面的順序縮小候選範圍,再做符合自己資料分布的驗證:

  1. **最重要的查詢是什麼?**列出主要 key、時間範圍、排序、結果量與熱點,不只寫一個總 QPS。
  2. **哪些更新必須一起成功?**確認單文件、單分區、跨實體交易與條件式更新的範圍。
  3. **成功回覆要承受什麼故障?**區分程序重啟、單節點毀損、故障切換、整個故障區域失效與誤刪。
  4. **可以接受多久的舊資料?**快取、讀副本、搜尋與投影延遲都要有對應的產品行為。
  5. **誰負責維護與重建?**把索引、repair、compaction、備份還原、回補與對帳一起列入。

效能比較也要對齊副本數、寫入確認、日誌同步、資料大小、索引與查詢分布。除了正常時的吞吐與 P95/P99,還要觀察故障、背景維護與資料重新分布期間,能否維持服務目標。單看關閉部分保證後的最高寫入速度,無法支持正式環境的選型。

NoSQL 可以作為主資料庫,也可以是可重建的讀模型;關鍵是明確界定哪個服務與儲存負責哪一份業務事實。副本提供可用性,備份與可驗證的還原程序處理另一類恢復需求,兩者都要納入設計。

總結

**MongoDB 的文件模型、Cassandra 的分區與查詢導向設計、ScyllaDB 的核心隔離與排程,各自優化不同的工作。**它們的價值需要放進實際資料布局、查詢路徑和可靠性要求來看,不能只用 NoSQL 或 ACID 標籤判斷。

面對「寫成功卻不見」的回報,先區分寫入確認、故障切換、讀取延遲、併發覆寫與預期淘汰;面對讀取壓力,先確認要改變的是資料模型、索引、流量治理,還是儲存引擎。把問題定位清楚,才知道多維護一套資料庫能換回什麼。

延伸閱讀:Event Sourcing、Outbox Pattern、Message Queue 與一致性。

封面為 AI 生成的概念插畫。