先写后读原则
The Write Last, Read First Rule

原始链接: https://tigerbeetle.com/blog/2025-11-06-the-write-last-read-first-rule/

## 虎甲虫:在没有事务的情况下构建正确的金融系统 虎甲虫是一个为正确性设计的金融交易数据库,但从单独正确的组件实现全系统正确性具有挑战性。本文探讨了在*没有*传统事务的情况下维持一致性,重点关注安全性和活性属性,例如“一致性”(账户在Postgres和虎甲虫中都存在)和“可追溯性”(正余额对应于有效账户)。 关键在于分离关注点:Postgres管理主数据(如账户持有人信息),而虎甲虫处理基于整数的交易本身。这允许扩展和独立的安全性/合规性。由于这些系统不共享事务,应用程序必须通过协调操作和重试来强制一致性。 一个关键的架构决策是指定一个**记录系统**(在本例中为虎甲虫——在虎甲虫中存在定义了全系统的存在)和一个**参考系统**(Postgres)。应用了**先写参考系统,后写记录系统**的原则。 为了处理潜在的故障,系统利用具有检查点(通过Resonate的分布式异步等待)的持久化执行。这要求所有操作都是**幂等的**——重复它们不会产生额外的效果。应用程序层编排这些幂等操作,解释子系统响应,并标记不一致之处以供操作员干预。 通过仔细选择记录系统,强制执行顺序,并利用持久化执行,开发人员即使在不依赖传统事务的情况下,也可以构建正确且可靠的金融系统。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 先写后读原则 (tigerbeetle.com) 7点 由 vismit2000 2小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

TigerBeetle is a financial transactions database built for correctness. Yet, building a correct system from correct components remains a challenge:

Composing systems, each correct in isolation, does not necessarily yield a correct system. In this post, we’ll explore how to maintain consistency in the absence of transactions, how to reason about correctness when intermediate states are externalized, and how to recover from partial failures.

TigerBeetle is a financial transactions database that offers two primitives for double-entry bookkeeping: accounts and transfers. A separate data store, such as Postgres, stores master data, such as name and address of the account holder or terms and conditions of the account.

This separation enables transfers to scale independently of general purpose master data (for example dealing with Black Friday events) and solves different security, compliance, or retention requirements of the independent data sets (for example enforce immutability of transfers).

Architecture

Just as a bank may have need for both a filing cabinet and a bank vault, Postgres specializes in strings and describing entities (master data), while TigerBeetle specializes in integers and moving integers between these entities.

A transaction is a sequence of operations ending in a commit, where all operations take effect, or an abort, where no operation takes effect. The completion is instant, intermediate state is not externalized and is not observable. Disruptions (such as process failure or network failure) are mitigated transparently.

Transaction Boundaries

However, the sequential composition of two transactions is not itself a transaction. The completion of the entire sequence is (at best) eventual, intermediate state is externalized and observable. Disruptions are not mitigated transparently.

Transaction Boundaries

Since Postgres and TigerBeetle do not share a transaction boundary, the application must ensure consistency through repeated attempts at completion and coordination, not transactions.

To reason about such coordination, we need to understand the guarantees we expect our system to uphold.

A system is characterized by a set of safety and liveness properties. A safety property states that nothing bad ever happens, while a liveness property states that something good eventually happens.

In this post, we will focus on two safety properties:

  • Consistent
    We consider the system consistent if every account in Postgres has an account in TigerBeetle, and vice versa.
Consistent =
  ∧ ∀ a₁ ∈ PG: ∃ a₂ ∈ TB: id(a₁) = id(a₂)
  ∧ ∀ a₁ ∈ TB: ∃ a₂ ∈ PG: id(a₁) = id(a₂)
  • Traceable
    We consider the system traceable if every account in TigerBeetle with a positive balance corresponds to an account in Postgres.
Traceable = ∀ a₁ ∈ TB: balance(a₁) > 0 => ∃ a₂ ∈ PG: id(a₁) = id(a₂)

In the absence of transactions, the system may be temporarily inconsistent. However, the system must always remain traceable to avoid the possibility of losing—or, more precisely, orphaning—money.

In the absence of transactions, we need to make an explicit architectural decision that transactions used to make implicitly: Which system determines the existence of an account? In other words: Which system is the source of truth?

We must designate a:

  • System of Record. The champion. If the account exists here, the account exists on a system level.

  • System of Reference. The supporter. If the account exists here but not in the system of record, the account does not exist on a system level.

So which system is the system of record and which is the system of reference? That is an architectural decision that depends on your requirements and the properties of the subsystems. In this case, TigerBeetle is the system of record:

  • If the account is present in Postgres, the account is not able to process transfers, so the account in Postgres merely represents a staged record.

  • If the account is present in TigerBeetle, the account is able to process transfers, so the account in TigerBeetle represents a committed record.

In other words, as soon as the account is created in TigerBeetle, the account exists system wide.

Once the system of record is chosen, correctness depends on performing operations in the right order.

Since the system of reference doesn’t determine existence, we can safely write to it first without committing anything. Only when we write to the system of record does the account spring into existence.

Conversely, when reading to check existence, we must consult the system of record, because reading from the system of reference tells us nothing about whether the account actually exists.

This principle—Write Last, Read First—ensures that we maintain application level consistency.

Remarkably, if the system of record provides strict serializability, like TigerBeetle, and if ordering is correctly applied, then the system as a whole preserves strict serializability, leading to a delightful developer experience.

Choosing the correct system of record and the correct order of operations is not just a philosophical exercise. If we designate the wrong system as the source of truth and perform operations in the wrong order, we may quickly violate safety properties.

For example, if we create the account in TigerBeetle but not in Postgres, the system may start processing transfers without containing any information about who this account belongs to. If the system crashes and forensics do not surface the necessary information to establish ownership, we violated the golden rule: traceability.

However, if we create the account in Postgres but subsequently not in TigerBeetle, no harm, no foul. Any transfer attempt is simply rejected by TigerBeetle, money cannot flow to an account that doesn’t exist in the ledger.

Clients interact with the system exclusively via the Application Programming Interface exposed by the application layer, which in turn interacts via the interfaces exposed by the subsystems, Postgres and TigerBeetle.

The Application Programming Interface has two responsibilities, Orchestration and aggregation: The API determines the order of operations and aggregates operation results into application level semantics.

We will implement the API with Resonate’s durable execution framework, Distributed Async Await. Distributed Async Await guarantees eventual completion simplifying reaching consistency even in the absence of transactions.

Resonate guarantees eventual completion via language integrated checkpointing and reliable resumption in case of disruptions: Executions resume where they left off by restarting from the beginning and skipping steps that have already been recorded (see Figure 4.)

Idempotence

However, we must consider a subtle issue inherent to checkpointing: In the event of a disruption, after performing an operation but before recording its completion, the operation will be performed again.

Therefore, every operation must be idempotent, i.e. the repeated application of an operation does not have any effects beyond the initial application.

For each subsystem, Postgres and TigerBeetle, we implement an idempotent function to create an account. In our case, both the Postgres and TigerBeetle account creation functions return whether the account was created, already existed with the same values, or already existed with different values:

https://github.com/resonatehq-examples/example-tigerbeetle-account-creation-ts.


Thanks to Dominik Tornow, Founder and CEO of Resonate, for penning this guest post!

联系我们 contact @ memedata.com