議題討論: 因為想把產品做 scale out 所以要用 NoSQL 取代 產品中原有的 PostgreSQL, 畢竟 RDBMS 本來就沒有 Native multi-master 的支援 !?
前提
下文感想的前提是如果技術團隊中目前沒有 NoSQL 的高手存在,但想要將 已上市的產品立即切換成任一種 NoSQL 的情境。
不包含團隊中本來就有多位 noSQL 高手的情境
NoSQL 我自己使用過 Firebase/FireStore/DataStore/MongoDB,僅是感想紀錄
先講結論
採用 NoSQL 的時間點 ?
- 當團隊中本來就有 NoSQL 高手 (或)
- 當團隊中同時有多位熟悉(多年經驗)特定 NoSQL 的 developer (或)
- 當團隊想開發公司/部門裡的軟體,使用自己生產的軟體(Eating your own dog food),藉以累積實戰經驗 (或)
- 新創團隊,產品尚未有使用者,離上市還很遠 (或)
- 這是趨勢 ! 不管就是只想用 Cloud Service 上現成的或 BAAS
除了以上5點之外,建議沿用 PostgreSQL,把最新版的 pgSQL 的特性發揮應可應付絕大部分的場景。雖然 NoSQL 要設定 Scalability 的確容易得多(原生的),但如果會產生更多不方便的地方(如下),那何必強求容易設定 Scale-Out 這一個優點 ?
不熟 NoSQL,因此產品開發除錯時程將會拉長 如果系統對大量的數字做統計且要求是即時精準,要付出更多系統設計與心力
RDBMS Scalability ?
在 NoSQL 未流行之前,證券交易所/銀行/航運/會計 ERP 哪個不是用 RDBMS (現在應該絕大部分也是?) 所以跟上趨勢並不是非得 NoSQL 替代不可( NoSQL無法完全取代 RDBMS)。
文末會提及三個可行方式來執行 RDBMS Scalability 這個議題(補強)。
Why RDBMS ?
以下列出 RBMS 相較 noSQL 之下的優點 :
-
REDO-LOG 機制 : RDBMS的 REDO LOG 機制 會在意外發生時以 Transaction 為單位進行 Data Recovery復原資料,我在早期的 MongoDB 版本測試時即有出現資料遺失。(當時google查網友也是類似狀況不少,不過後來的 MongoDB 版本已補上REDO-LOG,效果就沒追蹤了)。RDBMS 的 REDO-LOG 這一層是經過歷史悠久千錘百鍊的 !
-
Aggregations:
至今我仍認為是 NoSql 不方便的地方。如 count()/avg()/max()/min()/sum() 等在報表SQL常用的 function。舉一個簡單的例子:/* sql : number of input rows for which the value of expression is not null*/ count(expresion)
在 sql 中就是一行,不管是在 pgSQL/mySQL/msSQL….. 任一個版本就是一行SQL ! 以舊版(2019以前)的 firebase (firestore) 來說,假設要在 books collection 中取得 countBooks,先必須在 application 中先取得 reference 的 transaction 並在 transaction 中作計算的動作(很多行程式碼) 來保證 concurrent writes 的安全。
直到 2019 年 4月 Firebase 才支援 Distributed counters
FieldValue.increment()
這種 Native Atomicity Support 才完美。更別說其他實作更古老是用 collection 取回後取 size (如下)或是 把 collection 取回後再做 forEach loop 中作加總。 就只是個 count 這個看似簡單的需求,這個例子需要求精準的數字因此在這個場景是不好用的。noSQL.collection('...').get().then( snap => { size = snap.size // collection size });
在超高流量大儲存量的社交系統中,有些計數是不需立即精準 其實以
countComments
留言數或countLikes
按讚數這個設計, 假設正確值為 1234,你在 10 分鐘以後看到1230,而在20分鐘以後看到 1233, 其實對使用者本身並不存在傷害性。 -
Table Join :
RDBMS 有 INNER JOIN/CROSS JOIN/LEFT OUTER JOIN。 在 NoSQL 中因為鼓勵資料重複也就是 A Node中有 B,B Node中 也有 A,UserNode 中同時有 A+B,這樣子才會讓讀取資料 read 快,這時候你在 schema 上就要先設計好, 也就是說可能會 Join 到懷疑人生。NoSQL 的 schema refactor 會非常痛苦(schema less 是騙人的)
-
RDBMS 有Transaction/commit Recover :
-
RDBMS 有 constraint :
Unique , Index Key, Foreign Key:在 DB 這一層保證資料的正確性。
Why Postgresql ?
-
資料型態支援 jsonb/json : ANSI SQL 2016 標準中, JSON 已經列入 SQL 標準。 其 pgSQL 原支援的jsonb型態已能在資料分析時接近Schemaless Design 且 在 2019-9 發布的 pgSQL v12版已開始提供 json-path(a query language for json) 可以在 sql中存取 json 還有內建大量的json functions
-
pgSQL 從N年前(v7 ?) 就開始支持 CTE和Window functions,反觀 mySQL 的CTE 和 window function要 v8.0之後才有。現在 pgSQL 已經到 version 12 !
-
postgresql 早已支援 full column histogram statistics
-
postgresql 已千錘百鍊在各種操作情境中被優化 + 內建報表函數
-
postgresql 資料量在單台機器上的
- 單一 Table 可以是 32 TB 以上(v10之後不再受限32TB)
- 單一欄位 1 GB
還有其他優點,不列舉了 (應該說不找了)
因此若團隊使用熟悉的 pgSQL,可以有更快的開發速度。 並有高資料安全性(RDBMS 的 cocurrent I/O , REDO LOG 設計)
水平擴充是因為單體效能不佳 ?
如果是因為performce不佳是想要做 scale out的動機,那應該先講求 vertically scaling
追求效能,可以分三類來追各環節的細節:資料庫設定有問題 or Schema 設計有問題 or SQL Query 有問題
1. 資料庫設定不要只採用預設值
- shared_buffer:預設128MB~256MB,可以設定 25%~40% memory
- work_mem,
- max_parallel_workers, max_parallel_maintenance_workers
- maintenance_work_mem
- effective_cache_size
- max_connections, temp_buffers, effective_io_concurrency…..
以上一堆設定可以根據資料庫狀態調校
2. Schema Design
-
避免型態過度使用 :
能用 small integer 就用 smallint 就不要用 integer。例如 User Status 這狀態 init/registed/normal/revoked/deleted/suspended 頂多7種8種為何要用 int 去設計呢? 又如 dept 部門數量,明知道部門不可能有上億個部門, 卻用 int 設計 ? (當然這在小系統中小流量的效能影響不明顯,但在高流量或大量 table join 下成本驚人) -
盡可能減少 index :
table 每增加一個 index 都會消耗掉 write 的效能( index 是以 犧牲 write 效能去提升 read )。也就是說若欄位越多->index 越多->只會越拖死 Write -
避免設計多用途的 tabl 而增加 Null field 的機會 :
- 如多個 column value 是 null
- 如 schema 中有 type column , 此筆資料根據 type 決定 record 用途
-
log 可使用的 Timestamp field 使用 BRIN index
-
萬一一定要用 Text Searching 可以用 tsvector to_tsvector ts_tsquery
-
若要使用 schemaless 的情境可使用 jsonb native data type
3. SQL Query
這好像太多了
4. Connection-Pool
- 避免根本沒使用 or
- 避免使用了過時的 Connection-Pool 實作 or
- 避免使用了錯誤的設定
Horizontal Scaling
- 若資料儲存量預計設計會在 GB 級別 或以下 :
-
單機即可
- 先求系統程式碼做正確
- 先求單機效能
- 使用內建的 HA+Load Balancing+Replication
-
怕單機壞掉,掛上 Cloud 即可
- 去 AWS/Azure/GCP 租一台 128GB RAM 的機器
- 使用 Provider 的資料備份機制
- 自己寫 Trigger 備份
-
可以使用 Replication,建立多個 Readonly 的 Slave
-
- 若資料量為 TB/PB 級別 :
- 使用開源 citus,也有docker版
- 使用開源 postgres-xl
等到資料確認為TB 級別且postgreSQL的發展已被宣告成為 bottleneck 的那一天, 團隊應該大到有很多個 noSQL 高手幫忙重寫現有的系統與系統效能調教
結論
想要在小團隊中面對客戶直接維護noSQL跟SQL的產品版本未知數是挺多的! 如果是因為效能瓶頸,在做水平擴充之前先把單機的環境細節設定調整好, 最後若團隊中沒有noSQL經驗,就從吃自己的狗食,先做公司裡內部會用的軟體開始吧 !