NoSQL 到底解決什麼問題?從 MongoDB、Cassandra、ScyllaDB 看選型與資料可靠性
前言
「把一部分查詢搬去 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 的價值要由「新的讀模型能省下什麼工作」來證明。把相同的關聯模型原封不動複製過去,再由應用發出一串查詢,不一定能得到預期的改善。
選型前,先回答五個具體問題
我會用下面的順序縮小候選範圍,再做符合自己資料分布的驗證:
- **最重要的查詢是什麼?**列出主要 key、時間範圍、排序、結果量與熱點,不只寫一個總 QPS。
- **哪些更新必須一起成功?**確認單文件、單分區、跨實體交易與條件式更新的範圍。
- **成功回覆要承受什麼故障?**區分程序重啟、單節點毀損、故障切換、整個故障區域失效與誤刪。
- **可以接受多久的舊資料?**快取、讀副本、搜尋與投影延遲都要有對應的產品行為。
- **誰負責維護與重建?**把索引、repair、compaction、備份還原、回補與對帳一起列入。
效能比較也要對齊副本數、寫入確認、日誌同步、資料大小、索引與查詢分布。除了正常時的吞吐與 P95/P99,還要觀察故障、背景維護與資料重新分布期間,能否維持服務目標。單看關閉部分保證後的最高寫入速度,無法支持正式環境的選型。
NoSQL 可以作為主資料庫,也可以是可重建的讀模型;關鍵是明確界定哪個服務與儲存負責哪一份業務事實。副本提供可用性,備份與可驗證的還原程序處理另一類恢復需求,兩者都要納入設計。
總結
**MongoDB 的文件模型、Cassandra 的分區與查詢導向設計、ScyllaDB 的核心隔離與排程,各自優化不同的工作。**它們的價值需要放進實際資料布局、查詢路徑和可靠性要求來看,不能只用 NoSQL 或 ACID 標籤判斷。
面對「寫成功卻不見」的回報,先區分寫入確認、故障切換、讀取延遲、併發覆寫與預期淘汰;面對讀取壓力,先確認要改變的是資料模型、索引、流量治理,還是儲存引擎。把問題定位清楚,才知道多維護一套資料庫能換回什麼。
延伸閱讀:Event Sourcing、Outbox Pattern、Message Queue 與一致性。
封面為 AI 生成的概念插畫。











