引言:当金融数据库遇见文件存储
TigerBeetle 是一个为金融交易场景设计的分布式数据库,以其高性能、强一致性和容错能力著称。它采用双记账法(Double-Entry Bookkeeping)作为核心数据模型,专为处理金融交易而优化。然而,最近有开发者尝试将其重新用作通用文件存储系统,这一看似 "用锤子敲钉子" 的做法背后,却蕴含着对数据库核心特性的深度理解与创造性应用。
Aivars Kalvāns 在 2025 年 12 月发表的文章《TigerBeetle as a file storage》展示了这一实验性尝试。他提到:"如果我能将任意二进制数据存储在 TigerBeetle 中,保护它们免受存储故障的影响,那么我就能存储任何东西。" 这种思路不仅是对技术边界的探索,更是对现有系统能力的重新评估。
TigerBeetle 的核心特性与文件存储的适配挑战
双记账法的本质
TigerBeetle 的双记账法模型基于两个核心概念:账户(Account)和转账(Transfer)。每个金融交易都涉及从一个账户到另一个账户的资金转移,且必须保持借方和贷方的平衡。这种模型天然提供了:
- 原子性保证:每笔交易要么完全成功,要么完全失败
- 顺序一致性:所有转账按时间戳严格排序
- 审计追踪:完整的交易历史记录
- 数据完整性:通过余额验证确保数据一致性
文件存储的基本需求
相比之下,文件存储系统需要满足:
- 数据持久化:确保文件内容不会丢失
- 元数据管理:文件名、大小、修改时间等
- 顺序访问:支持文件的顺序读写
- 容错能力:应对硬件故障
将金融数据库适配为文件存储,需要在保持前者特性的同时,满足后者的需求。这就像用会计系统来管理图书馆藏书 —— 看似不匹配,但核心的登记、追踪、验证逻辑却有相通之处。
架构适配:从金融交易到文件字节
元数据映射策略
Aivars Kalvāns 的方案将文件系统的概念映射到 TigerBeetle 的数据模型中:
文件名存储:文件名被编码为 16 字节的整数,存储在 Account 记录的id字段中。这带来了 16 字节的文件名长度限制,但对于大多数场景已足够。
def create_a_file(filename, size):
if len(filename) > 16:
raise ValueError("Invalid filename, more than 16 bytes")
account = tb.Account(
id=int.from_bytes(filename.encode()),
user_data_64=size, # 存储文件总大小
user_data_32=len(filename), # 存储文件名长度
ledger=FILE,
code=FILE,
)
errors = client.create_accounts([account])
if errors:
raise ValueError(errors[0])
return account
文件大小记录:文件的总字节数存储在 Account 的user_data_64字段,文件名长度存储在user_data_32字段。这种设计使得credits_posted字段可以自动记录实际写入的字节数,为断点续传提供了基础。
数据分块存储机制
文件的实际内容被分割成小块,存储在 Transfer 记录中。每个 Transfer 最多可以存储 28 字节数据:
user_data_128:存储前 16 字节user_data_64:存储接下来的 8 字节user_data_32:存储最后的 4 字节amount字段:记录该 Transfer 实际使用的字节数(通常为 28,最后一个块可能更少)
transfers.append(
tb.Transfer(
id=tb.id(),
debit_account_id=system_id, # 系统文件"."
credit_account_id=file_id, # 目标文件
amount=len(block), # 实际字节数
user_data_128=int.from_bytes(block[:16]),
user_data_64=int.from_bytes(block[16:24]),
user_data_32=int.from_bytes(block[24:]),
ledger=FILE,
code=FILE,
)
)
双记账法的巧妙应用
系统创建了一个特殊的系统文件 "。" 作为所有数据的借方来源。当向目标文件写入数据时,实际上是从系统文件向目标文件 "转移" 字节。这种设计带来了几个重要优势:
- 审计完整性:系统文件的
debits_posted总和应等于所有文件credits_posted的总和 - 数据验证:可以通过余额检查确保没有数据丢失或损坏
- 事务一致性:每批 Transfer 作为一个事务提交,确保原子性
性能表现与工程考量
实测性能数据
在 Aivars Kalvāns 的测试中,一个 100MB 的视频文件(104,718,755 字节)的存储和检索表现如下:
上传性能:
- 总耗时:2 分 3.697 秒
- 平均速度:约 642 kB/s
- CPU 使用:用户态 1 分 4.408 秒,系统态 1.568 秒
下载性能:
- 总耗时:47.588 秒
- 平均速度:约 2,228 kB/s
- 下载速度是上传速度的 3.5 倍
数据完整性验证:通过 SHA256 哈希验证,往返过程没有丢失任何比特。
性能瓶颈分析
- 小数据块开销:每个 Transfer 最多 28 字节,100MB 文件需要约 370 万个 Transfer 记录
- 事务提交开销:批量提交 Transfer 时的事务管理开销
- 编码解码成本:字节到整数的转换和反向转换
- 网络往返延迟:客户端与 TigerBeetle 集群的通信延迟
优化策略建议
对于实际生产环境,可以考虑以下优化:
批量处理优化:
# 增加批量大小,减少事务提交次数
BATCH_SIZE = 1000 # 每批处理1000个Transfer
数据压缩:在存储前对文件进行压缩,减少 Transfer 数量 智能分块:根据文件类型和访问模式调整块大小 缓存策略:在客户端实现读写缓存,减少网络往返
适用场景与局限性
理想应用场景
- 关键配置文件的持久化存储:需要强一致性和审计追踪的系统配置文件
- 法律合规文档存储:金融、医疗等需要完整审计历史的场景
- 分布式锁服务:利用 TigerBeetle 的事务特性实现分布式锁
- 元数据管理:小文件或文件元数据的集中管理
技术局限性
- 文件名长度限制:16 字节的文件名限制
- 存储效率:每个 Transfer 的 28 字节存储上限导致存储效率较低
- 性能瓶颈:不适合大文件的高吞吐量场景
- 架构复杂度:需要理解双记账法模型才能正确使用
与专用文件存储的对比
| 特性 | TigerBeetle 文件存储 | 传统文件系统 | 对象存储 (S3) |
|---|---|---|---|
| 一致性 | 强一致性 | 文件系统级别 | 最终一致性 |
| 审计能力 | 完整审计追踪 | 有限审计 | 有限审计 |
| 性能 | 中等 (642kB/s 上传) | 高 | 高 |
| 容错性 | 高 (分布式容错) | 依赖硬件 | 高 |
| 适用文件大小 | 小到中等文件 | 任意大小 | 任意大小 |
工程实践价值
架构思维的拓展
这个实验最重要的价值不在于创建一个新的文件存储系统,而在于展示了如何重新思考现有系统的能力边界。TigerBeetle 的双记账法模型虽然为金融交易设计,但其核心特性 —— 原子性、一致性、持久性、隔离性 —— 正是许多存储系统所需的基础。
故障恢复机制
利用 TigerBeetle 的容错特性,文件存储可以获得:
- 网络分区容错:在脑裂情况下保持数据一致性
- 机器故障恢复:自动故障转移和数据复制
- 存储介质故障保护:通过冗余确保数据持久性
监控与告警
基于双记账法的特性,可以构建独特的监控指标:
- 字节平衡检查:系统文件借方总额 vs 所有文件贷方总额
- 传输完整性:每个文件的
credits_postedvs 实际文件大小 - 时序一致性:通过时间戳验证数据顺序
实现细节与最佳实践
文件上传流程
- 文件预处理:计算文件大小、分块、生成元数据
- 账户创建:创建对应的 Account 记录
- 数据分块传输:按 28 字节分块,批量创建 Transfer
- 完整性验证:验证
credits_posted与实际写入字节数 - 事务提交:确保所有 Transfer 原子提交
文件下载流程
- 账户查询:根据文件名查找 Account 记录
- 转账检索:获取该账户的所有 credit Transfer
- 数据重组:按时间戳顺序重组数据块
- 完整性检查:验证下载字节数与文件大小一致
错误处理策略
class TigerBeetleFileStorage:
def upload_file(self, filename, data):
try:
# 创建账户
account = self._create_account(filename, len(data))
# 分块上传
for i in range(0, len(data), 28):
block = data[i:i+28]
self._create_transfer(account.id, block)
# 验证完整性
self._verify_integrity(account.id, len(data))
except tb.CreateAccountsError as e:
# 处理账户创建错误
self._handle_account_error(e)
except tb.CreateTransfersError as e:
# 处理转账创建错误
self._handle_transfer_error(e)
except IntegrityError as e:
# 处理完整性错误
self._handle_integrity_error(e)
未来演进方向
性能优化路径
- 协议优化:设计更高效的数据编码方案
- 并行处理:支持并行上传多个文件块
- 压缩集成:内置数据压缩减少传输量
- 缓存层:添加客户端缓存减少网络往返
功能扩展可能
- 版本控制:利用 Transfer 的时间戳特性实现文件版本
- 访问控制:基于 Account 的权限管理
- 增量备份:只传输变化的文件块
- 跨集群复制:利用 TigerBeetle 的多集群特性
生态系统集成
- FUSE 文件系统:实现标准的文件系统接口
- S3 兼容 API:提供对象存储兼容接口
- 备份工具集成:与现有备份工具集成
- 监控系统对接:与 Prometheus、Grafana 等监控系统集成
结论
将 TigerBeetle 重新用作文件存储的实验,虽然在实际性能上无法与专用文件系统竞争,但在架构思维上具有重要价值。它展示了如何利用现有系统的核心特性解决看似不相关的问题,这种 "横向思考" 的能力在工程实践中至关重要。
对于特定场景 —— 特别是需要强一致性、完整审计追踪、高容错性的小文件存储 —— 这种方案提供了独特的价值。更重要的是,它提醒我们:在追求新技术的同时,不应忽视现有系统的潜力和可扩展性。
正如 Aivars Kalvāns 所言:"如果我能将任意二进制数据存储在 TigerBeetle 中,保护它们免受存储故障的影响,那么我就能存储任何东西。" 这种探索精神,正是技术进步的重要动力。
参考资料
- Aivars Kalvāns. "TigerBeetle as a file storage" (2025-12-07) - https://aivarsk.com/2025/12/07/tigerbeetle-blob-storage/
- TigerBeetle 官方文档 - https://docs.tigerbeetle.com/
- TigerBeetle GitHub 仓库 - https://github.com/tigerbeetle/tigerbeetle
- 完整实现代码 - https://gist.github.com/aivarsk/2b26854c956e36fdfd73349586f2b168