Hotdry.
systems-engineering

TigerBeetle作为文件存储的架构适配与工程实践

分析金融数据库TigerBeetle被重新用作文件存储的架构思路、双记账法适配方案、性能表现与适用场景。

引言:当金融数据库遇见文件存储

TigerBeetle 是一个为金融交易场景设计的分布式数据库,以其高性能、强一致性和容错能力著称。它采用双记账法(Double-Entry Bookkeeping)作为核心数据模型,专为处理金融交易而优化。然而,最近有开发者尝试将其重新用作通用文件存储系统,这一看似 "用锤子敲钉子" 的做法背后,却蕴含着对数据库核心特性的深度理解与创造性应用。

Aivars Kalvāns 在 2025 年 12 月发表的文章《TigerBeetle as a file storage》展示了这一实验性尝试。他提到:"如果我能将任意二进制数据存储在 TigerBeetle 中,保护它们免受存储故障的影响,那么我就能存储任何东西。" 这种思路不仅是对技术边界的探索,更是对现有系统能力的重新评估。

TigerBeetle 的核心特性与文件存储的适配挑战

双记账法的本质

TigerBeetle 的双记账法模型基于两个核心概念:账户(Account)和转账(Transfer)。每个金融交易都涉及从一个账户到另一个账户的资金转移,且必须保持借方和贷方的平衡。这种模型天然提供了:

  1. 原子性保证:每笔交易要么完全成功,要么完全失败
  2. 顺序一致性:所有转账按时间戳严格排序
  3. 审计追踪:完整的交易历史记录
  4. 数据完整性:通过余额验证确保数据一致性

文件存储的基本需求

相比之下,文件存储系统需要满足:

  1. 数据持久化:确保文件内容不会丢失
  2. 元数据管理:文件名、大小、修改时间等
  3. 顺序访问:支持文件的顺序读写
  4. 容错能力:应对硬件故障

将金融数据库适配为文件存储,需要在保持前者特性的同时,满足后者的需求。这就像用会计系统来管理图书馆藏书 —— 看似不匹配,但核心的登记、追踪、验证逻辑却有相通之处。

架构适配:从金融交易到文件字节

元数据映射策略

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,
    )
)

双记账法的巧妙应用

系统创建了一个特殊的系统文件 "。" 作为所有数据的借方来源。当向目标文件写入数据时,实际上是从系统文件向目标文件 "转移" 字节。这种设计带来了几个重要优势:

  1. 审计完整性:系统文件的debits_posted总和应等于所有文件credits_posted的总和
  2. 数据验证:可以通过余额检查确保没有数据丢失或损坏
  3. 事务一致性:每批 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 哈希验证,往返过程没有丢失任何比特。

性能瓶颈分析

  1. 小数据块开销:每个 Transfer 最多 28 字节,100MB 文件需要约 370 万个 Transfer 记录
  2. 事务提交开销:批量提交 Transfer 时的事务管理开销
  3. 编码解码成本:字节到整数的转换和反向转换
  4. 网络往返延迟:客户端与 TigerBeetle 集群的通信延迟

优化策略建议

对于实际生产环境,可以考虑以下优化:

批量处理优化

# 增加批量大小,减少事务提交次数
BATCH_SIZE = 1000  # 每批处理1000个Transfer

数据压缩:在存储前对文件进行压缩,减少 Transfer 数量 智能分块:根据文件类型和访问模式调整块大小 缓存策略:在客户端实现读写缓存,减少网络往返

适用场景与局限性

理想应用场景

  1. 关键配置文件的持久化存储:需要强一致性和审计追踪的系统配置文件
  2. 法律合规文档存储:金融、医疗等需要完整审计历史的场景
  3. 分布式锁服务:利用 TigerBeetle 的事务特性实现分布式锁
  4. 元数据管理:小文件或文件元数据的集中管理

技术局限性

  1. 文件名长度限制:16 字节的文件名限制
  2. 存储效率:每个 Transfer 的 28 字节存储上限导致存储效率较低
  3. 性能瓶颈:不适合大文件的高吞吐量场景
  4. 架构复杂度:需要理解双记账法模型才能正确使用

与专用文件存储的对比

特性 TigerBeetle 文件存储 传统文件系统 对象存储 (S3)
一致性 强一致性 文件系统级别 最终一致性
审计能力 完整审计追踪 有限审计 有限审计
性能 中等 (642kB/s 上传)
容错性 高 (分布式容错) 依赖硬件
适用文件大小 小到中等文件 任意大小 任意大小

工程实践价值

架构思维的拓展

这个实验最重要的价值不在于创建一个新的文件存储系统,而在于展示了如何重新思考现有系统的能力边界。TigerBeetle 的双记账法模型虽然为金融交易设计,但其核心特性 —— 原子性、一致性、持久性、隔离性 —— 正是许多存储系统所需的基础。

故障恢复机制

利用 TigerBeetle 的容错特性,文件存储可以获得:

  • 网络分区容错:在脑裂情况下保持数据一致性
  • 机器故障恢复:自动故障转移和数据复制
  • 存储介质故障保护:通过冗余确保数据持久性

监控与告警

基于双记账法的特性,可以构建独特的监控指标:

  • 字节平衡检查:系统文件借方总额 vs 所有文件贷方总额
  • 传输完整性:每个文件的credits_posted vs 实际文件大小
  • 时序一致性:通过时间戳验证数据顺序

实现细节与最佳实践

文件上传流程

  1. 文件预处理:计算文件大小、分块、生成元数据
  2. 账户创建:创建对应的 Account 记录
  3. 数据分块传输:按 28 字节分块,批量创建 Transfer
  4. 完整性验证:验证credits_posted与实际写入字节数
  5. 事务提交:确保所有 Transfer 原子提交

文件下载流程

  1. 账户查询:根据文件名查找 Account 记录
  2. 转账检索:获取该账户的所有 credit Transfer
  3. 数据重组:按时间戳顺序重组数据块
  4. 完整性检查:验证下载字节数与文件大小一致

错误处理策略

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)

未来演进方向

性能优化路径

  1. 协议优化:设计更高效的数据编码方案
  2. 并行处理:支持并行上传多个文件块
  3. 压缩集成:内置数据压缩减少传输量
  4. 缓存层:添加客户端缓存减少网络往返

功能扩展可能

  1. 版本控制:利用 Transfer 的时间戳特性实现文件版本
  2. 访问控制:基于 Account 的权限管理
  3. 增量备份:只传输变化的文件块
  4. 跨集群复制:利用 TigerBeetle 的多集群特性

生态系统集成

  1. FUSE 文件系统:实现标准的文件系统接口
  2. S3 兼容 API:提供对象存储兼容接口
  3. 备份工具集成:与现有备份工具集成
  4. 监控系统对接:与 Prometheus、Grafana 等监控系统集成

结论

将 TigerBeetle 重新用作文件存储的实验,虽然在实际性能上无法与专用文件系统竞争,但在架构思维上具有重要价值。它展示了如何利用现有系统的核心特性解决看似不相关的问题,这种 "横向思考" 的能力在工程实践中至关重要。

对于特定场景 —— 特别是需要强一致性、完整审计追踪、高容错性的小文件存储 —— 这种方案提供了独特的价值。更重要的是,它提醒我们:在追求新技术的同时,不应忽视现有系统的潜力和可扩展性。

正如 Aivars Kalvāns 所言:"如果我能将任意二进制数据存储在 TigerBeetle 中,保护它们免受存储故障的影响,那么我就能存储任何东西。" 这种探索精神,正是技术进步的重要动力。

参考资料

  1. Aivars Kalvāns. "TigerBeetle as a file storage" (2025-12-07) - https://aivarsk.com/2025/12/07/tigerbeetle-blob-storage/
  2. TigerBeetle 官方文档 - https://docs.tigerbeetle.com/
  3. TigerBeetle GitHub 仓库 - https://github.com/tigerbeetle/tigerbeetle
  4. 完整实现代码 - https://gist.github.com/aivarsk/2b26854c956e36fdfd73349586f2b168
查看归档