面對 2.5 億+ 記錄、頻繁 Schema 變更導致的 DDL 鎖表數小時、索引維護擠佔緩存等痛點,FriendFeed 摒棄傳統關係模型,在 MySQL 上構建了一套無模式存儲 + 獨立索引表 + 異步清理器架構。本文詳述實體表設計、索引表自動維護、一致性妥協與清理機制、分片策略、性能實測數據,為大規模動態業務提供參考。
id (UUID) + body (壓縮 JSON) + updated,Schema 固定不再變動index_user_id(user_id, entity_id)
CREATE TABLE entities (
added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -- 物理順序寫入優化
id BINARY(16) NOT NULL, -- UUID,業務主鍵
updated TIMESTAMP NOT NULL,
body MEDIUMBLOB, -- zlib 壓縮後的 Python dict/JSON
UNIQUE KEY (id),
KEY (updated)
) ENGINE=InnoDB;
added_id 保證新實體順序追加,利於分區讀取(新數據訪問更熱)以 user_id 索引為例:
CREATE TABLE index_user_id (
user_id BINARY(16) NOT NULL,
entity_id BINARY(16) NOT NULL UNIQUE,
PRIMARY KEY (user_id, entity_id)
) ENGINE=InnoDB;
查詢流程:
index_user_id 取 entity_id 列表entities 表按 id 批量取實體
-- 1. 建索引表
CREATE TABLE index_link (
link VARCHAR(735) NOT NULL,
entity_id BINARY(16) NOT NULL UNIQUE,
PRIMARY KEY (link, entity_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2. 代碼註冊索引
link_index = Index(table="index_link", properties=["link"], shard_on="link")
datastore = DataStore(mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"],
indexes=[user_id_index, link_index])
-- 3. 異步回填歷史數據(不阻塞線上服務)
./rundatastorecleaner.py --index=index_link
DROP TABLE 釋放空間核心原則:
寫入流程:
entities(主表)讀取糾偏:
entity_idbody清理器 持續運行:優先處理最近更新實體,秒級修復索引缺失/殘留。
上線後頁面加載延遲顯著下降並趨於穩定(見下圖):


