# 當資料庫遇到瓶頸，你會如何處理？ ── 下班有約系列文

- 作者：林鼎淵（Dean Lin）
- 網址：https://deanlin.net/blog/202405/database-bottlenecks/
- 發布：2024-05-22（更新：2024-05-25）
- 主題：技術教學
- 系列：工程師下班有約
- 標籤：Database、Database Design、MongoDB、Redis、Database Migration
- 摘要：除非真的撐不住了，否則我覺得應該沒幾間公司會想換資料庫。 今天跟大家分享一個資料庫遇到瓶頸必須遷移的的案例。

除非真的撐不住了，否則我覺得應該沒幾間公司會想換資料庫。

今天跟大家分享一個資料庫遇到瓶頸必須遷移的的案例。

過去朋友的公司用 Redis 來紀錄每件商品每分鐘的瀏覽數、庫存等資訊，以此來呈現即時的商品統計圖表（資料只要保存 1 個月）。

但隨著商品種類日漸增加（種類高達數萬），Redis 這類用記憶體（RAM）儲存資料的系統開始遇到了瓶頸。

過去還可以用課金增加記憶體的方式來解決，但最新的需求是將資料保存的時限拉長到一年（統計圖表時間搜尋範圍）。

而每天的資料量大約有 250MB，30 天還好說，變成 365 天成本就真的太可怕了。

於是團隊就開始討論合適的遷移方案。

## 方案一：Redis 僅保留 1 個月的資料量，過期的轉移到其他資料庫

最初的方案是增加一個排程腳本，每天定時將快過期的資料寫到其他資料庫保存。

之所以讓 Redis 保留一個月的資料量，因為系統默認顯示當前一個月的統計資料，存取次數較高。

但這麼做有許多缺點，比如：

- 如果使用者搜尋的時間點跨月，那後端的程式邏輯就會比較複雜。
- 需要同時維護兩套程式碼與資料庫，可能會造成未來隱憂（ex：版本升級、錯誤釐清）。
- 對團隊成員的技術水平有更高的要求，可能會提升招募的難度。

所以這個方案提出後當下就否決了。

## 方案二：將資料遷移到關連式資料庫

因為大部分的團隊成員都比較熟悉關聯式資料庫（ex: MySQL）。

所以大家想說要不就遷移到關聯式資料庫吧，這樣改版要花的時間成本較低。

但研究了一下發現可行性較低，因為：

- 如果不設計索引（index），搜尋效率會低到可怕。
- 但設計索引（index）後，因為每分鐘要寫入大量資料，所以會影響到寫入效能。
- 考量到未來統計報告可能需要更多資料，關聯式資料庫並不適合動態擴充欄位。

## 方案三：將資料遷移到非關連式資料庫（NoSQL）

幾經考慮後，最終的方案是選擇 MongoDB 這個 NoSQL。

主要原因是 MongoDB 無論在讀取、寫入，還是動態擴充欄位方面都能滿足需求。

在決定使用 MongoDB 後，接下來對資料結構做了一下調整。

在 Redis 中，原本將統計資料儲存在 Sorted Set 中，並使用 Hash 結構來存儲每個 timestamp 下的商品統計資料。結構如下：

```
Sorted Set: timestamps
Member: timestamp (e.g., 1620000000)
Hash: hash:timestamp (e.g., hash:1620000000)
{
    "商品名稱1": "統計資料1",
    "商品名稱2": "統計資料2",
    ...
}

```

然而，這樣的設計有些問題。因為實際頁面通常只顯示最多 20 筆商品的統計資料；但為了達成這個目標，我們需要先抓取區間內的 timestamp，然後再找到對應的 Key（商品名稱）。

在 Redis 這麼做還 Ok，但如果轉換到 MongoDB，效率可能會不理想。

因此，在遷移到 MongoDB 時重新設計了資料結構，將商品作為 collection 的主要文件，timestamp 與統計資料作為內嵌文件。

新的 MongoDB 設計如下：

```
{
    "商品名稱": "商品名稱1",
    "統計資料": [
        {
            "timestamp": "1620000000",
            "data": "統計資料1"
        },
        {
            "timestamp": "16200000100",
            "data": "統計資料2"
        }
    ]
}

```

在這個設計中，我們將商品名稱設為 index，以提高查詢特定商品統計資料的效率。

> 小提醒：經過實驗後，這個資料結構並不是最理想的，詳情參考這篇文章（[MongoDB 與 MySQL 建立 Index 對 Read/Write 的效能影響](https://deanlin.net/blog/202405/mongodb-vs-mysql-index-performance/)）

另外考量到時間搜尋範圍可能很大（ex: 4 個月的趨勢），面對大範圍的資料顯示，會以小時的統計資料呈現。

以上就是朋友跟我分享的資料庫遷移案例，希望對大家有幫助，如果有更好的方案，也歡迎留言分享。

延伸閱讀：[Database](https://deanlin.net/tags/database/)

---

關於作者：林鼎淵（Dean Lin），台灣的生成式 AI 企業內訓講師、外商資安公司軟體專家，也是生成式 AI 創新學院發起人。出版 7 本書（包含全台第一本 ChatGPT 應用專書），兩度獲得 iThome 鐵人賽佳作，累積上百場授課與顧問經驗。（https://deanlin.net/about/）
引用時請附上原文網址：https://deanlin.net/blog/202405/database-bottlenecks/
