據(jù)存儲(chǔ)利器TokuDB安裝配置與踩坑全記錄)
這臺(tái) MySQL 單機(jī) 8 萬(wàn)的寫(xiě)入量全庫(kù)爆掉之后我才正兒八經(jīng)去研究 TokuDB。當(dāng)時(shí)生產(chǎn)環(huán)境里一堆日志類數(shù)據(jù)動(dòng)輒幾百 GBInnoDB 吃磁盤吃得肉疼。換到 TokuDB 之后數(shù)據(jù)文件直接縮到原來(lái)的四分之一寫(xiě)入也不再被索引折騰得死去活來(lái)。今天我不講那些花里胡哨的理論直接把這套安裝流程里真正會(huì)踩的坑、必須配的參數(shù)、按下去就要命的選項(xiàng)全部拆開(kāi)來(lái)寫(xiě)一遍。你要是之前只在 MySQL 官網(wǎng)下載過(guò)默認(rèn)安裝包那大概率裝 TokuDB 直接會(huì)在加載插件那一步卡住。這個(gè)引擎的安裝牽扯到版本選型、底層依賴、內(nèi)核參數(shù)缺一個(gè)環(huán)節(jié)都跑不起來(lái)。下文按我從零裝的順序來(lái)適合數(shù)據(jù)庫(kù)運(yùn)維、后端開(kāi)發(fā)、以及準(zhǔn)備把冷數(shù)據(jù)從 InnoDB 遷出來(lái)的兄弟。1. TokuDB 是什么值不值得花精力裝1.1 先說(shuō)清楚它和 InnoDB 的核心差異TokuDB 最核心的東西叫 Fractal Tree分形樹(shù)索引跟 InnoDB 的 B 樹(shù)是完全不同的數(shù)據(jù)結(jié)構(gòu)。B 樹(shù)寫(xiě)入的時(shí)候最怕索引頁(yè)頻繁分裂和隨機(jī)寫(xiě)數(shù)據(jù)一多、索引一多寫(xiě)放大非常明顯。TokuDB 的做法是在樹(shù)節(jié)點(diǎn)里預(yù)留一塊消息緩沖寫(xiě)入操作先以消息形式掛在節(jié)點(diǎn)上攢到一定量再批量往下層合并。這個(gè)機(jī)制帶來(lái)的直接結(jié)果就是高并發(fā)隨機(jī)寫(xiě)入時(shí)索引維護(hù)成本遠(yuǎn)低于 B 樹(shù)。說(shuō)人話就是同樣是一張大表、多個(gè)二級(jí)索引TokuDB 在寫(xiě)入密集型場(chǎng)景下能把磁盤 I/O 壓得很低。再配合天然的頁(yè)壓縮能力數(shù)據(jù)文件體積通常能比 InnoDB 小好幾倍。另外還有一點(diǎn)必須提TokuDB 做 DDL 時(shí)不需要重建整張表。在 InnoDB 里給一張幾千萬(wàn)行的大表加索引往往要折騰半天甚至更久TokuDB 加索引基本秒級(jí)完成因?yàn)?Fractal Tree 對(duì)局部重構(gòu)的需求很小。這一點(diǎn)在做歸檔表結(jié)構(gòu)變更時(shí)非常爽。1.2 適合你的場(chǎng)景才值得裝我的建議很直接別把它當(dāng)默認(rèn)引擎也別把它神化。這玩意兒是為特定場(chǎng)景生的。場(chǎng)景推薦程度原因日志流水、埋點(diǎn)數(shù)據(jù)、IoT 傳感器數(shù)據(jù)強(qiáng)烈推薦寫(xiě)入量大、索引多、對(duì)壓縮比敏感歷史歸檔、冷數(shù)據(jù)存儲(chǔ)強(qiáng)烈推薦文件小、省磁盤檢索頻率低需要頻繁在線加索引的表推薦DDL 不鎖不重建體驗(yàn)接近秒改金融交易類強(qiáng)一致核心庫(kù)不推薦社區(qū)維護(hù)力度有限不建議賭核心數(shù)據(jù)MySQL 8.0 新項(xiàng)目完全不推薦官方 8.0 已不再內(nèi)置該引擎支持我見(jiàn)過(guò)有人把用戶主表和訂單表也遷到 TokuDB結(jié)果后續(xù)遇到一些復(fù)雜關(guān)聯(lián)查詢和在線 DDL 兼容問(wèn)題時(shí)來(lái)回折騰成本反而更高。所以判斷標(biāo)準(zhǔn)很簡(jiǎn)單如果你的表接近“寫(xiě)入多、讀取少、數(shù)據(jù)量大、不常修改、允許壓縮存儲(chǔ)”那 TokuDB 是真香。核心交易鏈路還是老老實(shí)實(shí) InnoDB 加合理拆分。2. 動(dòng)手前的版本選型與環(huán)境準(zhǔn)備2.1 版本選型是第一道坎TokuDB 不是裝個(gè)插件就能在所有 MySQL 里跑的。官方社區(qū)版默認(rèn)發(fā)布的二進(jìn)制里壓根就沒(méi)有包含 ha_tokudb.so 這類組件你拿官方包去搞INSTALL PLUGIN大概率直接報(bào)錯(cuò)。實(shí)際可用的方案基本集中在 Percona Server 5.6、5.7 的分支里這一階段 TokuDB 集成度和穩(wěn)定性最高。5.7 之后的 8.0 時(shí)代TokuDB 就逐漸被官方邊緣化相關(guān)組件的更新幾乎停滯我不建議你在 8.0 上做任何嘗試。選版本的時(shí)候我建議直接選 5.7 的最新維護(hù)版本理由是它跟現(xiàn)有主流業(yè)務(wù)兼容性最好備份工具、監(jiān)控體系也都成熟。另一個(gè)細(xì)節(jié)是安裝方式如果你用的是 RPM 或者 DEB 包記得看有沒(méi)有單獨(dú)的 tokudb 子包。比如下載 Percona Server 的包列表時(shí)能看到 percona-server-tokudb-57 這樣的組件包單獨(dú)裝上才會(huì)把 TokuDB 相關(guān)的庫(kù)文件和配置模板一起放好。2.2 內(nèi)核參數(shù)和依賴庫(kù)準(zhǔn)備這一步是最容易被忽略的。TokuDB 對(duì)運(yùn)行環(huán)境有兩個(gè)硬性要求交代不清楚后面全是淚。第一jemalloc。TokuDB 的內(nèi)存管理深度依賴 jemalloc官方文檔里的說(shuō)法是它需要 jemalloc 來(lái)避免內(nèi)存碎片化問(wèn)題。我實(shí)測(cè)下來(lái)不配 jemalloc 直接啟動(dòng)實(shí)例輕則一堆警告重則 TokuDB 初始化失敗。安裝方式很簡(jiǎn)單絕大多數(shù) Linux 發(fā)行版都有現(xiàn)成包包名一般是 jemalloc 開(kāi)頭具體叫 libjemalloc1、libjemalloc2 還是 jemalloc看你系統(tǒng)版本直接yum install jemalloc或apt install libjemalloc-dev就行。第二透明大頁(yè)THP必須關(guān)。Linux 的 THP 機(jī)制對(duì) InnoDB 影響不大對(duì) TokuDB 卻很容易引發(fā)嚴(yán)重的性能抖動(dòng)我甚至遇到過(guò)因?yàn)?THP 導(dǎo)致 TokuDB 啟動(dòng)時(shí)直接掛掉的情況。操作也很粗暴echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag這兩個(gè)路徑在某些發(fā)行版上略有差異可以用ls /sys/kernel/mm/看實(shí)際目錄。只執(zhí)行一次是不會(huì)重啟保持的建議寫(xiě)到/etc/rc.local或者通過(guò) systemd 的 tmpfiles 配置做持久化。除此之外我還習(xí)慣把vm.swappiness調(diào)到較小值比如 1減少內(nèi)存交換帶來(lái)的抖動(dòng)。如果底層是機(jī)械硬盤順便把磁盤 IO 調(diào)度器改成 deadlineSSD 則改成 none老內(nèi)核可能是 noop。2.3 內(nèi)存規(guī)劃先算清楚再加參數(shù)TokuDB 不像 InnoDB 使用 buffer pool 那一套它自己維護(hù)了一塊叫 cachetable 的內(nèi)存區(qū)域?qū)iT用來(lái)緩存索引節(jié)點(diǎn)和數(shù)據(jù)節(jié)點(diǎn)。你在 my.cnf 里看到的tokudb_cache_size就是這塊的上限。如果同一臺(tái)機(jī)器上還跑著 InnoDB內(nèi)存分配一定要先加總再配置。舉個(gè)例子物理內(nèi)存 32GInnoDB buffer_pool 給了 16G那 TokuDB cache_size 就不要再給 16G 了我一般會(huì)控制在 6G 到 8G留出操作系統(tǒng)和其他進(jìn)程的空間。之前見(jiàn)過(guò)有人隨手設(shè)成 24G結(jié)果實(shí)例啟動(dòng)后直接被系統(tǒng) OOM kill數(shù)據(jù)沒(méi)丟但業(yè)務(wù)中斷了一陣子非常不值。3. 一步步安裝 TokuDB 引擎3.1 安裝插件前的目錄準(zhǔn)備在 Percona Server 的分支版本里TokuDB 的插件文件一般會(huì)放到 MySQL 的 plugin 目錄下。你可以在 MySQL 里執(zhí)行下面這條 SQL 確認(rèn)插件目錄位置SHOW VARIABLES LIKE plugin_dir;拿到實(shí)際路徑后去目錄下找ha_tokudb.so是否存在。如果用的是打包安裝方式一般會(huì)自動(dòng)放好無(wú)需手動(dòng)拷貝。手動(dòng)編譯或者遷移二進(jìn)制時(shí)這一步容易漏。文件必須跟隨 MySQL 的 plugin 目錄權(quán)限屬主要一致否則啟動(dòng)加載時(shí)會(huì)因?yàn)闄?quán)限問(wèn)題失敗。同時(shí)要確認(rèn)數(shù)據(jù)目錄周邊有沒(méi)有_tokudb目錄規(guī)劃。沒(méi)創(chuàng)建也不要緊初始化階段引擎會(huì)自己建但你要知道 TokuDB 的數(shù)據(jù)文件并不是傳統(tǒng)的ibd文件結(jié)構(gòu)它有一套獨(dú)立的目錄組織方式后面看表文件大小的時(shí)候不能沿用 InnoDB 的習(xí)慣。3.2 配置 my.cnf 并啟動(dòng)安裝依賴完成后我先在 my.cnf 里寫(xiě)好基礎(chǔ)配置再啟動(dòng)實(shí)例避免插件加載時(shí)參數(shù)缺失。一個(gè)相對(duì)穩(wěn)妥的起始配置[mysqld_safe] malloc-lib/usr/lib64/libjemalloc.so.1 [mysqld] tokudb_cache_size8G tokudb_commit_syncON tokudb_directioON tokudb_read_block_size128K tokudb_read_buf_size128K tokudb_write_buf_size4M tokudb_row_formattokudb_zlib注意malloc-lib的路徑不同的發(fā)行版、不同的 jemalloc 版本文件后綴和路徑差別很大。你可以執(zhí)行下面的命令確認(rèn)find /usr -name libjemalloc.so* 2/dev/null把找到的真實(shí)路徑填進(jìn)去。如果路徑寫(xiě)錯(cuò)TokuDB 啟動(dòng)時(shí)同樣會(huì)報(bào) jemalloc 相關(guān)錯(cuò)誤。如果用的是 systemd 方式托管 MySQLmysqld_safe段可能不生效這種情況建議直接在 Environment 變量里追加LD_PRELOAD指向 libjemalloc 的絕對(duì)路徑。啟動(dòng)實(shí)例之后第一步是確認(rèn)引擎插件能不能被識(shí)別SHOW PLUGINS;看到輸出里存在 tokudb 行就說(shuō)明組件已經(jīng)加載進(jìn)來(lái)了。如果列表里沒(méi)有再看錯(cuò)誤日志通常會(huì)有明確提示下一節(jié)我會(huì)介紹最常見(jiàn)的幾個(gè)。3.3 創(chuàng)建第一張 TokuDB 表插件加載成功后我們直接建表驗(yàn)證。CREATE DATABASE test; USE test; CREATE TABLE tokudb_demo ( id BIGINT NOT NULL AUTO_INCREMENT, device_id INT NOT NULL, payload TEXT, created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_device_time (device_id, created_at) ) ENGINETokuDB ROW_FORMATtokudb_zlib;建表成功只是第一步還要養(yǎng)成習(xí)慣去確認(rèn)這張表真的用上了 TokuDBSELECT TABLE_NAME, ENGINE, ROW_FORMAT, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMAtest AND TABLE_NAMEtokub_demo;我這里把表名寫(xiě)成了 tokub_demo實(shí)際你根據(jù)自己剛建的表名查詢。如果 ENGINE 列顯示的是 TokuDB就成功了。此時(shí)去數(shù)據(jù)目錄看一眼_tokudb_開(kāi)頭的目錄會(huì)發(fā)現(xiàn)出現(xiàn)了一組文件這些就是 TokuDB 實(shí)際持有的數(shù)據(jù)節(jié)點(diǎn)和索引節(jié)點(diǎn)。驗(yàn)證通過(guò)后你還可以做一次從 InnoDB 遷移的完整演練CREATE TABLE tbl_moved LIKE tbl_original; ALTER TABLE tbl_moved ENGINETokuDB ROW_FORMATtokudb_lzma; INSERT INTO tbl_moved SELECT * FROM tbl_original;這種先建結(jié)構(gòu)再插入的方式比直接ALTER TABLE ... ENGINETokuDB更容易控制進(jìn)度適合大表遷移時(shí)不希望鎖住原表的場(chǎng)景。4. 安裝成功后的參數(shù)調(diào)優(yōu)與壓縮策略4.1 核心運(yùn)行參數(shù)一次講全安裝跑通只是開(kāi)始參數(shù)調(diào)不好TokuDB 跟普通引擎比也占不到便宜。tokudb_cache_size是最關(guān)鍵的一個(gè)。它決定 cachetable 的上限不宜設(shè)小太小會(huì)導(dǎo)致節(jié)點(diǎn)頻繁換入換出也不宜設(shè)大太大容易拖垮整機(jī)。我的參考策略是單機(jī)只跑 TokuDB 實(shí)例給物理內(nèi)存的 50%~60%和其他引擎混跑則根據(jù)內(nèi)存總量減掉 InnoDB 部分后再分配。tokudb_commit_sync控制提交時(shí)是否同步刷盤。默認(rèn)是 ON數(shù)據(jù)安全優(yōu)先。如果你做的是大量非關(guān)鍵日志寫(xiě)入可以改成 OFF 換取更高吞吐但代價(jià)是崩潰時(shí)可能丟最近一小段已提交事務(wù)這類場(chǎng)景需要業(yè)務(wù)上能容忍否則別動(dòng)。tokudb_directio簡(jiǎn)單理解是讓 TokuDB 繞過(guò)操作系統(tǒng)頁(yè)緩存直接讀寫(xiě)磁盤。開(kāi)頭我建議設(shè)置為 ON如果你的存儲(chǔ)本身是高性能 SSD這樣能少一層內(nèi)存拷貝。它和某些文件系統(tǒng)、虛擬化環(huán)境的兼容性需要實(shí)測(cè)遇到性能波動(dòng)可以試著關(guān)掉對(duì)比。還有一個(gè)容易誤解的參數(shù)tokudb_read_block_size它決定從磁盤讀取節(jié)點(diǎn)時(shí)每次拉取多大的塊。機(jī)械盤可以設(shè)大一點(diǎn)以減少尋道SSD 保持 128K 一般夠了。4.2 壓縮算法怎么選TokuDB 提供好幾種壓縮算法用ROW_FORMAT控制。在建表和修改表時(shí)都可以指定ROW_FORMAT壓縮比寫(xiě)入速度適用場(chǎng)景tokudb_lzma最高較慢冷數(shù)據(jù)歸檔幾乎不更新tokudb_zlib較高中等通用壓縮需求我主力推薦tokudb_quicklz中等較快寫(xiě)入頻率高、需要一定壓縮tokudb_snappy較低最快對(duì)壓縮比無(wú)要求、只想要寫(xiě)入快tokudb_uncompressed無(wú)壓縮最快不壓縮只想要 Fractal Tree我踩過(guò)的坑是剛開(kāi)始圖壓縮比全表選了 lzma結(jié)果跑批量導(dǎo)入時(shí) CPU 直接被打滿導(dǎo)入速度慢到懷疑人生。后來(lái)把實(shí)時(shí)寫(xiě)入的表?yè)Q成 snappy把只讀歸檔表保留為 lzma整體效果好了很多。建議你按數(shù)據(jù)熱度分層選擇而不是全庫(kù)一把梭。如果想在建表后修改可以執(zhí)行ALTER TABLE tokub_demo ROW_FORMATtokudb_lzma;這里有一點(diǎn)要注意TokuDB 的 DDL 雖然不需要重建表但切換壓縮格式本質(zhì)上是重寫(xiě)節(jié)點(diǎn)該操作仍然會(huì)在后臺(tái)做大量 I/O。小表無(wú)所謂大表請(qǐng)安排在業(yè)務(wù)低峰期。4.3 用監(jiān)控指標(biāo)確認(rèn)引擎狀態(tài)經(jīng)常有人裝了 TokuDB 之后發(fā)現(xiàn)數(shù)據(jù)量是降了但說(shuō)不清楚它到底工作正不正常。我在實(shí)踐中主要看幾個(gè)信息第一看SHOW ENGINE TOKUDB STATUS。輸出里能看到 cachetable 的當(dāng)前使用量、未刷盤節(jié)點(diǎn)數(shù)量、文件系統(tǒng)讀寫(xiě)統(tǒng)計(jì)等。如果節(jié)點(diǎn)數(shù)量持續(xù)異常增長(zhǎng)說(shuō)明 cache_size 可能偏小。第二看information_schema.TOKUDB_FRACTAL_TREE_INFO這個(gè)在 Percona Server 5.7 里會(huì)暴露每張 TokuDB 表的 Fractal Tree 節(jié)點(diǎn)信息包括消息緩沖的使用情況。消息緩沖如果長(zhǎng)期高水位說(shuō)明寫(xiě)入壓力大可以考慮調(diào)大寫(xiě)入緩沖區(qū)。第三比較實(shí)際文件大小和邏輯數(shù)據(jù)量。系統(tǒng)表里查到的是邏輯行數(shù)實(shí)際占用空間去看數(shù)據(jù)目錄下的_tokudb_文件大小。兩者差距越大說(shuō)明壓縮效果越好。我一般都是隔一段時(shí)間統(tǒng)計(jì)一次最后匯成一個(gè)表去評(píng)估成本節(jié)省情況。5. 常見(jiàn)安裝報(bào)錯(cuò)與避坑實(shí)錄5.1 加載插件時(shí)最常見(jiàn)的報(bào)錯(cuò)很多朋友跑INSTALL PLUGIN tokudb SONAME ha_tokudb.so;會(huì)直接得到一句ERROR 1123 (HY000): Cant initialize function tokudb或者ERROR 1126 (HY000): Cant open shared library .../ha_tokudb.so看到這類報(bào)錯(cuò)先別慌按順序查三件事。第一插件目錄對(duì)不對(duì)確認(rèn)ha_tokudb.so真實(shí)存在。第二有沒(méi)有裝 jemalloc并用 ldd 檢查它的依賴ldd /usr/local/mysql/lib/plugin/ha_tokudb.so如果輸出里有 not found 字樣說(shuō)明某個(gè)共享庫(kù)缺失。第三把 MySQL 錯(cuò)誤日志拖到最前面看有沒(méi)有關(guān)于內(nèi)存分配器初始化失敗的記錄。我遇到過(guò)的最典型組合是libjemalloc.so 路徑寫(xiě)錯(cuò)插件加載時(shí)報(bào) undefined symbol一改路徑立刻就好了。5.2 內(nèi)核參數(shù)沒(méi)關(guān)導(dǎo)致的坑透明大頁(yè)沒(méi)關(guān)掉的一種典型表現(xiàn)是TokuDB 能啟動(dòng)但長(zhǎng)時(shí)間跑下來(lái)性能越來(lái)越差或者壓測(cè)時(shí)出現(xiàn)詭異的周期性抖動(dòng)。排查方法很直接cat /sys/kernel/mm/transparent_hugepage/enabled如果輸出不是always [never]說(shuō)明還沒(méi)關(guān)徹底。另外有些云主機(jī)重啟后內(nèi)核參數(shù)會(huì)被重置別只寫(xiě)入一次就以為萬(wàn)事大吉必須確認(rèn)持久化生效。另一個(gè)被忽視的參數(shù)是文件系統(tǒng) mount 選項(xiàng)。TokuDB 對(duì)直接 I/O 的兼容性要求較高如果你在某個(gè)虛擬文件系統(tǒng)上開(kāi)tokudb_directioON遇到校驗(yàn)錯(cuò)誤可以考慮先換成 OFF 驗(yàn)證。我實(shí)際遇到過(guò)一次某個(gè)內(nèi)網(wǎng)低版本內(nèi)核加特定文件系統(tǒng)組合directio 開(kāi)啟后直接 O_DIRECT 校驗(yàn)失敗關(guān)閉后恢復(fù)。5.3 問(wèn)題排查速查表現(xiàn)象大概率原因建議動(dòng)作插件加載報(bào) undefined symboljemalloc 未安裝或路徑錯(cuò)誤確認(rèn) LD_PRELOAD 或 malloc-lib 路徑引擎啟動(dòng)后自動(dòng)退出透明大頁(yè)未關(guān)閉立即設(shè)置 never 并復(fù)查持久化寫(xiě)入時(shí) CPU 異常高壓縮算法選擇了 lzma考慮 zlib 或 snappy內(nèi)存被吃滿后 OOMcache_size 設(shè)得過(guò)大重新核算物理內(nèi)存和其他引擎占用數(shù)據(jù)文件沒(méi)有變小row_format 沒(méi)生效確認(rèn)表當(dāng)前 row_format 是 tokudb_ 開(kāi)頭備份腳本不識(shí)別 TokuDB 表用了只支持 InnoDB 的備份工具改用支持 TokuDB 的備份方式5.4 還有幾個(gè)容易忽略的細(xì)節(jié)TokuDB 對(duì)部分高級(jí)特性的支持不如 InnoDB 完整。全文索引這類功能就不用想了空間索引也基本別指望。外鍵支持層面存在兼容性差異我習(xí)慣在遷移前先用開(kāi)發(fā)環(huán)境跑一遍外鍵相關(guān)鏈路不盲目信任文檔里的一兩句話。如果你做了主從復(fù)制TokuDB 表產(chǎn)生的 binlog 事件和 InnoDB 是完全兼容的復(fù)制本身沒(méi)問(wèn)題。但災(zāi)難恢復(fù)測(cè)試要做足TokuDB 的崩潰恢復(fù)在極大數(shù)據(jù)量下比 InnoDB 慢這一點(diǎn)要在容災(zāi)預(yù)案里提前想到別真的宕機(jī)了才發(fā)現(xiàn)恢復(fù)時(shí)間不可控。備份方案也有講究。TokuDB 自己的備份插件提供的是熱備能力安裝方式類似引擎插件。如果你還在用傳統(tǒng)物理拷貝務(wù)必確認(rèn)表空間文件沒(méi)有被占用的進(jìn)程寫(xiě)壞。我現(xiàn)在的習(xí)慣是保留一個(gè)周期性的邏輯導(dǎo)出畢竟邏輯導(dǎo)出雖然慢但可控性最強(qiáng)。我個(gè)人實(shí)際操作中還有一個(gè)體會(huì)千萬(wàn)別在生產(chǎn)環(huán)境里直接改tokudb_row_format或tokudb_cache_size這種重磅參數(shù)。每次調(diào)整前先用一個(gè)小表測(cè)試再在準(zhǔn)生產(chǎn)環(huán)境跑一輪壓測(cè)最后才輪到生產(chǎn)。TokuDB 的生態(tài)沒(méi)有 InnoDB 那么龐大的社區(qū)兜底寧可慢一點(diǎn)也別拿線上穩(wěn)定性去賭參數(shù)效果。