From b5b539744ae308b2ac17f54159413a7cfbc1f5fc Mon Sep 17 00:00:00 2001 From: TigerBeetle Bot Date: Mon, 9 Sep 2024 18:56:21 +0000 Subject: [PATCH] Autogenerated commit from tigerbeetle/tigerbeetle@632199ed84402f3ee08dfffcb16dafe5f7599296 --- 404.html | 4 +- about/architecture/index.html | 4 +- about/index.html | 4 +- about/internals/cloud/index.html | 4 +- about/internals/data_file/index.html | 4 +- about/internals/index.html | 4 +- about/internals/lsm/index.html | 26 ++++---- about/internals/releases/index.html | 4 +- about/internals/sync/index.html | 59 +++++++++++-------- about/internals/testing/index.html | 4 +- about/internals/upgrades/index.html | 4 +- about/internals/vsr/index.html | 22 ++++--- about/oltp/index.html | 4 +- about/performance/index.html | 4 +- about/production-ready/index.html | 4 +- about/safety/index.html | 4 +- about/vopr/index.html | 4 +- about/zig/index.html | 4 +- ...99faa.7e453912.js => 13a99faa.46b4d6cd.js} | 2 +- assets/js/2cf7febb.1308ecc4.js | 1 - assets/js/2cf7febb.dd3e76e4.js | 1 + assets/js/8388e350.63e0b00a.js | 1 + assets/js/8388e350.a1ec4c84.js | 1 - assets/js/8cb2464c.6002416e.js | 1 - assets/js/8cb2464c.60d68d0d.js | 1 + assets/js/9b0eb4fb.ae095492.js | 1 + assets/js/9b0eb4fb.bc411fef.js | 1 - assets/js/ac624c80.6b6801d8.js | 1 - assets/js/ac624c80.87bfbbd1.js | 1 + ...6321c.c88352dd.js => b2d6321c.c1915e3d.js} | 2 +- assets/js/f310e26a.28734c2e.js | 1 + assets/js/f310e26a.d8e2bec8.js | 1 - assets/js/f4744cb4.6027eb4c.js | 1 + assets/js/f4744cb4.b3ee5ada.js | 1 - ...n.430bce87.js => runtime~main.54525745.js} | 2 +- clients/dotnet/index.html | 4 +- clients/go/index.html | 6 +- clients/java/index.html | 4 +- clients/node/index.html | 4 +- coding/data-modeling/index.html | 4 +- coding/financial-accounting/index.html | 4 +- coding/index.html | 4 +- coding/recipes/balance-bounds/index.html | 4 +- .../balance-conditional-transfers/index.html | 4 +- coding/recipes/close-account/index.html | 4 +- .../recipes/correcting-transfers/index.html | 4 +- coding/recipes/currency-exchange/index.html | 4 +- .../multi-debit-credit-transfers/index.html | 12 ++-- coding/recipes/rate-limiting/index.html | 4 +- .../index.html | 4 +- coding/system-architecture/index.html | 4 +- coding/time/index.html | 4 +- coding/two-phase-transfers/index.html | 4 +- index.html | 4 +- operating/deploy/index.html | 4 +- operating/docker/index.html | 4 +- operating/hardware/index.html | 4 +- operating/linux/index.html | 4 +- operating/managed-service/index.html | 4 +- operating/upgrading/index.html | 4 +- quick-start/index.html | 4 +- reference/account-balance/index.html | 4 +- reference/account-filter/index.html | 4 +- reference/account/index.html | 4 +- reference/query-filter/index.html | 24 ++++---- reference/requests/create_accounts/index.html | 10 ++-- .../requests/create_transfers/index.html | 8 +-- .../requests/get_account_balances/index.html | 4 +- .../requests/get_account_transfers/index.html | 4 +- reference/requests/index.html | 4 +- reference/requests/lookup_accounts/index.html | 4 +- .../requests/lookup_transfers/index.html | 4 +- reference/requests/query_accounts/index.html | 4 +- reference/requests/query_transfers/index.html | 4 +- reference/sessions/index.html | 4 +- reference/transfer/index.html | 19 ++++-- search/index.html | 4 +- 77 files changed, 214 insertions(+), 196 deletions(-) rename assets/js/{13a99faa.7e453912.js => 13a99faa.46b4d6cd.js} (62%) delete mode 100644 assets/js/2cf7febb.1308ecc4.js create mode 100644 assets/js/2cf7febb.dd3e76e4.js create mode 100644 assets/js/8388e350.63e0b00a.js delete mode 100644 assets/js/8388e350.a1ec4c84.js delete mode 100644 assets/js/8cb2464c.6002416e.js create mode 100644 assets/js/8cb2464c.60d68d0d.js create mode 100644 assets/js/9b0eb4fb.ae095492.js delete mode 100644 assets/js/9b0eb4fb.bc411fef.js delete mode 100644 assets/js/ac624c80.6b6801d8.js create mode 100644 assets/js/ac624c80.87bfbbd1.js rename assets/js/{b2d6321c.c88352dd.js => b2d6321c.c1915e3d.js} (51%) create mode 100644 assets/js/f310e26a.28734c2e.js delete mode 100644 assets/js/f310e26a.d8e2bec8.js create mode 100644 assets/js/f4744cb4.6027eb4c.js delete mode 100644 assets/js/f4744cb4.b3ee5ada.js rename assets/js/{runtime~main.430bce87.js => runtime~main.54525745.js} (91%) diff --git a/404.html b/404.html index 5f9089ec..012cc14d 100644 --- a/404.html +++ b/404.html @@ -6,13 +6,13 @@ Page Not Found | TigerBeetle Docs - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/about/architecture/index.html b/about/architecture/index.html index 1690bf97..0d8ab4e7 100644 --- a/about/architecture/index.html +++ b/about/architecture/index.html @@ -6,7 +6,7 @@ Architecture | TigerBeetle Docs - + @@ -48,7 +48,7 @@ impact through false sharing.

We order the header struct as we do to keep any C protocol implementations padding-free.

We use AEGIS-128L as our checksum, designed to fully exploit the parallelism and built-in AES support of recent Intel and ARM CPUs.

The reason we use two checksums instead of only a single checksum across header and data is that we need a reliable way to know the size of the data to expect before we start receiving the data.

Here is an example showing the risk of a single checksum for the recipient:

  1. We receive a header with a single checksum protecting both header and data.
  2. We extract the SIZE of the data from the header (4 GiB in this case).
  3. We cannot tell if this SIZE value is corrupt until we receive the data.
  4. We wait for 4 GiB of data to arrive before calculating/comparing checksums.
  5. Except the SIZE was corrupted in transit from 16 MiB to 4 GiB (2-bit flips).
  6. We never detect the corruption, the connection times out, and we miss our SLA.
- + \ No newline at end of file diff --git a/about/index.html b/about/index.html index 708e94c6..11477ff2 100644 --- a/about/index.html +++ b/about/index.html @@ -6,7 +6,7 @@ About TigerBeetle | TigerBeetle Docs - + @@ -82,7 +82,7 @@ Changfoot and Joran Dirk Greef, a performance analysis of Mojaloop's central ledger that sparked the idea for "an accounting database" as Adrian Hope-Bailie put it. And the rest, as they say, is history!

- + \ No newline at end of file diff --git a/about/internals/cloud/index.html b/about/internals/cloud/index.html index 0015fe6b..94518d2b 100644 --- a/about/internals/cloud/index.html +++ b/about/internals/cloud/index.html @@ -6,7 +6,7 @@ Cloud | TigerBeetle Docs - + @@ -36,7 +36,7 @@ providers) and share these as we go.

Credit to @tdaly61 from the Mojaloop community for prompting us with some great questions about Tigerbeetle in the cloud.

You can read more about how we use io_uring in A Programmer-Friendly I/O Abstraction Over io_uring and kqueue.

- + \ No newline at end of file diff --git a/about/internals/data_file/index.html b/about/internals/data_file/index.html index 111bfb43..6ea088e8 100644 --- a/about/internals/data_file/index.html +++ b/about/internals/data_file/index.html @@ -6,7 +6,7 @@ Data File | TigerBeetle Docs - + @@ -71,7 +71,7 @@ reconstruct the manifest in memory. Manifest describes levels and tables of a single LSM tree. A table is a pointer to its index block. The index block is a sorted array of pointers to data blocks. Data blocks are sorted arrays of values.

- + \ No newline at end of file diff --git a/about/internals/index.html b/about/internals/index.html index cacdafc0..940ce039 100644 --- a/about/internals/index.html +++ b/about/internals/index.html @@ -6,7 +6,7 @@ Internals | TigerBeetle Docs - + @@ -15,7 +15,7 @@ intended audience is folks who are contributing to TigerBeetle source.

It will not be particularly useful on its own for understanding TigerBeetle at a high level.

Furthermore, it isn't particularly organized for reading. It is primarily a reference.

Contents

- + \ No newline at end of file diff --git a/about/internals/lsm/index.html b/about/internals/lsm/index.html index 2822bdd3..0a870f09 100644 --- a/about/internals/lsm/index.html +++ b/about/internals/lsm/index.html @@ -6,16 +6,16 @@ LSM | TigerBeetle Docs - +
-
Skip to main content

LSM

Documentation for (roughly) code in the src/lsm directory.

Glossary

  • bar: lsm_compaction_ops beats; unit of incremental compaction.
  • beat: op % lsm_compaction_ops; Single step of an incremental compaction.
  • groove: A collection of LSM trees, storing objects and their indices.
  • immutable table: In-memory table; one per tree. Used to periodically flush the mutable table to -disk.
  • level: A collection of on-disk tables, numbering between 0 and config.lsm_levels - 1 (usually config.lsm_levels = 7).
  • forest: A collection of grooves.
  • manifest: Index of table and level metadata; one per tree.
  • mutable table: In-memory table; one per tree. All tree updates (e.g. Tree.put) directly modify just this table.
  • snapshot: Sequence number which selects the queryable partition of on-disk tables.

Tree

Tables

A tree is a hierarchy of in-memory and on-disk tables. There are three categories of tables:

  • The mutable table is an in-memory table.
    • Each tree has a single mutable table.
    • All tree updates, inserts, and removes are applied to the mutable table.
    • The mutable table's size is allocated to accommodate a full bar of updates.
  • The immutable table is an in-memory table.
    • Each tree has a single immutable table.
    • The mutable table's contents are periodically moved to the immutable table, +

      LSM

      Documentation for (roughly) code in the src/lsm directory.

      Glossary

      • bar: lsm_compaction_ops beats; unit of incremental compaction.
      • beat: op % lsm_compaction_ops; Single step of an incremental compaction.
      • groove: A collection of LSM trees, storing objects and their indices.
      • immutable table: In-memory table; one per tree. Used to periodically flush the mutable table to +disk.
      • level: A collection of on-disk tables, numbering between 0 and config.lsm_levels - 1 (usually config.lsm_levels = 7).
      • forest: A collection of grooves.
      • manifest: Index of table and level metadata; one per tree.
      • mutable table: In-memory table; one per tree. All tree updates (e.g. Tree.put) directly modify just this table.
      • snapshot: Sequence number which selects the queryable partition of on-disk tables.

      Tree

      Tables

      A tree is a hierarchy of in-memory and on-disk tables. There are three categories of tables:

      • The mutable table is an in-memory table.
        • Each tree has a single mutable table.
        • All tree updates, inserts, and removes are applied to the mutable table.
        • The mutable table's size is allocated to accommodate a full bar of updates.
      • The immutable table is an in-memory table.
        • Each tree has a single immutable table.
        • The mutable table's contents are periodically moved to the immutable table, where they are stored while being flushed to level 0.
      • Level 0 … level config.lsm_levels - 1 each contain an exponentially increasing number of immutable on-disk tables.
        • Each tree has as many as config.lsm_growth_factor ^ (level + 1) tables per level. -(config.lsm_growth_factor is typically 8).
        • Within a given level and snapshot, the tables' key ranges are disjoint.

      Compaction

      Tree compaction runs to the sound of music!

      Compacting LSM trees involves merging and moving tables into the next levels as needed. +(config.lsm_growth_factor is typically 8).

    • Within a given level and snapshot, the tables' key ranges are disjoint.

Compaction

Tree compaction runs to the sound of music!

Compacting LSM trees involves merging and moving tables into the next levels as needed. To avoid write amplification stalls and bound latency, compaction is done incrementally.

A full compaction phase is denoted as a bar, using terms from music notation. Each bar consists of lsm_compaction_ops beats or "compaction ticks" of work. A compaction tick executes asynchronously immediately after every commit, with @@ -29,27 +29,27 @@ level_b which intersect the level_a table's key range.

Invariants:

  • At the end of every beat, there is space in mutable table for the next beat.
  • The manifest log is compacted during every half-bar.
  • The compactions' output tables are not visible until the compaction has finished.
  1. First half-bar, first beat ("first beat"):

    • Assert no compactions are currently running.
    • Allow the per-level table limits to overflow if needed (for example, if we may compact a table from level A to level B, where level B is already full).
    • Start compactions from even levels that have reached their table limit.
    • Acquire reservations from the Free Set for all blocks (upper-bound) that will be written during this half-bar.
  2. First half-bar, last beat:

    • Finish ticking any incomplete even-level compactions.
    • Assert on callback completion that all compactions are complete.
    • Release reservations from the Free Set.
  3. Second half-bar, first beat ("middle beat"):

    • Assert no compactions are currently running.
    • Start compactions from odd levels that have reached their table limit.
    • Compact the immutable table if it contains any sorted values (it might be empty).
    • Acquire reservations from the Free Set for all blocks (upper-bound) that will be written -during this half-bar.
  4. Second half-bar, last beat:

    • Finish ticking any incomplete odd-level and immutable table compactions.
    • Assert on callback completion that all compactions are complete.
    • Assert on callback completion that no level's table count overflows.
    • Flush, clear, and sort mutable table values into immutable table for next bar.
    • Remove input tables that are invisible to all current and persisted snapshots.
    • Release reservations from the Free Set.

Compaction Selection Policy

Compaction selects the table from level A which overlaps the fewest visible tables of level B.

For example, in the following table (with lsm_growth_factor=2), each table is depicted as the range of keys it includes. The tables with uppercase letters would be chosen for compaction next.

Level 0   A─────────────H       l───────────────────────────z
Level 1 a───────e L─M o───────s u───────y
Level 2 b───d e─────h i───k l───n o─p q───s u─v w─────z
(Keys) a b c d e f g h i j k l m n o p q r s t u v w x y z

Links:

Compaction Move Table

When the selected input table from level A does not overlap any +during this half-bar.

  • Second half-bar, last beat:

    • Finish ticking any incomplete odd-level and immutable table compactions.
    • Assert on callback completion that all compactions are complete.
    • Assert on callback completion that no level's table count overflows.
    • Flush, clear, and sort mutable table values into immutable table for next bar.
    • Remove input tables that are invisible to all current and persisted snapshots.
    • Release reservations from the Free Set.
  • Compaction Selection Policy

    Compaction selects the table from level A which overlaps the fewest visible tables of level B.

    For example, in the following table (with lsm_growth_factor=2), each table is depicted as the range of keys it includes. The tables with uppercase letters would be chosen for compaction next.

    Level 0   A─────────────H       l───────────────────────────z
    Level 1 a───────e L─M o───────s u───────y
    Level 2 b───d e─────h i───k l───n o─p q───s u─v w─────z
    (Keys) a b c d e f g h i j k l m n o p q r s t u v w x y z

    Links:

    Compaction Move Table

    When the selected input table from level A does not overlap any input tables in level B, the input table can be "moved" to level B. That is, instead of sort-merging A and B, just update the input table's metadata in the manifest.

    This is referred to as the move table optimization.

    Where a tree performs inserts mostly in sort order, with a minimum of updates, this move table -optimization should enable the tree's performance to approach that of an append-only log.

    Compaction Table Overlap

    Applying this selection policy while compacting a table +optimization should enable the tree's performance to approach that of an append-only log.

    Compaction Table Overlap

    Applying this selection policy while compacting a table from level A to level B, what is the maximum number of level-B tables that may overlap with the selected level-A table (i.e. the "worst case")?

    Perhaps surprisingly, this is lsm_growth_factor:

    • Tables within a level are disjoint.
    • Level B has at most lsm_growth_factor times as many tables as level A.
    • To trigger compaction, level A's visible-table count exceeds table_count_max_for_level(lsm_growth_factor, level_a).
    • The selection policy chooses the table from level A which overlaps the fewest visible tables in level B.
    • If any table in level A overlaps more than lsm_growth_factor tables in level B, that implies the existence of a table in level A with less than lsm_growth_factor overlap. -The latter table would be selected over the former.

    Snapshots

    Each table has a minimum and maximum integer snapshot (snapshot_min and snapshot_max).

    Each query targets a particular snapshot. A table T is visible to a snapshot S when

    T.snapshot_min ≤ S ≤ T.snapshot_max

    and is invisible to the snapshot otherwise.

    Compaction does not modify tables in place — it copies data. Snapshots control and distinguish +The latter table would be selected over the former.

    Snapshots

    Each table has a minimum and maximum integer snapshot (snapshot_min and snapshot_max).

    Each query targets a particular snapshot. A table T is visible to a snapshot S when

    T.snapshot_min ≤ S ≤ T.snapshot_max

    and is invisible to the snapshot otherwise.

    Compaction does not modify tables in place — it copies data. Snapshots control and distinguish which copies are useful, and which can be deleted. Snapshots can also be persisted, enabling -queries against past states of the tree (unimplemented; future work).

    Snapshots and Compaction

    Consider the half-bar compaction beginning at op=X (12), with lsm_compaction_ops=M (8). +queries against past states of the tree (unimplemented; future work).

    Snapshots and Compaction

    Consider the half-bar compaction beginning at op=X (12), with lsm_compaction_ops=M (8). Each half-bar contains N=M/2 (4) beats. The next half-bar begins at Y=X+N (16).

    During the half-bar compaction X:

    • snapshot_max of each input table is truncated to Y-1 (15).
    • snapshot_min of each output table is initialized to Y (16).
    • snapshot_max of each output table is initialized to .
    0   4   8  12  16  20  24  (op, snapshot)
    ┼───┬───┼───┬───┼───┬───┼
    ####
    ····────────X────────···· (input tables, before compaction)
    ····──────────── (input tables, after compaction)
    Y────···· (output tables, after compaction)

    Beginning from the next op after the compaction (Y; 16):

    • The output tables of the above compaction X are visible.
    • The input tables of the above compaction X are invisible.
    • Therefore, it will lookup from the output tables, but ignore the input tables.
    • Callers must not query from the output tables of X before the compaction half-bar has finished -(i.e. before the end of beat Y-1 (15)), since those tables are incomplete.

    At this point the input tables can be removed if they are invisible to all persistent snapshots.

    Snapshot Queries

    Each query targets a particular snapshot, either:

    Persistent Snapshots

    TODO(Persistent Snapshots): Expand this section.

    Snapshot Values

    • The on-disk tables visible to a snapshot B do not contain the updates from the commit with op B.
    • Rather, snapshot B is first visible to a prefetch from the commit with op B.

    Consider the following diagram (lsm_compaction_ops=8):

    0   4   8  12  16  20  24  28  (op, snapshot)
    ┼───┬───┼───┬───┼───┬───┼───┬
    ,,,,,,,,........
    ↑A ↑B ↑C

    Compaction is driven by the commits of ops B→C (16…23). While these ops are being committed:

    • Updates from ops 0→A (0…7) are on-disk.
    • Updates from ops A→B (8…15) are in the immutable table.
      • These updates were moved to the immutable table from the immutable table at the end of op B-1 -(15).
      • These updates will exist in the immutable table until it is reset at the end of op C-1 (23).
    • Updates from ops B→C (16…23) are added to the mutable table (by the respective commit).
    • tree.lookup_snapshot_max is B when committing op B.
    • tree.lookup_snapshot_max is x when committing op x (for x ∈ {16,17,…,23}).

    At the end of the last beat of the compaction bar (23):

    • Updates from ops 0→B (0…15) are on disk.
    • Updates from ops B→C (16…23) are moved from the mutable table to the immutable table.
    • tree.lookup_snapshot_max is x when committing op x (for x ∈ {24,25,…}).

    Manifest

    The manifest is a tree's index of table locations and metadata.

    Each manifest has two components:

    Manifest Log

    The manifest log is an on-disk log of all updates to the trees' table indexes.

    The manifest log tracks:

    • tables created as compaction output
    • tables updated as compaction input (modifying their snapshot_max)
    • tables moved between levels by compaction
    • tables deleted after compaction

    Updates are accumulated in-memory before being flushed:

    • incrementally during compaction, or
    • in their entirety during checkpoint.

    The manifest log is periodically compacted to remove older entries that have been superseded by +(i.e. before the end of beat Y-1 (15)), since those tables are incomplete.

    At this point the input tables can be removed if they are invisible to all persistent snapshots.

    Snapshot Queries

    Each query targets a particular snapshot, either:

    Persistent Snapshots

    TODO(Persistent Snapshots): Expand this section.

    Snapshot Values

    • The on-disk tables visible to a snapshot B do not contain the updates from the commit with op B.
    • Rather, snapshot B is first visible to a prefetch from the commit with op B.

    Consider the following diagram (lsm_compaction_ops=8):

    0   4   8  12  16  20  24  28  (op, snapshot)
    ┼───┬───┼───┬───┼───┬───┼───┬
    ,,,,,,,,........
    ↑A ↑B ↑C

    Compaction is driven by the commits of ops B→C (16…23). While these ops are being committed:

    • Updates from ops 0→A (0…7) are on-disk.
    • Updates from ops A→B (8…15) are in the immutable table.
      • These updates were moved to the immutable table from the immutable table at the end of op B-1 +(15).
      • These updates will exist in the immutable table until it is reset at the end of op C-1 (23).
    • Updates from ops B→C (16…23) are added to the mutable table (by the respective commit).
    • tree.lookup_snapshot_max is B when committing op B.
    • tree.lookup_snapshot_max is x when committing op x (for x ∈ {16,17,…,23}).

    At the end of the last beat of the compaction bar (23):

    • Updates from ops 0→B (0…15) are on disk.
    • Updates from ops B→C (16…23) are moved from the mutable table to the immutable table.
    • tree.lookup_snapshot_max is x when committing op x (for x ∈ {24,25,…}).

    Manifest

    The manifest is a tree's index of table locations and metadata.

    Each manifest has two components:

    Manifest Log

    The manifest log is an on-disk log of all updates to the trees' table indexes.

    The manifest log tracks:

    • tables created as compaction output
    • tables updated as compaction input (modifying their snapshot_max)
    • tables moved between levels by compaction
    • tables deleted after compaction

    Updates are accumulated in-memory before being flushed:

    • incrementally during compaction, or
    • in their entirety during checkpoint.

    The manifest log is periodically compacted to remove older entries that have been superseded by newer entries. For example, if a table is created and later deleted, manifest log compaction will eventually remove any reference to the table from the log blocks.

    Each manifest block has a reference to the (chronologically) previous manifest block. The superblock stores the head and tail address/checksum of this linked list. -The reference on the header of the head manifest block "dangles" – the block it references has already been compacted.

    Manifest Level

    A ManifestLevel is an in-memory collection of the table metadata for a single level of a tree.

    For a given level and snapshot, there may be gaps in the key ranges of the visible tables, -but the key ranges are disjoint.

    Manifest levels are queried for tables at a target snapshot and within a key range.

    Example

    Given the ManifestLevel tables (with values chosen for visualization, not realism):

           label   A   B   C   D   E   F   G   H   I   J   K   L   M
    key_min 0 4 12 16 4 8 12 26 4 25 4 16 24
    key_max 3 11 15 19 7 11 15 27 7 27 11 19 27
    snapshot_min 1 1 1 1 3 3 3 3 5 5 7 7 7
    snapshot_max 9 3 3 7 5 7 9 5 7 7 9 9 9

    A level's tables can be visualized in 2D as a partitioned rectangle:

      0         1         2
    0 4 8 2 6 0 4 8
    9┌───┬───────┬───┬───┬───┬───┐
    │ │ K │ │ L │###│ M │
    7│ ├───┬───┤ ├───┤###└┬──┤
    │ │ I │ │ G │ │####│ J│
    5│ A ├───┤ F │ │ │####└┬─┤
    │ │ E │ │ │ D │#####│H│
    3│ ├───┴───┼───┤ │#####└─┤
    │ │ B │ C │ │#######│
    1└───┴───────┴───┴───┴───────┘

    Example iterations:

    visibility  snapshots   direction  key_min  key_max  tables
    visible 2 ascending 0 28 A, B, C, D
    visible 4 ascending 0 28 A, E, F, G, D, H
    visible 6 descending 12 28 J, D, G
    visible 8 ascending 0 28 A, K, G, L, M
    invisible 2, 4, 6 ascending 0 28 K, L, M

    Legend:

    • # represents a gap — no tables cover these keys during the snapshot.
    • The horizontal axis represents the key range.
    • The vertical axis represents the snapshot range.
    • Each rectangle is a table within the manifest level.
    • The sides of each rectangle depict:
      • left: table.key_min (the diagram is inclusive, and the table.key_min is inclusive)
      • right: table.key_max (the diagram is EXCLUSIVE, but the table.key_max is INCLUSIVE)
      • bottom: table.snapshot_min (inclusive)
      • top: table.snapshot_max (inclusive)
    • (Not depicted: tables may have table.key_min == table.key_max.)
    • (Not depicted: the newest set of tables would have table.snapshot_max == maxInt(u64).)
    - +The reference on the header of the head manifest block "dangles" – the block it references has already been compacted.

    Manifest Level

    A ManifestLevel is an in-memory collection of the table metadata for a single level of a tree.

    For a given level and snapshot, there may be gaps in the key ranges of the visible tables, +but the key ranges are disjoint.

    Manifest levels are queried for tables at a target snapshot and within a key range.

    Example

    Given the ManifestLevel tables (with values chosen for visualization, not realism):

           label   A   B   C   D   E   F   G   H   I   J   K   L   M
    key_min 0 4 12 16 4 8 12 26 4 25 4 16 24
    key_max 3 11 15 19 7 11 15 27 7 27 11 19 27
    snapshot_min 1 1 1 1 3 3 3 3 5 5 7 7 7
    snapshot_max 9 3 3 7 5 7 9 5 7 7 9 9 9

    A level's tables can be visualized in 2D as a partitioned rectangle:

      0         1         2
    0 4 8 2 6 0 4 8
    9┌───┬───────┬───┬───┬───┬───┐
    │ │ K │ │ L │###│ M │
    7│ ├───┬───┤ ├───┤###└┬──┤
    │ │ I │ │ G │ │####│ J│
    5│ A ├───┤ F │ │ │####└┬─┤
    │ │ E │ │ │ D │#####│H│
    3│ ├───┴───┼───┤ │#####└─┤
    │ │ B │ C │ │#######│
    1└───┴───────┴───┴───┴───────┘

    Example iterations:

    visibility  snapshots   direction  key_min  key_max  tables
    visible 2 ascending 0 28 A, B, C, D
    visible 4 ascending 0 28 A, E, F, G, D, H
    visible 6 descending 12 28 J, D, G
    visible 8 ascending 0 28 A, K, G, L, M
    invisible 2, 4, 6 ascending 0 28 K, L, M

    Legend:

    + \ No newline at end of file diff --git a/about/internals/releases/index.html b/about/internals/releases/index.html index 1c3079d2..97a0301a 100644 --- a/about/internals/releases/index.html +++ b/about/internals/releases/index.html @@ -6,7 +6,7 @@ Releases | TigerBeetle Docs - + @@ -39,7 +39,7 @@ commits)
  • $ ./zig/zig build scripts -- changelog

    This will update local repository to match remote, create a branch for changelog PR, and add a scaffold of the new changelog to CHANGELOG.md. Importantly, the scaffold will contain a new version number with patch version incremented:

    ## TigerBeetle 0.16.3   <- Double check this version.

    Released 2024-08-29

    - [#2256](https://github.com/tigerbeetle/tigerbeetle/pull/2256)
    Build: Check zig version
    - [#2248](https://github.com/tigerbeetle/tigerbeetle/pull/2248)
    vopr: heal *both* wal header sectors before replica startup

    ### Safety And Performance

    -

    ### Features

    -

    ### Internals

    -

    ### TigerTracks 🎧

    - []()

    If the current release is being skipped, replace the header with ## TigerBeetle (unreleased).

  • Fill in the changelog:

  • Commit the changelog and submit a pull request for review

  • After the PR is merged, push to the release branch:

    $ git fetch origin && git push origin origin/main:release
  • Ask someone else to approve the GitHub workflow.

  • Ping release manager for the next week in Slack.

  • - + \ No newline at end of file diff --git a/about/internals/sync/index.html b/about/internals/sync/index.html index df69f88b..86e38864 100644 --- a/about/internals/sync/index.html +++ b/about/internals/sync/index.html @@ -6,43 +6,50 @@ State Sync | TigerBeetle Docs - +
    -
    Skip to main content

    State Sync

    State sync synchronizes the state of a lagging replica with the healthy cluster.

    State sync is used when when a lagging replica's log no longer intersects with the cluster's current +

    State Sync

    State sync synchronizes the state of a lagging replica with the healthy cluster.

    State sync is used when a lagging replica's log no longer intersects with the cluster's current log — WAL repair cannot catch the replica up.

    (VRR refers to state sync as "state transfer", but we already have -transfers elsewhere.)

    In the context of state sync, "state" refers to:

    1. the superblock vsr_state.checkpoint
    2. the grid (manifest, free set, and client sessions blocks)
    3. the grid (LSM table data; acquired blocks only)
    4. client replies

    State sync consists of four protocols:

    The target of superblock-sync is the latest checkpoint of the healthy cluster. When we catch up to -the latest checkpoint (or very close to it), then we can transition back to a healthy state.

    Glossary

    Replica roles:

    • syncing replica: A replica performing superblock-sync. (Any step within 1-10 of the -sync algorithm)
    • healthy replica: A replica not performing superblock-sync — part of the active cluster.
    • divergent replica: A replica with a checkpoint that is (and can never be) canonical.

    Checkpoints:

    Algorithm

    1. Sync is needed.
    2. Trigger sync.
    3. Wait for non-grid commit operation to finish.
    4. Wait for grid IO to finish. (See Grid.cancel().)
    5. Wait for a usable sync target to arrive. (Usually we already have one.)
    6. Begin sync-superblock protocol.
    7. Request superblock checkpoint state.
    8. Update the superblock headers with:
      • Bump vsr_state.checkpoint.header to the sync target header.
      • Bump vsr_state.checkpoint.parent_checkpoint_id to the checkpoint id that is previous to our -sync target (i.e. it isn't our previous checkpoint).
      • Bump replica.commit_min. (If replica.commit_min exceeds replica.op, transition to -status=recovering_head).
      • Set vsr_state.sync_op_min to the minimum op which has not been repaired.
      • Set vsr_state.sync_op_max to the maximum op which has not been repaired.
    9. Sync-superblock protocol is done.
    10. Repair replies, +transfers elsewhere.)

      In the context of state sync, "state" refers to:

      1. the superblock vsr_state.checkpoint
      2. the grid (manifest, free set, and client sessions blocks)
      3. the grid (LSM table data; acquired blocks only)
      4. client replies

      State sync consists of four protocols:

      The target of superblock-sync is the latest checkpoint of the healthy cluster. When we catch up to +the latest checkpoint (or very close to it), then we can transition back to a healthy state.

      State sync is lazy — logically, sync is completed when the superblock is synced. The data +pointed to by the new superblock can be transferred on-demand.

      The state (superblock) and the WAL are updated atomically — start_view +message includes both.

      Glossary

      Replica roles:

      • syncing replica: A replica performing superblock-sync. (Any step within 1-5 of the +sync algorithm)
      • healthy replica: A replica not performing superblock-sync — part of the active cluster.

      Checkpoints:

      • checkpoint id/checkpoint identifier: Uniquely identifies a +particular checkpoint reproducibly across replicas. It is a hash over the entire state.
      • Durable checkpoint: A checkpoint whose state is present on at least replication quorum different +replicas.

      Algorithm

      1. Sync is needed.
      2. Trigger sync in response to start_view.
      3. Interrupt the in-progress commit process: +2.1. Wait for write operations to finish. +2.2. Cancel potentially stalled read operations. (See Grid.cancel().) +2.3. Wait for cancellation to finish.
      4. Install the new checkpoint and matching headers into the superblock:
        • Bump vsr_state.checkpoint.header to the sync target header.
        • Bump vsr_state.checkpoint.parent_checkpoint_id to the checkpoint id that is previous to our +sync target (i.e. it isn't our previous checkpoint).
        • Bump replica.commit_min.
        • Set vsr_state.sync_op_min to the minimum op which has not been repaired.
        • Set vsr_state.sync_op_max to the maximum op which has not been repaired.
      5. Repair replies, free set, client sessions, and manifest blocks, and table blocks that were created within the sync_op_{min,max} -range.
      6. Update the superblock with:
        • Set vsr_state.sync_op_min = 0
        • Set vsr_state.sync_op_max = 0

      If a newer sync target is discovered during steps 5-6 or 9, go to step 4.

      If the replica starts up with vsr_state.sync_op_max ≠ 0, go to step 9.

      0: Scenarios

      Scenarios requiring state sync:

      1. A replica was down/partitioned/slow for a while and the rest of the cluster moved on. The lagging +range.
      2. Update the superblock with:
        • Set vsr_state.sync_op_min = 0
        • Set vsr_state.sync_op_max = 0

      If the replica starts up with vsr_state.sync_op_max ≠ 0, go to step 4.

      0: Scenarios

      Scenarios requiring state sync:

      1. A replica was down/partitioned/slow for a while and the rest of the cluster moved on. The lagging replica is too far behind to catch up via WAL repair.
      2. A replica was just formatted and is being added to the cluster (i.e. via reconfiguration). The new replica is too far behind to catch -up via WAL repair.

      Causes of number 3:

      • A storage determinism bug.
      • An upgraded replica (e.g. a canary) running a different version of the code from the remainder of -the cluster, which unexpectedly changes its history. (The change either has a bug or should have -been gated behind a feature flag.)

      1: Triggers

      State sync is initially triggered by any of the following:

      • The replica receives a SV which indicates that it has lagged so far behind the cluster that its -log cannot possibly intersect.
      • repair_sync_timeout fires, and:
        • a WAL or grid repair is in progress and,
        • the replica's checkpoint is lagging behind the cluster's (far enough that the repair may never -complete).

      6: Request Superblock Checkpoint State

      The syncing replica sends command=request_sync_checkpoint messages (with the sync target -identifier attached to each) until it receives a command=sync_checkpoint with a matching -checkpoint identifier.

      Concepts

      Syncing Replica

      Syncing replicas may:

      Syncing replicas must not:

      • ack
      • commit prepares
      • be a primary

      Syncing Replicas write prepares to their WAL.

      When the replica completes superblock-sync, an up-to-date WAL and journal allow it to quickly catch -up (i.e. commit) to the current cluster state.

      Syncing Replicas don't ack prepares.

      If syncing replicas did ack prepares:

      Consider a cluster of 3 replicas:

      • the primary,
      • a normal backup, and
      • a syncing backup.
      1. Primary prepares many ops...
      2. Syncing backup prepares and acknowledges all of those messages.
      3. Normal backup is partitioned — its not seeing any of these prepares.
      4. Primary is receiving prepare_oks from the syncing backup, so it is committing.
      5. Primary eventually checkpoints.
      6. (This cycle repeats — primary keeps preparing/committing, syncing backup keeps preparing, and -normal backup is still partitioned.)

      But now primary is so far ahead that the normal backup needs to sync! Having 2/3 replicas -syncing means that a single grid-block corruption on the primary could make the cluster permanently -unavailable.

      Checkpoint Identifier

      A checkpoint id is a hash of the superblock CheckpointState.

      A checkpoint identifier is attached to the following message types:

      • command=commit: Current checkpoint identifier of sender.
      • command=ping: Current checkpoint identifier of sender.
      • command=prepare: The attached checkpoint id is the checkpoint id during which the corresponding +up via WAL repair.

    Deciding between between WAL repair and state sync:

    • If a replica lags by more than one checkpoint behind the primary, it must use state sync.
    • If a replica is on the same checkpoint as the primary, it can only repair WAL.
    • If a replica is just one checkpoint behind, either WAL repair or state sync might be necessary:
      • State sync is incorrect if there is only a single other replica on the next checkpoint --- the +replica that is ahead could have its state corrupted.
      • WAL repair is incorrect if all reachable peer replicas have already wrapped their logs and +evicted some prepares from the preceding checkpoint.
      • Summarizing, if the next checkpoint is durable (replicated on a quorum of replicas), the +lagging replica must eventually state sync.

    1: Triggers

    State sync is triggered when a replica receives a start_view message with a more advanced +checkpoint.

    If a replica isn't making progress committing because a grid block or a prepare can't be repaired +for some time, the replica proactively sends request_start_view to initiate the sync (see +repair_sync_timeout).

    Concepts

    Syncing Replica

    Syncing replicas participate in replication normally. They can append prepares, commit, and are +eligible to become primaries. In particular, a syncing replica can advance its own checkpoint as a +part of the normal commit process.

    The only restriction is that syncing replicas don't contribute to their checkpoint's replication +quorum. That is, for the cluster as a whole to advance the checkpoint, there must be at least a +replication quorum of healthy replicas.

    The mechanism for discovering sufficiently replicated (durable) checkpoints uses prepare_ok +messages. Sending a prepare_ok signals that the replica has a recent checkpoint fully synced. As a +consequence, observing a commit_max sufficiently ahead of a checkpoint signifies the durability of +the checkpoint.

    For this reason, syncing replicas withhold prepare_ok until commit_max confirms that their +checkpoint is fully replicated on a quorum of different replicas. See op_prepare_max, +op_prepare_ok_max and op_repair_min for details.

    Checkpoint Identifier

    A checkpoint id is a hash of the superblock CheckpointState.

    A checkpoint identifier is attached to the following message types:

    • command=commit: Current checkpoint identifier of sender.
    • command=ping: Current checkpoint identifier of sender.
    • command=prepare: The attached checkpoint id is the checkpoint id during which the corresponding prepare was originally prepared.
    • command=prepare_ok: The attached checkpoint id is the checkpoint id during which the -corresponding prepare was originally prepared.
    • command=request_sync_checkpoint: Requested checkpoint identifier.
    • command=sync_checkpoint: Current checkpoint identifier of sender.

    Sync Target

    A sync target is the checkpoint identifier of the checkpoint that the -superblock-sync is syncing towards.

    Not all checkpoint identifiers are valid sync targets.

    Every sync target must:

    • have an op greater than the syncing replica's current checkpoint op.
    • either:
      • be committed atop – i.e. the syncing replica can sync to healthy_replica.checkpoint_op when -trigger_for_checkpoint(healthy_replica.checkpoint_op) < healthy_replica.commit_min – ensuring -that the checkpoint has been reached by a quorum of replicas, or
      • be more than 1 checkpoint ahead of our current checkpoint.

    Storage Determinism

    When everything works, storage is deterministic. If non-determinism is detected (via checkpoint id +corresponding prepare was originally prepared.

    Storage Determinism

    When everything works, storage is deterministic. If non-determinism is detected (via checkpoint id mismatches) the replica which detects the mismatch will panic. This scenario should prompt operator -investigation and manual intervention.

    - +investigation and manual intervention.

    + \ No newline at end of file diff --git a/about/internals/testing/index.html b/about/internals/testing/index.html index 38579939..93a504f9 100644 --- a/about/internals/testing/index.html +++ b/about/internals/testing/index.html @@ -6,13 +6,13 @@ Testing | TigerBeetle Docs - +
    Skip to main content

    Testing

    Documentation for (roughly) code in the src/testing directory.

    VOPR Output

    Columns

    1. Replica index.
    2. Event:
      • $: crash
      • ^: recover
      • : commit
      • [: checkpoint start
      • ]: checkpoint done
      • >: sync done
    3. Role (according to the replica itself):
      • /: primary
      • \: backup
      • |: standby
      • ~: syncing
      • #: (crashed)
    4. Status:
      • The column (e.g. . vs .) corresponds to the replica index. (This can help identify events' replicas at a quick glance.)
      • The symbol indicates the replica.status.
      • .: normal
      • v: view_change
      • r: recovering
      • h: recovering_head
      • s: sync
    5. View: e.g. 74V indicates replica.view=74.
    6. Checkpoint and Commit: e.g. 83/_90/_98C indicates that:
      • the highest checkpointed op at the replica is 83 (replica.op_checkpoint()=83),
      • on top of that checkpoint, the replica applied ops up to and including 90 (replica.commit_min=90),
      • replica knows that ops at least up to 98 are committed in the cluster (replica.commit_max=98).
    7. Journal op: e.g. 87:150Jo indicates that the minimum op in the journal is 87 and the maximum is 150.
    8. Journal faulty/dirty: 0/1Jd indicates that the journal has 0 faulty headers and 1 dirty headers.
    9. WAL prepare ops: e.g. 85:149Wo indicates that the op of the oldest prepare in the WAL is 85 and the op of the newest prepare in the WAL is 149.
    10. Syncing ops: e.g. <0:123> indicates that vsr_state.sync_op_min=0 and vsr_state.sync_op_max=123.
    11. Release version: e.g. v1:2 indicates that the replica is running release version 1, and that its maximum available release is 2.
    12. Grid blocks acquired: e.g. 167Ga indicates that the grid has 167 blocks currently in use.
    13. Grid blocks queued grid.read_remote_queue: e.g. 0G! indicates that there are 0 reads awaiting remote fulfillment.
    14. Grid blocks queued grid_blocks_missing: e.g. 0G? indicates that there are 0 blocks awaiting remote repair.
    15. Pipeline prepares (primary-only): e.g. 1/4Pp indicates that the primary's pipeline has 2 prepares queued, out of a capacity of 4.
    16. Pipeline requests (primary-only): e.g. 0/3Pq indicates that the primary's pipeline has 0 requests queued, out of a capacity of 3.

    Example

    (The first line labels the columns, but is not part of the actual VOPR output).

     1 2 3 4-------- 5---  6----------  7-------  8-----  9------- 10-----   11-- 12-----  13-   14-   15---  16---

    3 [ / . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G? 0/4Pp 0/3Rq
    4 ^ \ . 2V 23/_23/_46C 19:_50Jo 0/_0J! 19:_50Wo <__0:__0> v1:2 nullGa 0G! 0G?
    2 \ . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    2 [ \ . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    6 | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    6 [ | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    3 ] / . 3V 95/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 167Ga 0G! 0G? 0/4Pp 0/3Rq
    2 ] \ . 3V 95/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 167Ga 0G! 0G?
    1 \ . 3V 71/_99/_99C 68:_99Jo 0/_1J! 67:_98Wo <__0:__0> v1:2 183Ga 0G! 0G?
    1 [ \ . 3V 71/_99/_99C 68:_99Jo 0/_1J! 67:_98Wo <__0:__0> v1:2 183Ga 0G! 0G?
    5 | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    5 [ | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    - + \ No newline at end of file diff --git a/about/internals/upgrades/index.html b/about/internals/upgrades/index.html index 184410c8..12637cde 100644 --- a/about/internals/upgrades/index.html +++ b/about/internals/upgrades/index.html @@ -6,7 +6,7 @@ Upgrades | TigerBeetle Docs - + @@ -40,7 +40,7 @@ run.


    1. Currently the total number of replicas, less one.
    2. MachO binaries are constructed as fat binaries, using unused, esoteric CPU identifiers to signal the header and body, for both x86_64 and arm64.
    3. The short names are for compatibility with Windows: PE supports up to 8 characters for section names without getting more complicated.
    - + \ No newline at end of file diff --git a/about/internals/vsr/index.html b/about/internals/vsr/index.html index 822c9d54..0c56bb3d 100644 --- a/about/internals/vsr/index.html +++ b/about/internals/vsr/index.html @@ -6,25 +6,23 @@ VSR | TigerBeetle Docs - +
    -
    Skip to main content

    VSR

    Documentation for (roughly) code in the src/vsr directory.

    Glossary

    Consensus:

    • checkpoint: Ensure that all updates from the past wrap of the WAL are durable in the grid, then advance the replica's recovery point by updating the superblock. After a checkpoint, the checkpointed WAL entries are safe to be overwritten by the next wrap. (Sidenote: in consensus literature this is sometimes called snapshotting. But we use that term to mean something else.)
    • header: Identifier for many kinds of messages, including each entry in the VSR log. Passed around instead of the entry when the full entry is not needed (such as view change).
    • journal: The in-memory data structure that manages the WAL.
    • nack: Short for negative acknowledgement. Used to determine (during a view change) which entries can be truncated from the log. See Protocol Aware Recovery.
    • op: Short for op-number. An op is assigned to each request that is submitted by the user before being stored in the log. An op is a monotonically increasing integer identifying each message to be handled by consensus. When messages with the same op in different views conflict, view change picks one version to commit. Each user batch (which may contain many batch entries) corresponds to one op. Each op is identified (once inside the VSR log) by a header.
    • superblock: All local state for the replica that cannot be replicated remotely. Loss is protected against by storing config.superblock_copies copies of the superblock.
    • view: A replica is primary for one view. Views are monotonically increasing integers that are incremented each time a new primary is selected.

    Storage:

    • zone: The TigerBeetle data file is made up of zones. The superblock is one zone.
    • grid: The zone on disk where LSM trees and metadata for them reside.
    • WAL: Write-ahead log. It is implemented as two on-disk ring buffers. Entries are only overwritten after they have been checkpointed.
    • state sync: The process of syncing checkpointed data (LSM root information, the grid, and the superblock freeset). When a replica lags behind the cluster far enough that their WALs no longer intersect, the lagging replica must state sync to catch up.

    Protocols

    Commands

    vsr.Header.CommandSourceTargetProtocols
    pingreplicareplicaPing (Replica-Replica)
    pongreplicareplicaPing (Replica-Replica)
    ping_clientclientreplicaPing (Replica-Client)
    pong_clientreplicaclientPing (Replica-Client)
    requestclientprimaryNormal
    preparereplicabackupNormal, Repair WAL
    prepare_okreplicaprimaryNormal, Repair WAL
    replyprimaryclientNormal, Repair Client Replies, Sync Client Replies
    commitprimarybackupNormal
    start_view_changereplicaall replicasStart-View-Change
    do_view_changereplicaall replicasView-Change
    start_viewprimarybackupRequest/Start View
    request_start_viewbackupprimaryRequest/Start View
    request_headersreplicareplicaRepair Journal
    request_preparereplicareplicaRepair WAL
    request_replyreplicareplicaRepair Client Replies, Sync Client Replies
    headersreplicareplicaRepair Journal
    evictionprimaryclientClient
    request_blocksreplicareplicaSync Forest, Repair Grid
    blockreplicareplicaSync Forest, Repair Grid
    request_sync_checkpointreplicareplicaSync Superblock
    sync_checkpointreplicareplicaSync Superblock

    Recovery

    Unlike VRR, TigerBeetle does not implement Recovery Protocol (see §4.3). +

    VSR

    Documentation for (roughly) code in the src/vsr directory.

    Glossary

    Consensus:

    • checkpoint: Ensure that all updates from the past wrap of the WAL are durable in the grid, then advance the replica's recovery point by updating the superblock. After a checkpoint, the checkpointed WAL entries are safe to be overwritten by the next wrap. (Sidenote: in consensus literature this is sometimes called snapshotting. But we use that term to mean something else.)
    • header: Identifier for many kinds of messages, including each entry in the VSR log. Passed around instead of the entry when the full entry is not needed (such as view change).
    • journal: The in-memory data structure that manages the WAL.
    • nack: Short for negative acknowledgement. Used to determine (during a view change) which entries can be truncated from the log. See Protocol Aware Recovery.
    • op: Short for op-number. An op is assigned to each request that is submitted by the user before being stored in the log. An op is a monotonically increasing integer identifying each message to be handled by consensus. When messages with the same op in different views conflict, view change picks one version to commit. Each user batch (which may contain many batch entries) corresponds to one op. Each op is identified (once inside the VSR log) by a header.
    • superblock: All local state for the replica that cannot be replicated remotely. Loss is protected against by storing config.superblock_copies copies of the superblock.
    • view: A replica is primary for one view. Views are monotonically increasing integers that are incremented each time a new primary is selected.

    Storage:

    • zone: The TigerBeetle data file is made up of zones. The superblock is one zone.
    • grid: The zone on disk where LSM trees and metadata for them reside.
    • WAL: Write-ahead log. It is implemented as two on-disk ring buffers. Entries are only overwritten after they have been checkpointed.
    • state sync: The process of syncing checkpointed data (LSM root information, the grid, and the superblock freeset). When a replica lags behind the cluster far enough that their WALs no longer intersect, the lagging replica must state sync to catch up.

    Protocols

    Commands

    vsr.Header.CommandSourceTargetProtocols
    pingreplicareplicaPing (Replica-Replica)
    pongreplicareplicaPing (Replica-Replica)
    ping_clientclientreplicaPing (Replica-Client)
    pong_clientreplicaclientPing (Replica-Client)
    requestclientprimaryNormal
    preparereplicabackupNormal, Repair WAL
    prepare_okreplicaprimaryNormal, Repair WAL
    replyprimaryclientNormal, Repair Client Replies, Sync Client Replies
    commitprimarybackupNormal
    start_view_changereplicaall replicasStart-View-Change
    do_view_changereplicaall replicasView-Change
    start_viewprimarybackupRequest/Start View, State Sync
    request_start_viewbackupprimaryRequest/Start View
    request_headersreplicareplicaRepair Journal
    request_preparereplicareplicaRepair WAL
    request_replyreplicareplicaRepair Client Replies, Sync Client Replies
    headersreplicareplicaRepair Journal
    evictionprimaryclientClient
    request_blocksreplicareplicaSync Forest, Repair Grid
    blockreplicareplicaSync Forest, Repair Grid

    Recovery

    Unlike VRR, TigerBeetle does not implement Recovery Protocol (see §4.3). Instead, replicas persist their VSR state to the superblock. -This ensures that a recovering replica never backtracks to an older view (from the point of view of the cluster).

    Protocol: Ping (Replica-Replica)

    Replicas send command=ping/command=pong messages to one another to synchronize clocks.

    Protocol: Ping (Replica-Client)

    Clients send command=ping_client (and receive command=pong_client) messages to (from) replicas to learn the cluster's current view.

    Protocol: Normal

    Normal protocol prepares and commits requests (from clients) and sends replies (to clients).

    1. The client sends a command=request message to the primary. (If the client's view is outdated, the receiver will forward the message on to the actual primary).
    2. The primary converts the command=request to a command=prepare (assigning it an op and timestamp).
    3. Each replica (in a chain beginning with the primary) performs the following steps concurrently:
      • Write the prepare to the WAL.
      • Forward the prepare to the next replica in the chain.
    4. Each replica sends a command=prepare_ok message to the primary once it has written the prepare to the WAL.
    5. When a primary collects a replication quorum of prepare_oks and it has committed all preceding prepares, it commits the prepare.
    6. The primary replies to the client.
    7. The backups are informed that the prepare was committed by either:
      • a subsequent prepare, or
      • a periodic command=commit heartbeat message.

    See also:

    Protocol: Start-View-Change

    Start-View-Change (SVC) protocol initiates view-changes with minimal disruption.

    Unlike the Start-View-Change described in VRR §4.2, this protocol runs in both status=normal and status=view_change (not just status=view_change).

    1. Depending on the replica's status:
      • status=normal & primary: When the replica has not recently received a prepare_ok (and it has a prepare in flight), pause broadcasting command=commit.
      • status=normal & backup: When the replica has not recently received a command=commit, broadcast command=start_view_change to all replicas (including self).
      • status=view_change: If the replica has not completed a view-change recently, send a command=start_view_change to all replicas (including self).
    2. (Periodically retry sending the SVC).
    3. If the backup receives a command=commit or changes views (respectively), stop the command=start_view_change retries.
    4. If the replica collects a view-change quorum of SVC messages, transition to status=view_change for the next view. (That is, increment the replica's view and start sending a DVC).

    This protocol approach enables liveness under asymmetric network partitions. For example, a replica which can send to the cluster but not receive may send SVCs, but if the remainder of the cluster is healthy, they will never achieve a quorum, so the view is stable. When the partition heals, the formerly-isolated replica may rejoin the original view (if it was isolated in status=normal) or a new view (if it was isolated in status=view_change).

    See also:

    Protocol: View-Change

    A replica sends command=do_view_change to all replicas, with the view it is attempting to start.

    • The primary of the view collects a view-change quorum of DVCs.
    • The backup of the view uses to do_view_change to updates its current view (transitioning to status=view_change).

    DVCs include headers from prepares which are:

    • present: A valid header, corresponding to a valid prepare in the replica's WAL.
    • missing: A valid header, corresponding to a prepare that the replica has not prepared/acked.
    • corrupt: A valid header, corresponding to a corrupt prepare in the replica's WAL.
    • blank: A placeholder (fake) header, corresponding to a header that the replica has never seen.
    • fault: A placeholder (fake) header, corresponding to a header that the replica may have prepared/acked.

    If the new primary collects a nack quorum of blank headers for a particular possibly-uncommitted op, it truncates the log.

    These cases are farther distinguished during WAL repair.

    When the primary collects its DVC quorum:

    1. If any DVC in the quorum is ahead of the primary by more than one checkpoint, +This ensures that a recovering replica never backtracks to an older view (from the point of view of the cluster).

      Protocol: Ping (Replica-Replica)

      Replicas send command=ping/command=pong messages to one another to synchronize clocks.

      Protocol: Ping (Replica-Client)

      Clients send command=ping_client (and receive command=pong_client) messages to (from) replicas to learn the cluster's current view.

      Protocol: Normal

      Normal protocol prepares and commits requests (from clients) and sends replies (to clients).

      1. The client sends a command=request message to the primary. (If the client's view is outdated, the receiver will forward the message on to the actual primary).
      2. The primary converts the command=request to a command=prepare (assigning it an op and timestamp).
      3. Each replica (in a chain beginning with the primary) performs the following steps concurrently:
        • Write the prepare to the WAL.
        • Forward the prepare to the next replica in the chain.
      4. Each replica sends a command=prepare_ok message to the primary once it has written the prepare to the WAL.
      5. When a primary collects a replication quorum of prepare_oks and it has committed all preceding prepares, it commits the prepare.
      6. The primary replies to the client.
      7. The backups are informed that the prepare was committed by either:
        • a subsequent prepare, or
        • a periodic command=commit heartbeat message.

      See also:

      Protocol: Start-View-Change

      Start-View-Change (SVC) protocol initiates view-changes with minimal disruption.

      Unlike the Start-View-Change described in VRR §4.2, this protocol runs in both status=normal and status=view_change (not just status=view_change).

      1. Depending on the replica's status:
        • status=normal & primary: When the replica has not recently received a prepare_ok (and it has a prepare in flight), pause broadcasting command=commit.
        • status=normal & backup: When the replica has not recently received a command=commit, broadcast command=start_view_change to all replicas (including self).
        • status=view_change: If the replica has not completed a view-change recently, send a command=start_view_change to all replicas (including self).
      2. (Periodically retry sending the SVC).
      3. If the backup receives a command=commit or changes views (respectively), stop the command=start_view_change retries.
      4. If the replica collects a view-change quorum of SVC messages, transition to status=view_change for the next view. (That is, increment the replica's view and start sending a DVC).

      This protocol approach enables liveness under asymmetric network partitions. For example, a replica which can send to the cluster but not receive may send SVCs, but if the remainder of the cluster is healthy, they will never achieve a quorum, so the view is stable. When the partition heals, the formerly-isolated replica may rejoin the original view (if it was isolated in status=normal) or a new view (if it was isolated in status=view_change).

      See also:

      Protocol: View-Change

      A replica sends command=do_view_change to all replicas, with the view it is attempting to start.

      • The primary of the view collects a view-change quorum of DVCs.
      • The backup of the view uses to do_view_change to update its current view (transitioning to status=view_change).

      DVCs include headers from prepares which are:

      • present: A valid header, corresponding to a valid prepare in the replica's WAL.
      • missing: A valid header, corresponding to a prepare that the replica has not prepared/acked.
      • corrupt: A valid header, corresponding to a corrupt prepare in the replica's WAL.
      • blank: A placeholder (fake) header, corresponding to a header that the replica has never seen.
      • fault: A placeholder (fake) header, corresponding to a header that the replica may have prepared/acked.

      If the new primary collects a nack quorum of blank headers for a particular possibly-uncommitted op, it truncates the log.

      These cases are farther distinguished during WAL repair.

      When the primary collects its DVC quorum:

      1. If any DVC in the quorum is ahead of the primary by more than one checkpoint, the new primary "forfeits" (that is, it immediately triggers another view change).
      2. If any DVC in the quorum is ahead of the primary by more than one checkpoint, and any messages in the next checkpoint are possibly committed, -the new primary forfeits.
      3. The primary installs the headers to its suffix.
      4. Then the primary repairs its headers. (Protocol: Repair Journal).
      5. Then the primary repairs its prepares. (Protocol: Repair WAL) (and potentially truncates uncommitted ops).
      6. Then primary commits all prepares which are not known to be uncommitted.
      7. Then the primary transitions to status=normal and broadcasts a command=start_view.

      Protocol: Request/Start View

      request_start_view

      A backup sends a command=request_start_view to the primary of a view when any of the following occur:

      • the backup learns about a newer view via a command=commit message, or
      • the backup learns about a newer view via a command=prepare message, or
      • the backup discovers commit_max exceeds min(op_head, op_checkpoint_next_trigger) (during repair), or
      • a replica recovers to status=recovering_head

      start_view

      When a status=normal primary receives command=request_start_view, it replies with a command=start_view. -command=start_view includes the view's current suffix — the headers of the latest messages in the view.

      Upon receiving a start_view for the new view, the backup installs the suffix, transitions to status=normal, and begins repair.

      A start_view contains the following headers (which may overlap):

      • The suffix: pipeline_prepare_queue_max headers from the head op down.
      • The "hooks": the header of any previous checkpoint triggers within our repairable range. -This helps a lagging replica catch up. (There are at most 2).

      Protocol: Repair Journal

      request_headers and headers repair gaps or breaks in a replica's journal headers. -Repaired headers are a prerequisite for repairing prepares.

      Because the headers are repaired backwards (from the head) by hash-chaining, it is safe for both backups and transitioning primaries.

      Gaps/breaks in a replica's journal headers may occur:

      • On a backup, receiving nonconsecutive ops, leaving a gap in its headers.
      • On a backup, which has not finished repair.
      • On a new primary during a view-change, which has not finished repair.

      Protocol: Repair WAL

      The replica's journal tracks which prepares the WAL requires — i.e. headers for which either:

      • no prepare was ever received, or
      • the prepare was received and written, but was since discovered to be corrupt

      During repair, missing/damaged prepares are requested & repaired chronologically, which:

      • improves the chances that older entries will be available, i.e. not yet overwritten
      • enables better pipelining of repair and commit.

      In response to a request_prepare:

      • Reply the command=prepare with the requested prepare, if available and valid.
      • Otherwise do not reply. (e.g. the corresponding slot in the WAL is corrupt)

      Per PAR's CTRL Protocol, we do not nack corrupt entries, since they might be the prepare being requested.

      Protocol: Repair Client Replies

      The replica stores the latest reply to each active client.

      During repair, corrupt client replies are requested & repaired.

      In response to a request_reply:

      • Respond with the command=reply (the requested reply), if available and valid.
      • Otherwise do not reply.

      Protocol: Client

      1. Client sends command=request operation=register to registers with the cluster by starting a new request-reply hashchain. (See also: Protocol: Normal).
      2. Client receives command=reply operation=register from the cluster. (If the cluster is at the maximum number of clients, it evicts the oldest).
      3. Repeat:
        1. Send command=request to cluster.
        2. If the client has been evicted, receive command=eviction from the cluster. (The client must re-register before sending more requests.)
        3. If the client has not been evicted, receive command=reply from cluster.

      See also:

      Protocol: Repair Grid

      Grid repair is triggered when a replica discovers a corrupt (or missing) grid block.

      1. The repairing replica sends a command=request_blocks to any other replica. The message body contains a list of block address/checksums.
      2. Upon receiving a command=request_blocks, a replica reads its own grid to check for the requested blocks. For each matching block found, reply with the command=block message (the block itself).
      3. Upon receiving a command=block, a replica writes the block to its grid, and resolves the reads that were blocked on it.

      Note that both sides of grid repair can run while the grid is being opened during replica startup. -That is, a replica can help other replicas repair and repair itself simultaneously.

      TODO Describe state sync fallback.

      Protocol: Sync Superblock

      State sync synchronizes the state of a lagging replica with the healthy cluster.

      State sync is used when when a lagging replica's log no longer intersects with the cluster's current log — -WAL repair cannot catch the replica up.

      This protocol updates the replica's superblock with a more recent one.

      See State Sync for details.

      Protocol: Sync Client Replies

      Sync missed client replies using Protocol: Repair Grid.

      (Runs immediately after Protocol: Sync Superblock.) -See State Sync for details.

      Protocol: Sync Forest

      Sync missed LSM manifest and table blocks using Protocol: Repair Grid.

      (Runs immediately after Protocol: Sync Superblock.) -See State Sync for details.

      Protocol: Reconfiguration

      TODO (Unimplemented)

      Quorums

      • The replication quorum is the minimum number of replicas required to complete a commit.
      • The view-change quorum is the minimum number of replicas required to complete a view-change.
      • The nack quorum is the minimum number of unique nacks required to truncate an uncommitted op.

      With the default configuration:

      Replica Count123456
      Replication Quorum122233
      View-Change Quorum122334
      Nack Quorum112334

      See also:

      Further reading

    - +the new primary forfeits.
  • The primary installs the headers to its suffix.
  • Then the primary repairs its headers. (Protocol: Repair Journal).
  • Then the primary repairs its prepares. (Protocol: Repair WAL) (and potentially truncates uncommitted ops).
  • Then primary commits all prepares which are not known to be uncommitted.
  • Then the primary transitions to status=normal and broadcasts a command=start_view.
  • Protocol: Request/Start View

    request_start_view

    A backup sends a command=request_start_view to the primary of a view when any of the following occur:

    • the backup learns about a newer view via a command=commit message, or
    • the backup learns about a newer view via a command=prepare message, or
    • the backup discovers commit_max exceeds min(op_head, op_checkpoint_next_trigger) (during repair),
    • the backup can't make progress committing and needs to state sync, or
    • a replica recovers to status=recovering_head

    start_view

    When a status=normal primary receives command=request_start_view, it replies with a command=start_view. +command=start_view includes:

    • The view's current suffix — the headers of the latest messages in the view.
    • The current checkpoint (see State Sync).

    Together, the checkpoint and the view headers fully specify the logical and physical state of the view.

    Upon receiving a start_view for the new view, the backup installs the checkpoint if needed, installs the suffix, transitions to status=normal, and begins repair.

    A start_view contains the following headers (which may overlap):

    • The suffix: pipeline_prepare_queue_max headers from the head op down.
    • The "hooks": the header of any previous checkpoint triggers within our repairable range. +This helps a lagging replica catch up. (There are at most 2).

    Protocol: Repair Journal

    request_headers and headers repair gaps or breaks in a replica's journal headers. +Repaired headers are a prerequisite for repairing prepares.

    Because the headers are repaired backwards (from the head) by hash-chaining, it is safe for both backups and transitioning primaries.

    Gaps/breaks in a replica's journal headers may occur:

    • On a backup, receiving nonconsecutive ops, leaving a gap in its headers.
    • On a backup, which has not finished repair.
    • On a new primary during a view-change, which has not finished repair.

    Protocol: Repair WAL

    The replica's journal tracks which prepares the WAL requires — i.e. headers for which either:

    • no prepare was ever received, or
    • the prepare was received and written, but was since discovered to be corrupt

    During repair, missing/damaged prepares are requested & repaired chronologically, which:

    • improves the chances that older entries will be available, i.e. not yet overwritten
    • enables better pipelining of repair and commit.

    In response to a request_prepare:

    • Reply the command=prepare with the requested prepare, if available and valid.
    • Otherwise do not reply. (e.g. the corresponding slot in the WAL is corrupt)

    Per PAR's CTRL Protocol, we do not nack corrupt entries, since they might be the prepare being requested.

    See also State Sync protocol — the extent of WAL that the replica can/should repair +depends on the checkpoint.

    Protocol: Repair Client Replies

    The replica stores the latest reply to each active client.

    During repair, corrupt client replies are requested & repaired.

    In response to a request_reply:

    • Respond with the command=reply (the requested reply), if available and valid.
    • Otherwise do not reply.

    Protocol: Client

    1. Client sends command=request operation=register to registers with the cluster by starting a new request-reply hashchain. (See also: Protocol: Normal).
    2. Client receives command=reply operation=register from the cluster. (If the cluster is at the maximum number of clients, it evicts the oldest).
    3. Repeat:
      1. Send command=request to cluster.
      2. If the client has been evicted, receive command=eviction from the cluster. (The client must re-register before sending more requests.)
      3. If the client has not been evicted, receive command=reply from cluster.

    See also:

    Protocol: Repair Grid

    Grid repair is triggered when a replica discovers a corrupt (or missing) grid block.

    1. The repairing replica sends a command=request_blocks to any other replica. The message body contains a list of block address/checksums.
    2. Upon receiving a command=request_blocks, a replica reads its own grid to check for the requested blocks. For each matching block found, reply with the command=block message (the block itself).
    3. Upon receiving a command=block, a replica writes the block to its grid, and resolves the reads that were blocked on it.

    Note that both sides of grid repair can run while the grid is being opened during replica startup. +That is, a replica can help other replicas repair and repair itself simultaneously.

    TODO Describe state sync fallback.

    Protocol: Sync Client Replies

    Sync missed client replies using Protocol: Repair Grid.

    See State Sync for details.

    Protocol: Sync Forest

    Sync missed LSM manifest and table blocks using Protocol: Repair Grid.

    See State Sync for details.

    Protocol: Reconfiguration

    TODO (Unimplemented)

    Quorums

    • The replication quorum is the minimum number of replicas required to complete a commit.
    • The view-change quorum is the minimum number of replicas required to complete a view-change.
    • The nack quorum is the minimum number of unique nacks required to truncate an uncommitted op.

    With the default configuration:

    Replica Count123456
    Replication Quorum122233
    View-Change Quorum122334
    Nack Quorum112334

    See also:

    Further reading

    + \ No newline at end of file diff --git a/about/oltp/index.html b/about/oltp/index.html index 94de9081..e848550e 100644 --- a/about/oltp/index.html +++ b/about/oltp/index.html @@ -6,7 +6,7 @@ Built for OLTP | TigerBeetle Docs - + @@ -85,7 +85,7 @@ how much of business transactions. And it is built to handle write-heavy and high-contention workloads at high performance and with strong safety guarantees. TigerBeetle can help you build your application correctly today, and it can handle the scale as your business grows.

    - + \ No newline at end of file diff --git a/about/performance/index.html b/about/performance/index.html index 3c089e93..d64dbb9b 100644 --- a/about/performance/index.html +++ b/about/performance/index.html @@ -6,7 +6,7 @@ Performance | TigerBeetle Docs - + @@ -53,7 +53,7 @@ the transactions so the shards responsible for those accounts become bottlenecks.

    For more details on when single-threaded implementations of algorithms outperform multi-threaded implementations, see "Scalability! But at what COST?.

    - + \ No newline at end of file diff --git a/about/production-ready/index.html b/about/production-ready/index.html index c8f76c90..adcb0f8e 100644 --- a/about/production-ready/index.html +++ b/about/production-ready/index.html @@ -6,7 +6,7 @@ Production Ready | TigerBeetle Docs - + @@ -21,7 +21,7 @@ transfers, may be replaced by TigerBeetle's new Query engine when it ships. These features will be simple to update in code, and will first be deprecated, with time to update between versions.

    We are happy to provide assistance with operating TigerBeetle in production. Please contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com if your company would like professional support.

    - + \ No newline at end of file diff --git a/about/safety/index.html b/about/safety/index.html index 0d19f639..7c68cce7 100644 --- a/about/safety/index.html +++ b/about/safety/index.html @@ -6,7 +6,7 @@ Safety | TigerBeetle Docs - + @@ -92,7 +92,7 @@ attack surface.

    We are confident that io_uring is the safest (and most performant) way for TigerBeetle to handle async I/O. It is significantly easier for the kernel to implement this correctly than for us to include a userspace multithreaded thread pool (for example, as libuv does).

    - + \ No newline at end of file diff --git a/about/vopr/index.html b/about/vopr/index.html index 5526679e..b1df5e5f 100644 --- a/about/vopr/index.html +++ b/about/vopr/index.html @@ -6,7 +6,7 @@ Deterministic Simulation Testing | TigerBeetle Docs - + @@ -39,7 +39,7 @@ storage checkers verify that this is the case across simulations.

    Inspiration

    TigerBeetle's approach to DST was heavily inspired by the work of FoundationDB and Antithesis.

    Learn More

    - + \ No newline at end of file diff --git a/about/zig/index.html b/about/zig/index.html index bc244e52..dec25089 100644 --- a/about/zig/index.html +++ b/about/zig/index.html @@ -6,7 +6,7 @@ Zig | TigerBeetle Docs - + @@ -26,7 +26,7 @@ TigerBeetle to adopt Zig since our stable roadmaps will probably coincide. We wanted to invest for the next 20 years and didn't want to be stuck with C/C++ or compiler/language complexity and pay a tax for the lifetime of the project.

    - + \ No newline at end of file diff --git a/assets/js/13a99faa.7e453912.js b/assets/js/13a99faa.46b4d6cd.js similarity index 62% rename from assets/js/13a99faa.7e453912.js rename to assets/js/13a99faa.46b4d6cd.js index 3c8740b6..4387740b 100644 --- a/assets/js/13a99faa.7e453912.js +++ b/assets/js/13a99faa.46b4d6cd.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1372],{3905:(e,n,t)=>{t.d(n,{Zo:()=>u,kt:()=>f});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function s(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=r.createContext({}),c=function(e){var n=r.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},u=function(e){var n=c(e.components);return r.createElement(l.Provider,{value:n},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},h=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),p=c(t),h=a,f=p["".concat(l,".").concat(h)]||p[h]||d[h]||i;return t?r.createElement(f,s(s({ref:n},u),{},{components:t})):r.createElement(f,s({ref:n},u))}));function f(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,s=new Array(i);s[0]=h;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[p]="string"==typeof e?e:a,s[1]=o;for(var c=2;c{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var r=t(7462),a=(t(7294),t(3905));const i={title:"Go"},s=void 0,o={unversionedId:"clients/go",id:"clients/go",title:"Go",description:"The TigerBeetle client for Go.",source:"@site/pages/clients/go.md",sourceDirName:"clients",slug:"/clients/go",permalink:"/clients/go",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/go.md",tags:[],version:"current",frontMatter:{title:"Go"},sidebar:"tutorialSidebar",previous:{title:".NET",permalink:"/clients/dotnet"},next:{title:"Java",permalink:"/clients/java"}},l={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],u={toc:c},p="wrapper";function d(e){let{components:n,...t}=e;return(0,a.kt)(p,(0,r.Z)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"tigerbeetle-go"},"tigerbeetle-go"),(0,a.kt)("p",null,"The TigerBeetle client for Go."),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go"},(0,a.kt)("img",{parentName:"a",src:"https://pkg.go.dev/badge/github.com/tigerbeetle/tigerbeetle-go.svg",alt:"Go Reference"}))),(0,a.kt)("p",null,"Make sure to import ",(0,a.kt)("inlineCode",{parentName:"p"},"github.com/tigerbeetle/tigerbeetle-go"),", not\nthis repo and subdirectory."),(0,a.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,a.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Go >= 1.21")),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Additionally on Windows"),": you must install ",(0,a.kt)("a",{parentName:"p",href:"https://ziglang.org/download/#release-0.13.0"},"Zig\n0.13.0")," and set the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CC")," environment variable to ",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe cc"),". Use the full path for\n",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe"),"."),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("p",null,"First, create a directory for your project and ",(0,a.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,a.kt)("p",null,"Then, install the TigerBeetle client:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go mod init tbtest\ngo get github.com/tigerbeetle/tigerbeetle-go\n")),(0,a.kt)("p",null,"Now, create ",(0,a.kt)("inlineCode",{parentName:"p"},"main.go")," and copy this into it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'package main\n\nimport (\n "fmt"\n "log"\n "os"\n\n . "github.com/tigerbeetle/tigerbeetle-go"\n . "github.com/tigerbeetle/tigerbeetle-go/pkg/types"\n)\n\nfunc main() {\n fmt.Println("Import ok!")\n}\n\n')),(0,a.kt)("p",null,"Finally, build and run:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go run main.go\n")),(0,a.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,a.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,a.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,a.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,a.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,a.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,a.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,a.kt)("p",null,"In this example the cluster ID is ",(0,a.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,a.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,a.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'tbAddress := os.Getenv("TB_ADDRESS")\nif len(tbAddress) == 0 {\n tbAddress = "3000"\n}\nclient, err := NewClient(ToUint128(0), []string{tbAddress})\nif err != nil {\n log.Printf("Error creating client: %s", err)\n return\n}\ndefer client.Close()\n')),(0,a.kt)("p",null,"The following are valid addresses:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,a.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,a.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,a.kt)("p",null,"See details for account fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accountsRes, err := client.CreateAccounts([]Account{\n {\n ID: ToUint128(137),\n DebitsPending: ToUint128(0),\n DebitsPosted: ToUint128(0),\n CreditsPending: ToUint128(0),\n CreditsPosted: ToUint128(0),\n UserData128: ToUint128(0),\n UserData64: 0,\n UserData32: 0,\n Reserved: 0,\n Ledger: 1,\n Code: 718,\n Flags: 0,\n Timestamp: 0,\n },\n})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n\nfor _, err := range accountsRes {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"Uint128")," fields like ",(0,a.kt)("inlineCode",{parentName:"p"},"ID"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"UserData128"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Amount")," and\naccount balances have a few helper functions to make it easier\nto convert 128-bit little-endian unsigned integers between\n",(0,a.kt)("inlineCode",{parentName:"p"},"string"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"math/big.Int"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"[]byte"),"."),(0,a.kt)("p",null,"See the type ",(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go/pkg/types#Uint128"},"Uint128")," for more details."),(0,a.kt)("h3",{id:"account-flags"},"Account Flags"),(0,a.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.AccountFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{DebitsMustNotExceedCredits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{CreditsMustNotExceedDebits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{History: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account0 := Account{ /* ... account values ... */ }\naccount1 := Account{ /* ... account values ... */ }\naccount0.Flags = AccountFlags{Linked: true}.ToUint16()\n\naccountErrors, err := client.CreateAccounts([]Account{account0, account1})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account2 := Account{ /* ... account values ... */ }\naccount3 := Account{ /* ... account values ... */ }\naccount4 := Account{ /* ... account values ... */ }\n\naccountErrors, err = client.CreateAccounts([]Account{account2, account3, account4})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\nfor _, err := range accountErrors {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,a.kt)("inlineCode",{parentName:"p"},"client.createAccounts")," with enum values in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object, or you can 2) look up the error code in\nthe ",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object for a human-readable string."),(0,a.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,a.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,a.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accounts, err := client.LookupAccounts([]Uint128{ToUint128(137), ToUint128(138)})\nif err != nil {\n log.Printf("Could not fetch accounts: %s", err)\n return\n}\nlog.Println(accounts)\n')),(0,a.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,a.kt)("p",null,"This creates a journal entry between two accounts."),(0,a.kt)("p",null,"See details for transfer fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers := []Transfer{{\n ID: ToUint128(1),\n DebitAccountID: ToUint128(1),\n CreditAccountID: ToUint128(2),\n Amount: ToUint128(10),\n PendingID: ToUint128(0),\n UserData128: ToUint128(2),\n UserData64: 0,\n UserData32: 0,\n Timeout: 0,\n Ledger: 1,\n Code: 1,\n Flags: 0,\n Timestamp: 0,\n}}\n\ntransfersRes, err := client.CreateTransfers(transfers)\nif err != nil {\n log.Printf("Error creating transfer batch: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'for _, err := range transfersRes {\n log.Printf("Batch transfer at %d failed to create: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("h2",{id:"batching"},"Batching"),(0,a.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,a.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"for i := 0; i < len(transfers); i++ {\n transfersRes, err = client.CreateTransfers([]Transfer{transfers[i]})\n // Error handling omitted.\n}\n")),(0,a.kt)("p",null,"But the insert rate will be a ",(0,a.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,a.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,a.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"BATCH_SIZE := 8190\nfor i := 0; i < len(transfers); i += BATCH_SIZE {\n batch := BATCH_SIZE\n if i+BATCH_SIZE > len(transfers) {\n batch = len(transfers) - i\n }\n transfersRes, err = client.CreateTransfers(transfers[i : i+batch])\n // Error handling omitted.\n}\n")),(0,a.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,a.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,a.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,a.kt)("p",null,"The transfer ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.TransferFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Pending: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{PostPendingTransfer: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{VoidPendingTransfer: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer0 := Transfer{ /* ... account values ... */ }\ntransfer1 := Transfer{ /* ... account values ... */ }\ntransfer0.Flags = TransferFlags{Linked: true}.ToUint16()\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer0, transfer1})\n// Error handling omitted.\n")),(0,a.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,a.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,a.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,a.kt)("p",null,"With ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer := Transfer{\n ID: ToUint128(2),\n // Post the entire pending amount.\n Amount: AmountMax,\n PendingID: ToUint128(1),\n Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,a.kt)("p",null,"In contrast, with ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,a.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer = Transfer{\n ID: ToUint128(2),\n PendingID: ToUint128(1),\n Flags: TransferFlags{VoidPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,a.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,a.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,a.kt)("p",null,"If no transfer matches an ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,a.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers, err = client.LookupTransfers([]Uint128{ToUint128(1), ToUint128(2)})\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter := AccountFilter{\n AccountID: ToUint128(2),\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten transfers at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\ntransfers, err = client.GetAccountTransfers(filter)\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,a.kt)("p",null,"Only accounts created with the flag\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,a.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,a.kt)("p",null,"The balances in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter = AccountFilter{\n AccountID: ToUint128(2),\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\naccount_balances, err := client.GetAccountBalances(filter)\nif err != nil {\n log.Printf("Could not fetch the history: %s", err)\n return\n}\nlog.Println(account_balances)\n')),(0,a.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The accounts in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter := QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code\n Ledger: 0, // No filter by Ledger\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_accounts, err := client.QueryAccounts(query_filter)\nif err != nil {\n log.Printf("Could not query accounts: %s", err)\n return\n}\nlog.Println(query_accounts)\n')),(0,a.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter = QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData.\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code.\n Ledger: 0, // No filter by Ledger.\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_transfers, err := client.QueryTransfers(query_filter)\nif err != nil {\n log.Printf("Could not query transfers: %s", err)\n return\n}\nlog.Println(query_transfers)\n')),(0,a.kt)("h2",{id:"linked-events"},"Linked Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,a.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,a.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"batch := []Transfer{}\nlinkedFlag := TransferFlags{Linked: true}.ToUint16()\n\n// An individual transfer (successful):\nbatch = append(batch, Transfer{ID: ToUint128(1) /* ... rest of transfer ... */})\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Fail with exists\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... , */}) // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (successful):\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... rest of transfer ... */})\n\ntransfersRes, err = client.CreateTransfers(batch)\n")),(0,a.kt)("h2",{id:"imported-events"},"Imported Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,a.kt)("p",null,"The entire batch of events must be set with the flag ",(0,a.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,a.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"// First, load and import all accounts with their timestamps from the historical source.\naccountsBatch := []Account{}\nfor index, account := range historicalAccounts {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n account.Timestamp = historicalTimestamp\n\n account.Flags = AccountFlags{\n // Set the account as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n accountsBatch = append(accountsBatch, account)\n}\naccountsRes, err = client.CreateAccounts(accountsBatch)\n\n// Then, load and import all transfers with their timestamps from the historical source.\ntransfersBatch := []Transfer{}\nfor index, transfer := range historicalTransfers {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n transfer.Timestamp = historicalTimestamp\n\n transfer.Flags = TransferFlags{\n // Set the transfer as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n transfersBatch = append(transfersBatch, transfer)\n}\ntransfersRes, err = client.CreateTransfers(transfersBatch)\n// Error handling omitted..\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1372],{3905:(e,n,t)=>{t.d(n,{Zo:()=>u,kt:()=>f});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function s(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=r.createContext({}),c=function(e){var n=r.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},u=function(e){var n=c(e.components);return r.createElement(l.Provider,{value:n},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},h=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),p=c(t),h=a,f=p["".concat(l,".").concat(h)]||p[h]||d[h]||i;return t?r.createElement(f,s(s({ref:n},u),{},{components:t})):r.createElement(f,s({ref:n},u))}));function f(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,s=new Array(i);s[0]=h;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[p]="string"==typeof e?e:a,s[1]=o;for(var c=2;c{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var r=t(7462),a=(t(7294),t(3905));const i={title:"Go"},s=void 0,o={unversionedId:"clients/go",id:"clients/go",title:"Go",description:"The TigerBeetle client for Go.",source:"@site/pages/clients/go.md",sourceDirName:"clients",slug:"/clients/go",permalink:"/clients/go",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/go.md",tags:[],version:"current",frontMatter:{title:"Go"},sidebar:"tutorialSidebar",previous:{title:".NET",permalink:"/clients/dotnet"},next:{title:"Java",permalink:"/clients/java"}},l={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],u={toc:c},p="wrapper";function d(e){let{components:n,...t}=e;return(0,a.kt)(p,(0,r.Z)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"tigerbeetle-go"},"tigerbeetle-go"),(0,a.kt)("p",null,"The TigerBeetle client for Go."),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go"},(0,a.kt)("img",{parentName:"a",src:"https://pkg.go.dev/badge/github.com/tigerbeetle/tigerbeetle-go.svg",alt:"Go Reference"}))),(0,a.kt)("p",null,"Make sure to import ",(0,a.kt)("inlineCode",{parentName:"p"},"github.com/tigerbeetle/tigerbeetle-go"),", not\nthis repo and subdirectory."),(0,a.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,a.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Go >= 1.21")),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Additionally on Windows"),": you must install ",(0,a.kt)("a",{parentName:"p",href:"https://ziglang.org/download/#release-0.13.0"},"Zig\n0.13.0")," and set the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CC")," environment variable to ",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe cc"),". Use the full path for\n",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe"),"."),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("p",null,"First, create a directory for your project and ",(0,a.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,a.kt)("p",null,"Then, install the TigerBeetle client:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go mod init tbtest\ngo get github.com/tigerbeetle/tigerbeetle-go\n")),(0,a.kt)("p",null,"Now, create ",(0,a.kt)("inlineCode",{parentName:"p"},"main.go")," and copy this into it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'package main\n\nimport (\n "fmt"\n "log"\n "os"\n\n . "github.com/tigerbeetle/tigerbeetle-go"\n . "github.com/tigerbeetle/tigerbeetle-go/pkg/types"\n)\n\nfunc main() {\n fmt.Println("Import ok!")\n}\n\n')),(0,a.kt)("p",null,"Finally, build and run:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go run main.go\n")),(0,a.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,a.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,a.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,a.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,a.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,a.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,a.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,a.kt)("p",null,"In this example the cluster ID is ",(0,a.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,a.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,a.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'tbAddress := os.Getenv("TB_ADDRESS")\nif len(tbAddress) == 0 {\n tbAddress = "3000"\n}\nclient, err := NewClient(ToUint128(0), []string{tbAddress})\nif err != nil {\n log.Printf("Error creating client: %s", err)\n return\n}\ndefer client.Close()\n')),(0,a.kt)("p",null,"The following are valid addresses:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,a.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,a.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,a.kt)("p",null,"See details for account fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accountsRes, err := client.CreateAccounts([]Account{\n {\n ID: ToUint128(137),\n DebitsPending: ToUint128(0),\n DebitsPosted: ToUint128(0),\n CreditsPending: ToUint128(0),\n CreditsPosted: ToUint128(0),\n UserData128: ToUint128(0),\n UserData64: 0,\n UserData32: 0,\n Reserved: 0,\n Ledger: 1,\n Code: 718,\n Flags: 0,\n Timestamp: 0,\n },\n})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n\nfor _, err := range accountsRes {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"Uint128")," fields like ",(0,a.kt)("inlineCode",{parentName:"p"},"ID"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"UserData128"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Amount")," and\naccount balances have a few helper functions to make it easier\nto convert 128-bit little-endian unsigned integers between\n",(0,a.kt)("inlineCode",{parentName:"p"},"string"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"math/big.Int"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"[]byte"),"."),(0,a.kt)("p",null,"See the type ",(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go/pkg/types#Uint128"},"Uint128")," for more details."),(0,a.kt)("h3",{id:"account-flags"},"Account Flags"),(0,a.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.AccountFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{DebitsMustNotExceedCredits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{CreditsMustNotExceedDebits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{History: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account0 := Account{ /* ... account values ... */ }\naccount1 := Account{ /* ... account values ... */ }\naccount0.Flags = AccountFlags{Linked: true}.ToUint16()\n\naccountErrors, err := client.CreateAccounts([]Account{account0, account1})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account2 := Account{ /* ... account values ... */ }\naccount3 := Account{ /* ... account values ... */ }\naccount4 := Account{ /* ... account values ... */ }\n\naccountErrors, err = client.CreateAccounts([]Account{account2, account3, account4})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\nfor _, err := range accountErrors {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,a.kt)("inlineCode",{parentName:"p"},"client.createAccounts")," with enum values in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object, or you can 2) look up the error code in\nthe ",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object for a human-readable string."),(0,a.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,a.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,a.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accounts, err := client.LookupAccounts([]Uint128{ToUint128(137), ToUint128(138)})\nif err != nil {\n log.Printf("Could not fetch accounts: %s", err)\n return\n}\nlog.Println(accounts)\n')),(0,a.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,a.kt)("p",null,"This creates a journal entry between two accounts."),(0,a.kt)("p",null,"See details for transfer fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers := []Transfer{{\n ID: ToUint128(1),\n DebitAccountID: ToUint128(1),\n CreditAccountID: ToUint128(2),\n Amount: ToUint128(10),\n PendingID: ToUint128(0),\n UserData128: ToUint128(2),\n UserData64: 0,\n UserData32: 0,\n Timeout: 0,\n Ledger: 1,\n Code: 1,\n Flags: 0,\n Timestamp: 0,\n}}\n\ntransfersRes, err := client.CreateTransfers(transfers)\nif err != nil {\n log.Printf("Error creating transfer batch: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'for _, err := range transfersRes {\n log.Printf("Batch transfer at %d failed to create: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("h2",{id:"batching"},"Batching"),(0,a.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,a.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"for i := 0; i < len(transfers); i++ {\n transfersRes, err = client.CreateTransfers([]Transfer{transfers[i]})\n // Error handling omitted.\n}\n")),(0,a.kt)("p",null,"But the insert rate will be a ",(0,a.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,a.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,a.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"BATCH_SIZE := 8190\nfor i := 0; i < len(transfers); i += BATCH_SIZE {\n batch := BATCH_SIZE\n if i+BATCH_SIZE > len(transfers) {\n batch = len(transfers) - i\n }\n transfersRes, err = client.CreateTransfers(transfers[i : i+batch])\n // Error handling omitted.\n}\n")),(0,a.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,a.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,a.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,a.kt)("p",null,"The transfer ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.TransferFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Pending: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{PostPendingTransfer: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{VoidPendingTransfer: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer0 := Transfer{ /* ... account values ... */ }\ntransfer1 := Transfer{ /* ... account values ... */ }\ntransfer0.Flags = TransferFlags{Linked: true}.ToUint16()\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer0, transfer1})\n// Error handling omitted.\n")),(0,a.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,a.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,a.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,a.kt)("p",null,"With ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer := Transfer{\n ID: ToUint128(2),\n // Post the entire pending amount.\n Amount: AmountMax,\n PendingID: ToUint128(1),\n Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,a.kt)("p",null,"In contrast, with ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,a.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer = Transfer{\n ID: ToUint128(2),\n PendingID: ToUint128(1),\n Flags: TransferFlags{VoidPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,a.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,a.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,a.kt)("p",null,"If no transfer matches an ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,a.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers, err = client.LookupTransfers([]Uint128{ToUint128(1), ToUint128(2)})\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter := AccountFilter{\n AccountID: ToUint128(2),\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten transfers at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\ntransfers, err = client.GetAccountTransfers(filter)\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,a.kt)("p",null,"Only accounts created with the flag\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,a.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,a.kt)("p",null,"The balances in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter = AccountFilter{\n AccountID: ToUint128(2),\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\naccount_balances, err := client.GetAccountBalances(filter)\nif err != nil {\n log.Printf("Could not fetch the history: %s", err)\n return\n}\nlog.Println(account_balances)\n')),(0,a.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The accounts in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter := QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code\n Ledger: 0, // No filter by Ledger\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_accounts, err := client.QueryAccounts(query_filter)\nif err != nil {\n log.Printf("Could not query accounts: %s", err)\n return\n}\nlog.Println(query_accounts)\n')),(0,a.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter = QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData.\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code.\n Ledger: 0, // No filter by Ledger.\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_transfers, err := client.QueryTransfers(query_filter)\nif err != nil {\n log.Printf("Could not query transfers: %s", err)\n return\n}\nlog.Println(query_transfers)\n')),(0,a.kt)("h2",{id:"linked-events"},"Linked Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,a.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,a.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"batch := []Transfer{}\nlinkedFlag := TransferFlags{Linked: true}.ToUint16()\n\n// An individual transfer (successful):\nbatch = append(batch, Transfer{ID: ToUint128(1) /* ... rest of transfer ... */})\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Fail with exists\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... , */}) // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (successful):\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... rest of transfer ... */})\n\ntransfersRes, err = client.CreateTransfers(batch)\n")),(0,a.kt)("h2",{id:"imported-events"},"Imported Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,a.kt)("p",null,"The entire batch of events must be set with the flag ",(0,a.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,a.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"// First, load and import all accounts with their timestamps from the historical source.\naccountsBatch := []Account{}\nfor index, account := range historicalAccounts {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n account.Timestamp = historicalTimestamp\n\n account.Flags = AccountFlags{\n // Set the account as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n accountsBatch = append(accountsBatch, account)\n}\naccountsRes, err = client.CreateAccounts(accountsBatch)\n\n// Then, load and import all transfers with their timestamps from the historical source.\ntransfersBatch := []Transfer{}\nfor index, transfer := range historicalTransfers {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n transfer.Timestamp = historicalTimestamp\n\n transfer.Flags = TransferFlags{\n // Set the transfer as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n transfersBatch = append(transfersBatch, transfer)\n}\ntransfersRes, err = client.CreateTransfers(transfersBatch)\n// Error handling omitted..\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2cf7febb.1308ecc4.js b/assets/js/2cf7febb.1308ecc4.js deleted file mode 100644 index a7cae461..00000000 --- a/assets/js/2cf7febb.1308ecc4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9743],{3905:(e,t,n)=>{n.d(t,{Zo:()=>f,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function d(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),o=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):d(d({},t),e)),n},f=function(e){var t=o(e.components);return a.createElement(l.Provider,{value:t},e.children)},p="mdxType",_={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,l=e.parentName,f=s(e,["components","mdxType","originalType","parentName"]),p=o(n),c=r,m=p["".concat(l,".").concat(c)]||p[c]||_[c]||i;return n?a.createElement(m,d(d({ref:t},f),{},{components:n})):a.createElement(m,d({ref:t},f))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,d=new Array(i);d[0]=c;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:r,d[1]=s;for(var o=2;o{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>d,default:()=>_,frontMatter:()=>i,metadata:()=>s,toc:()=>o});var a=n(7462),r=(n(7294),n(3905));const i={},d="create_transfers",s={unversionedId:"reference/requests/create_transfers",id:"reference/requests/create_transfers",title:"create_transfers",description:"Create one or more Transfers. A successfully created transfer will modify the",source:"@site/pages/reference/requests/create_transfers.md",sourceDirName:"reference/requests",slug:"/reference/requests/create_transfers",permalink:"/reference/requests/create_transfers",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/requests/create_transfers.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"create_accounts",permalink:"/reference/requests/create_accounts"},next:{title:"get_account_balances",permalink:"/reference/requests/get_account_balances"}},l={},o=[{value:"Event",id:"event",level:2},{value:"Result",id:"result",level:2},{value:"ok",id:"ok",level:3},{value:"linked_event_failed",id:"linked_event_failed",level:3},{value:"linked_event_chain_open",id:"linked_event_chain_open",level:3},{value:"imported_event_expected",id:"imported_event_expected",level:3},{value:"imported_event_not_expected",id:"imported_event_not_expected",level:3},{value:"timestamp_must_be_zero",id:"timestamp_must_be_zero",level:3},{value:"imported_event_timestamp_out_of_range",id:"imported_event_timestamp_out_of_range",level:3},{value:"imported_event_timestamp_must_not_advance",id:"imported_event_timestamp_must_not_advance",level:3},{value:"reserved_flag",id:"reserved_flag",level:3},{value:"id_must_not_be_zero",id:"id_must_not_be_zero",level:3},{value:"id_must_not_be_int_max",id:"id_must_not_be_int_max",level:3},{value:"flags_are_mutually_exclusive",id:"flags_are_mutually_exclusive",level:3},{value:"debit_account_id_must_not_be_zero",id:"debit_account_id_must_not_be_zero",level:3},{value:"debit_account_id_must_not_be_int_max",id:"debit_account_id_must_not_be_int_max",level:3},{value:"credit_account_id_must_not_be_zero",id:"credit_account_id_must_not_be_zero",level:3},{value:"credit_account_id_must_not_be_int_max",id:"credit_account_id_must_not_be_int_max",level:3},{value:"accounts_must_be_different",id:"accounts_must_be_different",level:3},{value:"pending_id_must_be_zero",id:"pending_id_must_be_zero",level:3},{value:"pending_id_must_not_be_zero",id:"pending_id_must_not_be_zero",level:3},{value:"pending_id_must_not_be_int_max",id:"pending_id_must_not_be_int_max",level:3},{value:"pending_id_must_be_different",id:"pending_id_must_be_different",level:3},{value:"timeout_reserved_for_pending_transfer",id:"timeout_reserved_for_pending_transfer",level:3},{value:"closing_transfer_must_be_pending",id:"closing_transfer_must_be_pending",level:3},{value:"amount_must_not_be_zero",id:"amount_must_not_be_zero",level:3},{value:"ledger_must_not_be_zero",id:"ledger_must_not_be_zero",level:3},{value:"code_must_not_be_zero",id:"code_must_not_be_zero",level:3},{value:"debit_account_not_found",id:"debit_account_not_found",level:3},{value:"credit_account_not_found",id:"credit_account_not_found",level:3},{value:"accounts_must_have_the_same_ledger",id:"accounts_must_have_the_same_ledger",level:3},{value:"transfer_must_have_the_same_ledger_as_accounts",id:"transfer_must_have_the_same_ledger_as_accounts",level:3},{value:"pending_transfer_not_found",id:"pending_transfer_not_found",level:3},{value:"pending_transfer_not_pending",id:"pending_transfer_not_pending",level:3},{value:"pending_transfer_has_different_debit_account_id",id:"pending_transfer_has_different_debit_account_id",level:3},{value:"pending_transfer_has_different_credit_account_id",id:"pending_transfer_has_different_credit_account_id",level:3},{value:"pending_transfer_has_different_ledger",id:"pending_transfer_has_different_ledger",level:3},{value:"pending_transfer_has_different_code",id:"pending_transfer_has_different_code",level:3},{value:"exceeds_pending_transfer_amount",id:"exceeds_pending_transfer_amount",level:3},{value:"pending_transfer_has_different_amount",id:"pending_transfer_has_different_amount",level:3},{value:"pending_transfer_already_posted",id:"pending_transfer_already_posted",level:3},{value:"pending_transfer_already_voided",id:"pending_transfer_already_voided",level:3},{value:"pending_transfer_expired",id:"pending_transfer_expired",level:3},{value:"exists_with_different_flags",id:"exists_with_different_flags",level:3},{value:"exists_with_different_debit_account_id",id:"exists_with_different_debit_account_id",level:3},{value:"exists_with_different_credit_account_id",id:"exists_with_different_credit_account_id",level:3},{value:"exists_with_different_amount",id:"exists_with_different_amount",level:3},{value:"exists_with_different_pending_id",id:"exists_with_different_pending_id",level:3},{value:"exists_with_different_user_data_128",id:"exists_with_different_user_data_128",level:3},{value:"exists_with_different_user_data_64",id:"exists_with_different_user_data_64",level:3},{value:"exists_with_different_user_data_32",id:"exists_with_different_user_data_32",level:3},{value:"exists_with_different_timeout",id:"exists_with_different_timeout",level:3},{value:"exists_with_different_code",id:"exists_with_different_code",level:3},{value:"exists",id:"exists",level:3},{value:"imported_event_timestamp_must_not_regress",id:"imported_event_timestamp_must_not_regress",level:3},{value:"imported_event_timestamp_must_postdate_debit_account",id:"imported_event_timestamp_must_postdate_debit_account",level:3},{value:"imported_event_timestamp_must_postdate_credit_account",id:"imported_event_timestamp_must_postdate_credit_account",level:3},{value:"imported_event_timeout_must_be_zero",id:"imported_event_timeout_must_be_zero",level:3},{value:"debit_account_already_closed",id:"debit_account_already_closed",level:3},{value:"debit_account_already_closed",id:"debit_account_already_closed-1",level:3},{value:"overflows_debits_pending",id:"overflows_debits_pending",level:3},{value:"overflows_credits_pending",id:"overflows_credits_pending",level:3},{value:"overflows_debits_posted",id:"overflows_debits_posted",level:3},{value:"overflows_credits_posted",id:"overflows_credits_posted",level:3},{value:"overflows_debits",id:"overflows_debits",level:3},{value:"overflows_credits",id:"overflows_credits",level:3},{value:"overflows_timeout",id:"overflows_timeout",level:3},{value:"exceeds_credits",id:"exceeds_credits",level:3},{value:"exceeds_debits",id:"exceeds_debits",level:3},{value:"Client libraries",id:"client-libraries",level:2},{value:"Internals",id:"internals",level:2}],f={toc:o},p="wrapper";function _(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"create_transfers"},(0,r.kt)("inlineCode",{parentName:"h1"},"create_transfers")),(0,r.kt)("p",null,"Create one or more ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer")),"s. A successfully created transfer will modify the\namount fields of its ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},"debit")," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},"credit")," accounts."),(0,r.kt)("h2",{id:"event"},"Event"),(0,r.kt)("p",null,"The transfer to create. See ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer"))," for constraints."),(0,r.kt)("h2",{id:"result"},"Result"),(0,r.kt)("p",null,"Results are listed in this section in order of descending precedence \u2014 that is, if more than one\nerror is applicable to the transfer being created, only the result listed first is returned."),(0,r.kt)("h3",{id:"ok"},(0,r.kt)("inlineCode",{parentName:"h3"},"ok")),(0,r.kt)("p",null,"The transfer was successfully created; did not previously exist."),(0,r.kt)("p",null,"Note that ",(0,r.kt)("inlineCode",{parentName:"p"},"ok")," is generated by the client implementation; the network protocol does not include a\nresult when the transfer was successfully created."),(0,r.kt)("h3",{id:"linked_event_failed"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_failed")),(0,r.kt)("p",null,"The transfer was not created. One or more of the other transfers in the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},"linked chain")," is invalid, so the whole chain failed."),(0,r.kt)("h3",{id:"linked_event_chain_open"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_chain_open")),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.linked"))," flag was set\non the last event in the batch, which is not legal. (",(0,r.kt)("inlineCode",{parentName:"p"},"flags.linked")," indicates that the chain\ncontinues to the next operation)."),(0,r.kt)("h3",{id:"imported_event_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_expected")),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," was\nset on the first transfer of the batch, but not all transfers in the batch.\nBatches cannot mix imported transfers with non-imported transfers."),(0,r.kt)("h3",{id:"imported_event_not_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_not_expected")),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," was\nexpected to ",(0,r.kt)("em",{parentName:"p"},"not")," be set, as it's not allowed to mix transfers with different ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag\nin the same batch. The first transfer determines the entire operation."),(0,r.kt)("h3",{id:"timestamp_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"timestamp_must_be_zero")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},"Account.flags.imported")," is ",(0,r.kt)("em",{parentName:"p"},"not")," set."),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," is nonzero, but\nmust be zero. The cluster is responsible for setting this field."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," can only be assigned when creating transfers\nwith ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},"Transfer.flags.imported")," set."),(0,r.kt)("h3",{id:"imported_event_timestamp_out_of_range"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_out_of_range")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," is out of range,\nbut must be a user-defined timestamp greater than ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and less than ",(0,r.kt)("inlineCode",{parentName:"p"},"2^63"),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_advance"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_advance")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," is\ngreater than the current ",(0,r.kt)("a",{parentName:"p",href:"/coding/time"},"cluster time"),", but it must be a past timestamp."),(0,r.kt)("h3",{id:"reserved_flag"},(0,r.kt)("inlineCode",{parentName:"h3"},"reserved_flag")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"Transfer.flags.reserved")," is nonzero, but must be zero."),(0,r.kt)("h3",{id:"id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.id"))," is zero, which is a reserved value."),(0,r.kt)("h3",{id:"id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.id"))," is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", which is a reserved\nvalue."),(0,r.kt)("h3",{id:"flags_are_mutually_exclusive"},(0,r.kt)("inlineCode",{parentName:"h3"},"flags_are_mutually_exclusive")),(0,r.kt)("p",null,"The transfer was not created. An account cannot be created with the specified combination of\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags")),"."),(0,r.kt)("p",null,"Flag compatibility (\u2713 = compatible, \u2717 = mutually exclusive):"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit")))))),(0,r.kt)("h3",{id:"debit_account_id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," is\nzero, but must be a valid account id."),(0,r.kt)("h3",{id:"debit_account_id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," is\n",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", but must be a valid account id."),(0,r.kt)("h3",{id:"credit_account_id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"credit_account_id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," is\nzero, but must be a valid account id."),(0,r.kt)("h3",{id:"credit_account_id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"credit_account_id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," is\n",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", but must be a valid account id."),(0,r.kt)("h3",{id:"accounts_must_be_different"},(0,r.kt)("inlineCode",{parentName:"h3"},"accounts_must_be_different")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must not be equal."),(0,r.kt)("p",null,"That is, an account cannot transfer money to itself."),(0,r.kt)("h3",{id:"pending_id_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_be_zero")),(0,r.kt)("p",null,"The transfer was not created. Only post/void transfers can reference a pending transfer."),(0,r.kt)("p",null,"Either:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.post_pending_transfer"))," must be set,\nor"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.void_pending_transfer"))," must be set,\nor"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," must be zero.")),(0,r.kt)("h3",{id:"pending_id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created.\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.post_pending_transfer"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.void_pending_transfer"))," is set, but\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," is zero. A posting or voiding transfer must\nreference a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"pending"))," transfer."),(0,r.kt)("h3",{id:"pending_id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),",\nwhich is a reserved value."),(0,r.kt)("h3",{id:"pending_id_must_be_different"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_be_different")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," is set to the same\nid as ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.id")),". Instead it should refer to a different (existing)\ntransfer."),(0,r.kt)("h3",{id:"timeout_reserved_for_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"h3"},"timeout_reserved_for_pending_transfer")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timeout"))," is nonzero, but only\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},"pending")," transfers have nonzero timeouts."),(0,r.kt)("h3",{id:"closing_transfer_must_be_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"closing_transfer_must_be_pending")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.pending"))," is not set,\nbut closing transfers must be two-phase pending transfers."),(0,r.kt)("p",null,"If either ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.closing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.closing_credit"))," is set,\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.pending"))," must also be set."),(0,r.kt)("p",null,"This ensures that closing transfers are reversible by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},"voiding")," the pending transfer, and requires that the\nreversal operation references the corresponding closing transfer, guarding against unexpected\ninterleaving of close/unclose operations."),(0,r.kt)("h3",{id:"amount_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"amount_must_not_be_zero")),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"},"Deprecated"),": This error code is only returned to clients prior to release ",(0,r.kt)("inlineCode",{parentName:"p"},"0.16.0"),".\nSince ",(0,r.kt)("inlineCode",{parentName:"p"},"0.16.0"),", zero-amount transfers are permitted."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.amount"))," is zero, but must be\nnonzero."),(0,r.kt)("p",null,"Every transfer must move value. Only posting and voiding transfer amounts may be zero \u2014 when zero,\nthey will move the full pending amount.")),(0,r.kt)("h3",{id:"ledger_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"ledger_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.ledger"))," is zero, but must be\nnonzero."),(0,r.kt)("h3",{id:"code_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"code_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.code"))," is zero, but must be nonzero."),(0,r.kt)("h3",{id:"debit_account_not_found"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_not_found")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," must\nrefer to an existing ",(0,r.kt)("inlineCode",{parentName:"p"},"Account"),"."),(0,r.kt)("h3",{id:"credit_account_not_found"},(0,r.kt)("inlineCode",{parentName:"h3"},"credit_account_not_found")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must\nrefer to an existing ",(0,r.kt)("inlineCode",{parentName:"p"},"Account"),"."),(0,r.kt)("h3",{id:"accounts_must_have_the_same_ledger"},(0,r.kt)("inlineCode",{parentName:"h3"},"accounts_must_have_the_same_ledger")),(0,r.kt)("p",null,"The transfer was not created. The accounts referred to by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must have an identical\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger")),"."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"/coding/recipes/currency-exchange"},"Currency exchange")," is implemented with multiple\ntransfers."),(0,r.kt)("h3",{id:"transfer_must_have_the_same_ledger_as_accounts"},(0,r.kt)("inlineCode",{parentName:"h3"},"transfer_must_have_the_same_ledger_as_accounts")),(0,r.kt)("p",null,"The transfer was not created. The accounts referred to by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," are equivalent, but differ from the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.ledger")),"."),(0,r.kt)("h3",{id:"pending_transfer_not_found"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_not_found")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," does not exist."),(0,r.kt)("h3",{id:"pending_transfer_not_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_not_pending")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but does not have\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))," set."),(0,r.kt)("h3",{id:"pending_transfer_has_different_debit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_debit_account_id")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"debit_account_id")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account_id")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending\ntransfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account_id"),"."),(0,r.kt)("h3",{id:"pending_transfer_has_different_credit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_credit_account_id")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"credit_account_id")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account_id")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending\ntransfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account_id"),"."),(0,r.kt)("h3",{id:"pending_transfer_has_different_ledger"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_ledger")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"ledger")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending transfer's\n",(0,r.kt)("inlineCode",{parentName:"p"},"ledger"),"."),(0,r.kt)("h3",{id:"pending_transfer_has_different_code"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_code")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,r.kt)("inlineCode",{parentName:"a"},"code")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"code")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"code"),"."),(0,r.kt)("h3",{id:"exceeds_pending_transfer_amount"},(0,r.kt)("inlineCode",{parentName:"h3"},"exceeds_pending_transfer_amount")),(0,r.kt)("p",null,"The transfer was not created. The transfer's ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount"))," exceeds the ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),"\nof its ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer."),(0,r.kt)("h3",{id:"pending_transfer_has_different_amount"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_amount")),(0,r.kt)("p",null,"The transfer was not created. The transfer is attempting to\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},"void")," a pending transfer. The voiding transfer's\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount"))," must be either ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or exactly the ",(0,r.kt)("inlineCode",{parentName:"p"},"amount")," of the pending\ntransfer."),(0,r.kt)("p",null,"To partially void a transfer, create a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},"posting transfer"),"\nwith an amount less than the pending transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),"."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"To partially void a transfer, create a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},"posting transfer"),"\nwith an amount between ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and the pending transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),".")),(0,r.kt)("h3",{id:"pending_transfer_already_posted"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_already_posted")),(0,r.kt)("p",null,"The transfer was not created. The referenced ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer was\nalready posted by a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"post_pending_transfer")),"."),(0,r.kt)("h3",{id:"pending_transfer_already_voided"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_already_voided")),(0,r.kt)("p",null,"The transfer was not created. The referenced ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer was\nalready voided by a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"void_pending_transfer")),"."),(0,r.kt)("h3",{id:"pending_transfer_expired"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_expired")),(0,r.kt)("p",null,"The transfer was not created. The referenced ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer was\nalready voided because its ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},"timeout")," has passed."),(0,r.kt)("h3",{id:"exists_with_different_flags"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_flags")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"flags")),"."),(0,r.kt)("h3",{id:"exists_with_different_debit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_debit_account_id")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"debit_account_id")),"."),(0,r.kt)("h3",{id:"exists_with_different_credit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_credit_account_id")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"credit_account_id")),"."),(0,r.kt)("h3",{id:"exists_with_different_amount"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_amount")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),"."),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," set, then the actual amount\ntransferred exceeds this failed transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),"."),(0,r.kt)("h3",{id:"exists_with_different_pending_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_pending_id")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"pending_id")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_128"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_128")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_128"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_128")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_64"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_64")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_64"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_64")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_32"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_32")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_32"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_32")),"."),(0,r.kt)("h3",{id:"exists_with_different_timeout"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_timeout")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"timeout")),"."),(0,r.kt)("h3",{id:"exists_with_different_code"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_code")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,r.kt)("inlineCode",{parentName:"a"},"code")),"."),(0,r.kt)("h3",{id:"exists"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists."),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," set, then the existing\ntransfer may have a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),", limited to the maximum\n",(0,r.kt)("inlineCode",{parentName:"p"},"amount")," of the transfer in the request."),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer")),"\nset, then the existing transfer may have a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"If the original posted amount was less than the pending amount,\nthen the transfer amount must be equal to the posted amount."),(0,r.kt)("li",{parentName:"ul"},"Otherwise, the transfer amount must be greater than or equal to the pending amount.")),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," set, then the existing\ntransfer may have a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),", limited to the maximum\n",(0,r.kt)("inlineCode",{parentName:"p"},"amount")," of the transfer in the request.")),(0,r.kt)("p",null,"Otherwise, with the possible exception of the ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," field, the existing transfer is identical\nto the transfer in the request."),(0,r.kt)("p",null,"To correctly ",(0,r.kt)("a",{parentName:"p",href:"/coding/reliable-transaction-submission"},"recover from application crashes"),",\nmany applications should handle ",(0,r.kt)("inlineCode",{parentName:"p"},"exists")," exactly as ",(0,r.kt)("a",{parentName:"p",href:"#ok"},(0,r.kt)("inlineCode",{parentName:"a"},"ok")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_regress"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_regress")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp")),"\nregressed, but it must be greater than the last timestamp assigned to any ",(0,r.kt)("inlineCode",{parentName:"p"},"Transfer")," in the cluster and cannot be equal to the timestamp of any existing ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Account")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_postdate_debit_account"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_postdate_debit_account")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"timestamp"))," is less than the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_postdate_credit_account"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_postdate_credit_account")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"timestamp"))," is less than the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp")),"."),(0,r.kt)("h3",{id:"imported_event_timeout_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timeout_must_be_zero")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timeout"))," is nonzero, but\nmust be zero."),(0,r.kt)("p",null,"It's possible to import ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},"pending")," transfers with a user-defined\ntimestamp, but since it's not driven by the cluster clock, it cannot define a timeout for\nautomatic expiration.\nIn those cases, the ",(0,r.kt)("a",{parentName:"p",href:"/coding/two-phase-transfers"},"two-phase post or rollback")," must be\ndone manually."),(0,r.kt)("h3",{id:"debit_account_already_closed"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_already_closed")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsclosed"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.closed"))," is not already set."),(0,r.kt)("h3",{id:"debit_account_already_closed-1"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_already_closed")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsclosed"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.closed"))," is not already set."),(0,r.kt)("h3",{id:"overflows_debits_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_debits_pending")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_credits_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_credits_pending")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_debits_posted"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_debits_posted")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_credits_posted"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_credits_posted")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.credits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_debits"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_debits")),(0,r.kt)("p",null,"The transfer was not created.\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + debit_account.debits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_credits"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_credits")),(0,r.kt)("p",null,"The transfer was not created.\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + credit_account.credits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_timeout"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_timeout")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer.timestamp + (transfer.timeout * 1_000_000_000)")," would\nexceed ",(0,r.kt)("inlineCode",{parentName:"p"},"2^63"),"."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timeout"))," is converted to nanoseconds."),(0,r.kt)("p",null,"This computation uses the ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," value assigned by the\nreplica, not the ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," value sent by the client."),(0,r.kt)("h3",{id:"exceeds_credits"},(0,r.kt)("inlineCode",{parentName:"h3"},"exceeds_credits")),(0,r.kt)("p",null,"The transfer was not created."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},"debit account")," has\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsdebits_must_not_exceed_credits"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.debits_must_not_exceed_credits"))," set, but\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + debit_account.debits_posted + transfer.amount")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.credits_posted"),"."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"If ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," is set, then\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + debit_account.debits_posted + 1")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.credits_posted"),".")),(0,r.kt)("h3",{id:"exceeds_debits"},(0,r.kt)("inlineCode",{parentName:"h3"},"exceeds_debits")),(0,r.kt)("p",null,"The transfer was not created."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},"credit account")," has\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagscredits_must_not_exceed_debits"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.credits_must_not_exceed_debits"))," set, but\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + credit_account.credits_posted + transfer.amount")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.debits_posted"),"."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"If ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," is set, then\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + credit_account.credits_posted + 1")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.debits_posted"),".")),(0,r.kt)("h2",{id:"client-libraries"},"Client libraries"),(0,r.kt)("p",null,"For language-specific docs see:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/dotnet#create-transfers"},".NET library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/java#create-transfers"},"Java library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/go#create-transfers"},"Go library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/node#create-transfers"},"Node.js library"))),(0,r.kt)("h2",{id:"internals"},"Internals"),(0,r.kt)("p",null,"If you're curious and want to learn more, you can find the source code for creating a transfer in\n",(0,r.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/state_machine.zig"},"src/state_machine.zig"),".\nSearch for ",(0,r.kt)("inlineCode",{parentName:"p"},"fn create_transfer(")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fn execute("),"."))}_.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2cf7febb.dd3e76e4.js b/assets/js/2cf7febb.dd3e76e4.js new file mode 100644 index 00000000..12bc6bfe --- /dev/null +++ b/assets/js/2cf7febb.dd3e76e4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9743],{3905:(e,t,n)=>{n.d(t,{Zo:()=>f,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function d(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),o=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):d(d({},t),e)),n},f=function(e){var t=o(e.components);return a.createElement(l.Provider,{value:t},e.children)},p="mdxType",_={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,l=e.parentName,f=s(e,["components","mdxType","originalType","parentName"]),p=o(n),c=r,m=p["".concat(l,".").concat(c)]||p[c]||_[c]||i;return n?a.createElement(m,d(d({ref:t},f),{},{components:n})):a.createElement(m,d({ref:t},f))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,d=new Array(i);d[0]=c;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:r,d[1]=s;for(var o=2;o{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>d,default:()=>_,frontMatter:()=>i,metadata:()=>s,toc:()=>o});var a=n(7462),r=(n(7294),n(3905));const i={},d="create_transfers",s={unversionedId:"reference/requests/create_transfers",id:"reference/requests/create_transfers",title:"create_transfers",description:"Create one or more Transfers. A successfully created transfer will modify the",source:"@site/pages/reference/requests/create_transfers.md",sourceDirName:"reference/requests",slug:"/reference/requests/create_transfers",permalink:"/reference/requests/create_transfers",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/requests/create_transfers.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"create_accounts",permalink:"/reference/requests/create_accounts"},next:{title:"get_account_balances",permalink:"/reference/requests/get_account_balances"}},l={},o=[{value:"Event",id:"event",level:2},{value:"Result",id:"result",level:2},{value:"ok",id:"ok",level:3},{value:"linked_event_failed",id:"linked_event_failed",level:3},{value:"linked_event_chain_open",id:"linked_event_chain_open",level:3},{value:"imported_event_expected",id:"imported_event_expected",level:3},{value:"imported_event_not_expected",id:"imported_event_not_expected",level:3},{value:"timestamp_must_be_zero",id:"timestamp_must_be_zero",level:3},{value:"imported_event_timestamp_out_of_range",id:"imported_event_timestamp_out_of_range",level:3},{value:"imported_event_timestamp_must_not_advance",id:"imported_event_timestamp_must_not_advance",level:3},{value:"reserved_flag",id:"reserved_flag",level:3},{value:"id_must_not_be_zero",id:"id_must_not_be_zero",level:3},{value:"id_must_not_be_int_max",id:"id_must_not_be_int_max",level:3},{value:"flags_are_mutually_exclusive",id:"flags_are_mutually_exclusive",level:3},{value:"debit_account_id_must_not_be_zero",id:"debit_account_id_must_not_be_zero",level:3},{value:"debit_account_id_must_not_be_int_max",id:"debit_account_id_must_not_be_int_max",level:3},{value:"credit_account_id_must_not_be_zero",id:"credit_account_id_must_not_be_zero",level:3},{value:"credit_account_id_must_not_be_int_max",id:"credit_account_id_must_not_be_int_max",level:3},{value:"accounts_must_be_different",id:"accounts_must_be_different",level:3},{value:"pending_id_must_be_zero",id:"pending_id_must_be_zero",level:3},{value:"pending_id_must_not_be_zero",id:"pending_id_must_not_be_zero",level:3},{value:"pending_id_must_not_be_int_max",id:"pending_id_must_not_be_int_max",level:3},{value:"pending_id_must_be_different",id:"pending_id_must_be_different",level:3},{value:"timeout_reserved_for_pending_transfer",id:"timeout_reserved_for_pending_transfer",level:3},{value:"closing_transfer_must_be_pending",id:"closing_transfer_must_be_pending",level:3},{value:"amount_must_not_be_zero",id:"amount_must_not_be_zero",level:3},{value:"ledger_must_not_be_zero",id:"ledger_must_not_be_zero",level:3},{value:"code_must_not_be_zero",id:"code_must_not_be_zero",level:3},{value:"debit_account_not_found",id:"debit_account_not_found",level:3},{value:"credit_account_not_found",id:"credit_account_not_found",level:3},{value:"accounts_must_have_the_same_ledger",id:"accounts_must_have_the_same_ledger",level:3},{value:"transfer_must_have_the_same_ledger_as_accounts",id:"transfer_must_have_the_same_ledger_as_accounts",level:3},{value:"pending_transfer_not_found",id:"pending_transfer_not_found",level:3},{value:"pending_transfer_not_pending",id:"pending_transfer_not_pending",level:3},{value:"pending_transfer_has_different_debit_account_id",id:"pending_transfer_has_different_debit_account_id",level:3},{value:"pending_transfer_has_different_credit_account_id",id:"pending_transfer_has_different_credit_account_id",level:3},{value:"pending_transfer_has_different_ledger",id:"pending_transfer_has_different_ledger",level:3},{value:"pending_transfer_has_different_code",id:"pending_transfer_has_different_code",level:3},{value:"exceeds_pending_transfer_amount",id:"exceeds_pending_transfer_amount",level:3},{value:"pending_transfer_has_different_amount",id:"pending_transfer_has_different_amount",level:3},{value:"pending_transfer_already_posted",id:"pending_transfer_already_posted",level:3},{value:"pending_transfer_already_voided",id:"pending_transfer_already_voided",level:3},{value:"pending_transfer_expired",id:"pending_transfer_expired",level:3},{value:"exists_with_different_flags",id:"exists_with_different_flags",level:3},{value:"exists_with_different_debit_account_id",id:"exists_with_different_debit_account_id",level:3},{value:"exists_with_different_credit_account_id",id:"exists_with_different_credit_account_id",level:3},{value:"exists_with_different_amount",id:"exists_with_different_amount",level:3},{value:"exists_with_different_pending_id",id:"exists_with_different_pending_id",level:3},{value:"exists_with_different_user_data_128",id:"exists_with_different_user_data_128",level:3},{value:"exists_with_different_user_data_64",id:"exists_with_different_user_data_64",level:3},{value:"exists_with_different_user_data_32",id:"exists_with_different_user_data_32",level:3},{value:"exists_with_different_timeout",id:"exists_with_different_timeout",level:3},{value:"exists_with_different_code",id:"exists_with_different_code",level:3},{value:"exists",id:"exists",level:3},{value:"imported_event_timestamp_must_not_regress",id:"imported_event_timestamp_must_not_regress",level:3},{value:"imported_event_timestamp_must_postdate_debit_account",id:"imported_event_timestamp_must_postdate_debit_account",level:3},{value:"imported_event_timestamp_must_postdate_credit_account",id:"imported_event_timestamp_must_postdate_credit_account",level:3},{value:"imported_event_timeout_must_be_zero",id:"imported_event_timeout_must_be_zero",level:3},{value:"debit_account_already_closed",id:"debit_account_already_closed",level:3},{value:"debit_account_already_closed",id:"debit_account_already_closed-1",level:3},{value:"overflows_debits_pending",id:"overflows_debits_pending",level:3},{value:"overflows_credits_pending",id:"overflows_credits_pending",level:3},{value:"overflows_debits_posted",id:"overflows_debits_posted",level:3},{value:"overflows_credits_posted",id:"overflows_credits_posted",level:3},{value:"overflows_debits",id:"overflows_debits",level:3},{value:"overflows_credits",id:"overflows_credits",level:3},{value:"overflows_timeout",id:"overflows_timeout",level:3},{value:"exceeds_credits",id:"exceeds_credits",level:3},{value:"exceeds_debits",id:"exceeds_debits",level:3},{value:"Client libraries",id:"client-libraries",level:2},{value:"Internals",id:"internals",level:2}],f={toc:o},p="wrapper";function _(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"create_transfers"},(0,r.kt)("inlineCode",{parentName:"h1"},"create_transfers")),(0,r.kt)("p",null,"Create one or more ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer")),"s. A successfully created transfer will modify the\namount fields of its ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},"debit")," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},"credit")," accounts."),(0,r.kt)("h2",{id:"event"},"Event"),(0,r.kt)("p",null,"The transfer to create. See ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer"))," for constraints."),(0,r.kt)("h2",{id:"result"},"Result"),(0,r.kt)("p",null,"Results are listed in this section in order of descending precedence \u2014 that is, if more than one\nerror is applicable to the transfer being created, only the result listed first is returned."),(0,r.kt)("h3",{id:"ok"},(0,r.kt)("inlineCode",{parentName:"h3"},"ok")),(0,r.kt)("p",null,"The transfer was successfully created; did not previously exist."),(0,r.kt)("p",null,"Note that ",(0,r.kt)("inlineCode",{parentName:"p"},"ok")," is generated by the client implementation; the network protocol does not include a\nresult when the transfer was successfully created."),(0,r.kt)("h3",{id:"linked_event_failed"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_failed")),(0,r.kt)("p",null,"The transfer was not created. One or more of the other transfers in the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},"linked chain")," is invalid, so the whole chain failed."),(0,r.kt)("h3",{id:"linked_event_chain_open"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_chain_open")),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.linked"))," flag was set\non the last event in the batch, which is not legal. (",(0,r.kt)("inlineCode",{parentName:"p"},"flags.linked")," indicates that the chain\ncontinues to the next operation)."),(0,r.kt)("h3",{id:"imported_event_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_expected")),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," was\nset on the first transfer of the batch, but not all transfers in the batch.\nBatches cannot mix imported transfers with non-imported transfers."),(0,r.kt)("h3",{id:"imported_event_not_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_not_expected")),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," was\nexpected to ",(0,r.kt)("em",{parentName:"p"},"not")," be set, as it's not allowed to mix transfers with different ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag\nin the same batch. The first transfer determines the entire operation."),(0,r.kt)("h3",{id:"timestamp_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"timestamp_must_be_zero")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is ",(0,r.kt)("em",{parentName:"p"},"not")," set."),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," is nonzero, but\nmust be zero. The cluster is responsible for setting this field."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," can only be assigned when creating transfers\nwith ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," set."),(0,r.kt)("h3",{id:"imported_event_timestamp_out_of_range"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_out_of_range")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," is out of range,\nbut must be a user-defined timestamp greater than ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and less than ",(0,r.kt)("inlineCode",{parentName:"p"},"2^63"),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_advance"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_advance")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," is\ngreater than the current ",(0,r.kt)("a",{parentName:"p",href:"/coding/time"},"cluster time"),", but it must be a past timestamp."),(0,r.kt)("h3",{id:"reserved_flag"},(0,r.kt)("inlineCode",{parentName:"h3"},"reserved_flag")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"Transfer.flags.reserved")," is nonzero, but must be zero."),(0,r.kt)("h3",{id:"id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.id"))," is zero, which is a reserved value."),(0,r.kt)("h3",{id:"id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.id"))," is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", which is a reserved\nvalue."),(0,r.kt)("h3",{id:"flags_are_mutually_exclusive"},(0,r.kt)("inlineCode",{parentName:"h3"},"flags_are_mutually_exclusive")),(0,r.kt)("p",null,"The transfer was not created. An account cannot be created with the specified combination of\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags")),"."),(0,r.kt)("p",null,"Flag compatibility (\u2713 = compatible, \u2717 = mutually exclusive):"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2717 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.imported")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_debit"))),(0,r.kt)("li",{parentName:"ul"},"\u2713 ",(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.closing_credit")))))),(0,r.kt)("h3",{id:"debit_account_id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," is\nzero, but must be a valid account id."),(0,r.kt)("h3",{id:"debit_account_id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," is\n",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", but must be a valid account id."),(0,r.kt)("h3",{id:"credit_account_id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"credit_account_id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," is\nzero, but must be a valid account id."),(0,r.kt)("h3",{id:"credit_account_id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"credit_account_id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," is\n",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", but must be a valid account id."),(0,r.kt)("h3",{id:"accounts_must_be_different"},(0,r.kt)("inlineCode",{parentName:"h3"},"accounts_must_be_different")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must not be equal."),(0,r.kt)("p",null,"That is, an account cannot transfer money to itself."),(0,r.kt)("h3",{id:"pending_id_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_be_zero")),(0,r.kt)("p",null,"The transfer was not created. Only post/void transfers can reference a pending transfer."),(0,r.kt)("p",null,"Either:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.post_pending_transfer"))," must be set,\nor"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.void_pending_transfer"))," must be set,\nor"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," must be zero.")),(0,r.kt)("h3",{id:"pending_id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created.\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.post_pending_transfer"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.void_pending_transfer"))," is set, but\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," is zero. A posting or voiding transfer must\nreference a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"pending"))," transfer."),(0,r.kt)("h3",{id:"pending_id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_not_be_int_max")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),",\nwhich is a reserved value."),(0,r.kt)("h3",{id:"pending_id_must_be_different"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_id_must_be_different")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," is set to the same\nid as ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.id")),". Instead it should refer to a different (existing)\ntransfer."),(0,r.kt)("h3",{id:"timeout_reserved_for_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"h3"},"timeout_reserved_for_pending_transfer")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timeout"))," is nonzero, but only\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},"pending")," transfers have nonzero timeouts."),(0,r.kt)("h3",{id:"closing_transfer_must_be_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"closing_transfer_must_be_pending")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.pending"))," is not set,\nbut closing transfers must be two-phase pending transfers."),(0,r.kt)("p",null,"If either ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsclosing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.closing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsclosing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.closing_credit"))," is set,\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.pending"))," must also be set."),(0,r.kt)("p",null,"This ensures that closing transfers are reversible by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},"voiding")," the pending transfer, and requires that the\nreversal operation references the corresponding closing transfer, guarding against unexpected\ninterleaving of close/unclose operations."),(0,r.kt)("h3",{id:"amount_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"amount_must_not_be_zero")),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"},"Deprecated"),": This error code is only returned to clients prior to release ",(0,r.kt)("inlineCode",{parentName:"p"},"0.16.0"),".\nSince ",(0,r.kt)("inlineCode",{parentName:"p"},"0.16.0"),", zero-amount transfers are permitted."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.amount"))," is zero, but must be\nnonzero."),(0,r.kt)("p",null,"Every transfer must move value. Only posting and voiding transfer amounts may be zero \u2014 when zero,\nthey will move the full pending amount.")),(0,r.kt)("h3",{id:"ledger_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"ledger_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.ledger"))," is zero, but must be\nnonzero."),(0,r.kt)("h3",{id:"code_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"code_must_not_be_zero")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.code"))," is zero, but must be nonzero."),(0,r.kt)("h3",{id:"debit_account_not_found"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_not_found")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," must\nrefer to an existing ",(0,r.kt)("inlineCode",{parentName:"p"},"Account"),"."),(0,r.kt)("h3",{id:"credit_account_not_found"},(0,r.kt)("inlineCode",{parentName:"h3"},"credit_account_not_found")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must\nrefer to an existing ",(0,r.kt)("inlineCode",{parentName:"p"},"Account"),"."),(0,r.kt)("h3",{id:"accounts_must_have_the_same_ledger"},(0,r.kt)("inlineCode",{parentName:"h3"},"accounts_must_have_the_same_ledger")),(0,r.kt)("p",null,"The transfer was not created. The accounts referred to by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must have an identical\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger")),"."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"/coding/recipes/currency-exchange"},"Currency exchange")," is implemented with multiple\ntransfers."),(0,r.kt)("h3",{id:"transfer_must_have_the_same_ledger_as_accounts"},(0,r.kt)("inlineCode",{parentName:"h3"},"transfer_must_have_the_same_ledger_as_accounts")),(0,r.kt)("p",null,"The transfer was not created. The accounts referred to by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," and\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," are equivalent, but differ from the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.ledger")),"."),(0,r.kt)("h3",{id:"pending_transfer_not_found"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_not_found")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," does not exist."),(0,r.kt)("h3",{id:"pending_transfer_not_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_not_pending")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but does not have\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))," set."),(0,r.kt)("h3",{id:"pending_transfer_has_different_debit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_debit_account_id")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"debit_account_id")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account_id")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending\ntransfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account_id"),"."),(0,r.kt)("h3",{id:"pending_transfer_has_different_credit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_credit_account_id")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"credit_account_id")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account_id")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending\ntransfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account_id"),"."),(0,r.kt)("h3",{id:"pending_transfer_has_different_ledger"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_ledger")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"ledger")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending transfer's\n",(0,r.kt)("inlineCode",{parentName:"p"},"ledger"),"."),(0,r.kt)("h3",{id:"pending_transfer_has_different_code"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_code")),(0,r.kt)("p",null,"The transfer was not created. The transfer referenced by\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.pending_id"))," exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,r.kt)("inlineCode",{parentName:"a"},"code")),"."),(0,r.kt)("p",null,"The post/void transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"code")," must either be ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or identical to the pending transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"code"),"."),(0,r.kt)("h3",{id:"exceeds_pending_transfer_amount"},(0,r.kt)("inlineCode",{parentName:"h3"},"exceeds_pending_transfer_amount")),(0,r.kt)("p",null,"The transfer was not created. The transfer's ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount"))," exceeds the ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),"\nof its ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer."),(0,r.kt)("h3",{id:"pending_transfer_has_different_amount"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_has_different_amount")),(0,r.kt)("p",null,"The transfer was not created. The transfer is attempting to\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},"void")," a pending transfer. The voiding transfer's\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount"))," must be either ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," or exactly the ",(0,r.kt)("inlineCode",{parentName:"p"},"amount")," of the pending\ntransfer."),(0,r.kt)("p",null,"To partially void a transfer, create a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},"posting transfer"),"\nwith an amount less than the pending transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),"."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"To partially void a transfer, create a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},"posting transfer"),"\nwith an amount between ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and the pending transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),".")),(0,r.kt)("h3",{id:"pending_transfer_already_posted"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_already_posted")),(0,r.kt)("p",null,"The transfer was not created. The referenced ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer was\nalready posted by a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"post_pending_transfer")),"."),(0,r.kt)("h3",{id:"pending_transfer_already_voided"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_already_voided")),(0,r.kt)("p",null,"The transfer was not created. The referenced ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer was\nalready voided by a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"void_pending_transfer")),"."),(0,r.kt)("h3",{id:"pending_transfer_expired"},(0,r.kt)("inlineCode",{parentName:"h3"},"pending_transfer_expired")),(0,r.kt)("p",null,"The transfer was not created. The referenced ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},"pending")," transfer was\nalready voided because its ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},"timeout")," has passed."),(0,r.kt)("h3",{id:"exists_with_different_flags"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_flags")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"flags")),"."),(0,r.kt)("h3",{id:"exists_with_different_debit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_debit_account_id")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"debit_account_id")),"."),(0,r.kt)("h3",{id:"exists_with_different_credit_account_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_credit_account_id")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"credit_account_id")),"."),(0,r.kt)("h3",{id:"exists_with_different_amount"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_amount")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),"."),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," set, then the actual amount\ntransferred exceeds this failed transfer's ",(0,r.kt)("inlineCode",{parentName:"p"},"amount"),"."),(0,r.kt)("h3",{id:"exists_with_different_pending_id"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_pending_id")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#pending_id"},(0,r.kt)("inlineCode",{parentName:"a"},"pending_id")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_128"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_128")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_128"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_128")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_64"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_64")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_64"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_64")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_32"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_32")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_32"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_32")),"."),(0,r.kt)("h3",{id:"exists_with_different_timeout"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_timeout")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"timeout")),"."),(0,r.kt)("h3",{id:"exists_with_different_code"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_code")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,r.kt)("inlineCode",{parentName:"a"},"code")),"."),(0,r.kt)("h3",{id:"exists"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists")),(0,r.kt)("p",null,"A transfer with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists."),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," set, then the existing\ntransfer may have a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),", limited to the maximum\n",(0,r.kt)("inlineCode",{parentName:"p"},"amount")," of the transfer in the request."),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspost_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer")),"\nset, then the existing transfer may have a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"If the original posted amount was less than the pending amount,\nthen the transfer amount must be equal to the posted amount."),(0,r.kt)("li",{parentName:"ul"},"Otherwise, the transfer amount must be greater than or equal to the pending amount.")),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"If the transfer has ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," set, then the existing\ntransfer may have a different ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#amount"},(0,r.kt)("inlineCode",{parentName:"a"},"amount")),", limited to the maximum\n",(0,r.kt)("inlineCode",{parentName:"p"},"amount")," of the transfer in the request.")),(0,r.kt)("p",null,"Otherwise, with the possible exception of the ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," field, the existing transfer is identical\nto the transfer in the request."),(0,r.kt)("p",null,"To correctly ",(0,r.kt)("a",{parentName:"p",href:"/coding/reliable-transaction-submission"},"recover from application crashes"),",\nmany applications should handle ",(0,r.kt)("inlineCode",{parentName:"p"},"exists")," exactly as ",(0,r.kt)("a",{parentName:"p",href:"#ok"},(0,r.kt)("inlineCode",{parentName:"a"},"ok")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_regress"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_regress")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp")),"\nregressed, but it must be greater than the last timestamp assigned to any ",(0,r.kt)("inlineCode",{parentName:"p"},"Transfer")," in the cluster and cannot be equal to the timestamp of any existing ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Account")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_postdate_debit_account"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_postdate_debit_account")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"timestamp"))," is less than the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_postdate_credit_account"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_postdate_credit_account")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"timestamp"))," is less than the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp")),"."),(0,r.kt)("h3",{id:"imported_event_timeout_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timeout_must_be_zero")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.flags.imported"))," is set."),(0,r.kt)("p",null,"The transfer was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timeout"))," is nonzero, but\nmust be zero."),(0,r.kt)("p",null,"It's possible to import ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagspending"},"pending")," transfers with a user-defined\ntimestamp, but since it's not driven by the cluster clock, it cannot define a timeout for\nautomatic expiration.\nIn those cases, the ",(0,r.kt)("a",{parentName:"p",href:"/coding/two-phase-transfers"},"two-phase post or rollback")," must be\ndone manually."),(0,r.kt)("h3",{id:"debit_account_already_closed"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_already_closed")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.debit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsclosed"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.closed"))," is not already set."),(0,r.kt)("h3",{id:"debit_account_already_closed-1"},(0,r.kt)("inlineCode",{parentName:"h3"},"debit_account_already_closed")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.credit_account_id"))," must\nrefer to an ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," whose ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsclosed"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.closed"))," is not already set."),(0,r.kt)("h3",{id:"overflows_debits_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_debits_pending")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_credits_pending"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_credits_pending")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_debits_posted"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_debits_posted")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_credits_posted"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_credits_posted")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.credits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_debits"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_debits")),(0,r.kt)("p",null,"The transfer was not created.\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + debit_account.debits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_credits"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_credits")),(0,r.kt)("p",null,"The transfer was not created.\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + credit_account.credits_posted + transfer.amount")," would overflow a\n128-bit unsigned integer."),(0,r.kt)("h3",{id:"overflows_timeout"},(0,r.kt)("inlineCode",{parentName:"h3"},"overflows_timeout")),(0,r.kt)("p",null,"The transfer was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer.timestamp + (transfer.timeout * 1_000_000_000)")," would\nexceed ",(0,r.kt)("inlineCode",{parentName:"p"},"2^63"),"."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timeout"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timeout"))," is converted to nanoseconds."),(0,r.kt)("p",null,"This computation uses the ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," value assigned by the\nreplica, not the ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," value sent by the client."),(0,r.kt)("h3",{id:"exceeds_credits"},(0,r.kt)("inlineCode",{parentName:"h3"},"exceeds_credits")),(0,r.kt)("p",null,"The transfer was not created."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},"debit account")," has\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsdebits_must_not_exceed_credits"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.debits_must_not_exceed_credits"))," set, but\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + debit_account.debits_posted + transfer.amount")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.credits_posted"),"."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"If ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," is set, then\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.debits_pending + debit_account.debits_posted + 1")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"debit_account.credits_posted"),".")),(0,r.kt)("h3",{id:"exceeds_debits"},(0,r.kt)("inlineCode",{parentName:"h3"},"exceeds_debits")),(0,r.kt)("p",null,"The transfer was not created."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},"credit account")," has\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagscredits_must_not_exceed_debits"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.credits_must_not_exceed_debits"))," set, but\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + credit_account.credits_posted + transfer.amount")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.debits_posted"),"."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Client release < 0.16.0"),(0,r.kt)("p",null,"If ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," is set, then\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.credits_pending + credit_account.credits_posted + 1")," would exceed\n",(0,r.kt)("inlineCode",{parentName:"p"},"credit_account.debits_posted"),".")),(0,r.kt)("h2",{id:"client-libraries"},"Client libraries"),(0,r.kt)("p",null,"For language-specific docs see:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/dotnet#create-transfers"},".NET library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/java#create-transfers"},"Java library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/go#create-transfers"},"Go library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/node#create-transfers"},"Node.js library"))),(0,r.kt)("h2",{id:"internals"},"Internals"),(0,r.kt)("p",null,"If you're curious and want to learn more, you can find the source code for creating a transfer in\n",(0,r.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/state_machine.zig"},"src/state_machine.zig"),".\nSearch for ",(0,r.kt)("inlineCode",{parentName:"p"},"fn create_transfer(")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fn execute("),"."))}_.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8388e350.63e0b00a.js b/assets/js/8388e350.63e0b00a.js new file mode 100644 index 00000000..70539885 --- /dev/null +++ b/assets/js/8388e350.63e0b00a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6641],{3905:(e,t,a)=>{a.d(t,{Zo:()=>m,kt:()=>h});var n=a(7294);function l(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(l[a]=e[a]);return l}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(l[a]=e[a])}return l}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},m=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var a=e.components,l=e.mdxType,i=e.originalType,s=e.parentName,m=r(e,["components","mdxType","originalType","parentName"]),c=p(a),d=l,h=c["".concat(s,".").concat(d)]||c[d]||u[d]||i;return a?n.createElement(h,o(o({ref:t},m),{},{components:a})):n.createElement(h,o({ref:t},m))}));function h(e,t){var a=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var i=a.length,o=new Array(i);o[0]=d;var r={};for(var s in t)hasOwnProperty.call(t,s)&&(r[s]=t[s]);r.originalType=e,r[c]="string"==typeof e?e:l,o[1]=r;for(var p=2;p{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>r,toc:()=>p});var n=a(7462),l=(a(7294),a(3905));const i={sidebar_position:3},o="LSM",r={unversionedId:"about/internals/lsm",id:"about/internals/lsm",title:"LSM",description:"Documentation for (roughly) code in the src/lsm directory.",source:"@site/pages/about/internals/lsm.md",sourceDirName:"about/internals",slug:"/about/internals/lsm",permalink:"/about/internals/lsm",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/lsm.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Data File",permalink:"/about/internals/data_file"},next:{title:"State Sync",permalink:"/about/internals/sync"}},s={},p=[{value:"Glossary",id:"glossary",level:2},{value:"Tree",id:"tree",level:2},{value:"Tables",id:"tables",level:3},{value:"Compaction",id:"compaction",level:3},{value:"Compaction Selection Policy",id:"compaction-selection-policy",level:4},{value:"Compaction Move Table",id:"compaction-move-table",level:5},{value:"Compaction Table Overlap",id:"compaction-table-overlap",level:5},{value:"Snapshots",id:"snapshots",level:3},{value:"Snapshots and Compaction",id:"snapshots-and-compaction",level:4},{value:"Snapshot Queries",id:"snapshot-queries",level:4},{value:"Persistent Snapshots",id:"persistent-snapshots",level:5},{value:"Snapshot Values",id:"snapshot-values",level:4},{value:"Manifest",id:"manifest",level:3},{value:"Manifest Log",id:"manifest-log",level:4},{value:"Manifest Level",id:"manifest-level",level:4},{value:"Example",id:"example",level:5}],m={toc:p},c="wrapper";function u(e){let{components:t,...a}=e;return(0,l.kt)(c,(0,n.Z)({},m,a,{components:t,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"lsm"},"LSM"),(0,l.kt)("p",null,"Documentation for (roughly) code in the ",(0,l.kt)("inlineCode",{parentName:"p"},"src/lsm")," directory."),(0,l.kt)("h2",{id:"glossary"},"Glossary"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"bar"),": ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_compaction_ops")," beats; unit of incremental compaction."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"beat"),": ",(0,l.kt)("inlineCode",{parentName:"li"},"op % lsm_compaction_ops"),"; Single step of an incremental compaction."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"groove"),": A collection of LSM trees, storing objects and their indices."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"immutable table"),": In-memory table; one per tree. Used to periodically flush the mutable table to\ndisk."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"level"),": A collection of on-disk tables, numbering between ",(0,l.kt)("inlineCode",{parentName:"li"},"0")," and ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_levels - 1")," (usually ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_levels = 7"),")."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"forest"),": A collection of grooves."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"manifest"),": Index of table and level metadata; one per tree."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"mutable table"),": In-memory table; one per tree. All tree updates (e.g. ",(0,l.kt)("inlineCode",{parentName:"li"},"Tree.put"),") directly modify just this table."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"snapshot"),": Sequence number which selects the queryable partition of on-disk tables.")),(0,l.kt)("h2",{id:"tree"},"Tree"),(0,l.kt)("h3",{id:"tables"},"Tables"),(0,l.kt)("p",null,"A tree is a hierarchy of in-memory and on-disk tables. There are three categories of tables:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/table_memory.zig"},"mutable table")," is an in-memory table.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Each tree has a single mutable table."),(0,l.kt)("li",{parentName:"ul"},"All tree updates, inserts, and removes are applied to the mutable table."),(0,l.kt)("li",{parentName:"ul"},"The mutable table's size is allocated to accommodate a full bar of updates."))),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/table_memory.zig"},"immutable table")," is an in-memory table.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Each tree has a single immutable table."),(0,l.kt)("li",{parentName:"ul"},"The mutable table's contents are periodically moved to the immutable table,\nwhere they are stored while being flushed to level ",(0,l.kt)("inlineCode",{parentName:"li"},"0"),"."))),(0,l.kt)("li",{parentName:"ul"},"Level ",(0,l.kt)("inlineCode",{parentName:"li"},"0")," \u2026 level ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_levels - 1")," each contain an exponentially increasing number of\nimmutable on-disk tables.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Each tree has as many as ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_growth_factor ^ (level + 1)")," tables per level.\n(",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_growth_factor")," is typically 8)."),(0,l.kt)("li",{parentName:"ul"},"Within a given level and snapshot, the tables' key ranges are ",(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/manifest_level.zig"},"disjoint"),".")))),(0,l.kt)("h3",{id:"compaction"},"Compaction"),(0,l.kt)("p",null,"Tree compaction runs to the sound of music!"),(0,l.kt)("p",null,"Compacting LSM trees involves merging and moving tables into the next levels as needed.\nTo avoid write amplification stalls and bound latency, compaction is done incrementally."),(0,l.kt)("p",null,"A full compaction phase is denoted as a bar, using terms from music notation.\nEach bar consists of ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_compaction_ops"),' beats or "compaction ticks" of work.\nA compaction tick executes asynchronously immediately after every commit, with\n',(0,l.kt)("inlineCode",{parentName:"p"},"beat = commit.op % lsm_compaction_ops"),"."),(0,l.kt)("p",null,'A bar is split in half according to the "first" beat and "middle" beat.\nThe first half of the bar compacts even levels while the latter compacts odd levels.\nMutable table changes are sorted and compacted into the immutable table.\nThe immutable table is compacted into level 0 during the odd level half of the bar.'),(0,l.kt)("p",null,"At any given point, there are at most ",(0,l.kt)("inlineCode",{parentName:"p"},"\u2308levels/2\u2309")," compactions running concurrently.\nThe source level is denoted as ",(0,l.kt)("inlineCode",{parentName:"p"},"level_a")," and the target level as ",(0,l.kt)("inlineCode",{parentName:"p"},"level_b"),".\nThe last level in the LSM tree has no target level so it is never a source level.\nEach compaction compacts a ",(0,l.kt)("a",{parentName:"p",href:"#compaction-selection-policy"},"single table")," from ",(0,l.kt)("inlineCode",{parentName:"p"},"level_a")," into all tables in\n",(0,l.kt)("inlineCode",{parentName:"p"},"level_b")," which intersect the ",(0,l.kt)("inlineCode",{parentName:"p"},"level_a")," table's key range."),(0,l.kt)("p",null,"Invariants:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"At the end of every beat, there is space in mutable table for the next beat."),(0,l.kt)("li",{parentName:"ul"},"The manifest log is compacted during every half-bar."),(0,l.kt)("li",{parentName:"ul"},"The compactions' output tables are not ",(0,l.kt)("a",{parentName:"li",href:"#snapshots-and-compaction"},"visible")," until the compaction has finished.")),(0,l.kt)("ol",null,(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},'First half-bar, first beat ("first beat"):'),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Assert no compactions are currently running."),(0,l.kt)("li",{parentName:"ul"},"Allow the per-level table limits to overflow if needed (for example, if we may compact a table\nfrom level ",(0,l.kt)("inlineCode",{parentName:"li"},"A")," to level ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),", where level ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," is already full)."),(0,l.kt)("li",{parentName:"ul"},"Start compactions from even levels that have reached their table limit."),(0,l.kt)("li",{parentName:"ul"},"Acquire reservations from the Free Set for all blocks (upper-bound) that will be written\nduring this half-bar."))),(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},"First half-bar, last beat:"),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Finish ticking any incomplete even-level compactions."),(0,l.kt)("li",{parentName:"ul"},"Assert on callback completion that all compactions are complete."),(0,l.kt)("li",{parentName:"ul"},"Release reservations from the Free Set."))),(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},'Second half-bar, first beat ("middle beat"):'),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Assert no compactions are currently running."),(0,l.kt)("li",{parentName:"ul"},"Start compactions from odd levels that have reached their table limit."),(0,l.kt)("li",{parentName:"ul"},"Compact the immutable table if it contains any sorted values (it might be empty)."),(0,l.kt)("li",{parentName:"ul"},"Acquire reservations from the Free Set for all blocks (upper-bound) that will be written\nduring this half-bar."))),(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},"Second half-bar, last beat:"),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Finish ticking any incomplete odd-level and immutable table compactions."),(0,l.kt)("li",{parentName:"ul"},"Assert on callback completion that all compactions are complete."),(0,l.kt)("li",{parentName:"ul"},"Assert on callback completion that no level's table count overflows."),(0,l.kt)("li",{parentName:"ul"},"Flush, clear, and sort mutable table values into immutable table for next bar."),(0,l.kt)("li",{parentName:"ul"},"Remove input tables that are invisible to all current and persisted snapshots."),(0,l.kt)("li",{parentName:"ul"},"Release reservations from the Free Set.")))),(0,l.kt)("h4",{id:"compaction-selection-policy"},"Compaction Selection Policy"),(0,l.kt)("p",null,"Compaction selects the table from level ",(0,l.kt)("inlineCode",{parentName:"p"},"A")," which overlaps the fewest visible tables of level ",(0,l.kt)("inlineCode",{parentName:"p"},"B"),"."),(0,l.kt)("p",null,"For example, in the following table (with ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_growth_factor=2"),"), each table is depicted as the range of keys it includes. The tables with uppercase letters would be chosen for compaction next."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"Level 0 A\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500H l\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500z\nLevel 1 a\u2500\u2500\u2500\u2500\u2500\u2500\u2500e L\u2500M o\u2500\u2500\u2500\u2500\u2500\u2500\u2500s u\u2500\u2500\u2500\u2500\u2500\u2500\u2500y\nLevel 2 b\u2500\u2500\u2500d e\u2500\u2500\u2500\u2500\u2500h i\u2500\u2500\u2500k l\u2500\u2500\u2500n o\u2500p q\u2500\u2500\u2500s u\u2500v w\u2500\u2500\u2500\u2500\u2500z\n(Keys) a b c d e f g h i j k l m n o p q r s t u v w x y z\n")),(0,l.kt)("p",null,"Links:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/manifest.zig"},(0,l.kt)("inlineCode",{parentName:"a"},"Manifest.compaction_table"))),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("a",{parentName:"li",href:"http://vldb.org/pvldb/vol14/p2216-sarkar.pdf"},"Constructing and Analyzing the LSM Compaction Design Space"),' describes the tradeoffs of various data movement policies. TigerBeetle implements the "least overlapping with parent" policy.'),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("a",{parentName:"li",href:"https://rocksdb.org/blog/2016/01/29/compaction_pri.html"},"Option of Compaction Priority"))),(0,l.kt)("h5",{id:"compaction-move-table"},"Compaction Move Table"),(0,l.kt)("p",null,"When the ",(0,l.kt)("a",{parentName:"p",href:"#compaction-selection-policy"},"selected input table")," from level ",(0,l.kt)("inlineCode",{parentName:"p"},"A")," does not overlap ",(0,l.kt)("em",{parentName:"p"},"any"),"\ninput tables in level ",(0,l.kt)("inlineCode",{parentName:"p"},"B"),', the input table can be "moved" to level ',(0,l.kt)("inlineCode",{parentName:"p"},"B"),".\nThat is, instead of sort-merging ",(0,l.kt)("inlineCode",{parentName:"p"},"A")," and ",(0,l.kt)("inlineCode",{parentName:"p"},"B"),", just update the input table's metadata in the manifest."),(0,l.kt)("p",null,"This is referred to as the ",(0,l.kt)("em",{parentName:"p"},"move table")," optimization."),(0,l.kt)("p",null,"Where a tree performs inserts mostly in sort order, with a minimum of updates, this ",(0,l.kt)("em",{parentName:"p"},"move table"),"\noptimization should enable the tree's performance to approach that of an append-only log."),(0,l.kt)("h5",{id:"compaction-table-overlap"},"Compaction Table Overlap"),(0,l.kt)("p",null,"Applying ",(0,l.kt)("a",{parentName:"p",href:"#compaction-selection-policy"},"this"),' selection policy while compacting a table\nfrom level A to level B, what is the maximum number of level-B tables that may overlap with the\nselected level-A table (i.e. the "worst case")?'),(0,l.kt)("p",null,"Perhaps surprisingly, this is ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_growth_factor"),":"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Tables within a level are disjoint."),(0,l.kt)("li",{parentName:"ul"},"Level ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," has at most ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_growth_factor")," times as many tables as level ",(0,l.kt)("inlineCode",{parentName:"li"},"A"),"."),(0,l.kt)("li",{parentName:"ul"},"To trigger compaction, level ",(0,l.kt)("inlineCode",{parentName:"li"},"A"),"'s visible-table count exceeds\n",(0,l.kt)("inlineCode",{parentName:"li"},"table_count_max_for_level(lsm_growth_factor, level_a)"),"."),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("a",{parentName:"li",href:"#compaction-selection-policy"},"selection policy")," chooses the table from level ",(0,l.kt)("inlineCode",{parentName:"li"},"A"),"\nwhich overlaps the fewest visible tables in level ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),"."),(0,l.kt)("li",{parentName:"ul"},"If any table in level ",(0,l.kt)("inlineCode",{parentName:"li"},"A")," overlaps ",(0,l.kt)("em",{parentName:"li"},"more than")," ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_growth_factor")," tables in level ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),",\nthat implies the existence of a table in level ",(0,l.kt)("inlineCode",{parentName:"li"},"A")," with ",(0,l.kt)("em",{parentName:"li"},"less than")," ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_growth_factor")," overlap.\nThe latter table would be selected over the former.")),(0,l.kt)("h3",{id:"snapshots"},"Snapshots"),(0,l.kt)("p",null,"Each table has a minimum and maximum integer snapshot (",(0,l.kt)("inlineCode",{parentName:"p"},"snapshot_min")," and ",(0,l.kt)("inlineCode",{parentName:"p"},"snapshot_max"),")."),(0,l.kt)("p",null,"Each query targets a particular snapshot. A table ",(0,l.kt)("inlineCode",{parentName:"p"},"T")," is ",(0,l.kt)("em",{parentName:"p"},"visible")," to a snapshot ",(0,l.kt)("inlineCode",{parentName:"p"},"S")," when"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"T.snapshot_min \u2264 S \u2264 T.snapshot_max\n")),(0,l.kt)("p",null,"and is ",(0,l.kt)("em",{parentName:"p"},"invisible")," to the snapshot otherwise."),(0,l.kt)("p",null,"Compaction does not modify tables in place \u2014 it copies data. Snapshots control and distinguish\nwhich copies are useful, and which can be deleted. Snapshots can also be persisted, enabling\nqueries against past states of the tree (unimplemented; future work)."),(0,l.kt)("h4",{id:"snapshots-and-compaction"},"Snapshots and Compaction"),(0,l.kt)("p",null,"Consider the half-bar compaction beginning at op=",(0,l.kt)("inlineCode",{parentName:"p"},"X")," (",(0,l.kt)("inlineCode",{parentName:"p"},"12"),"), with ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_compaction_ops=M")," (",(0,l.kt)("inlineCode",{parentName:"p"},"8"),").\nEach half-bar contains ",(0,l.kt)("inlineCode",{parentName:"p"},"N=M/2")," (",(0,l.kt)("inlineCode",{parentName:"p"},"4"),") beats. The next half-bar begins at ",(0,l.kt)("inlineCode",{parentName:"p"},"Y=X+N")," (",(0,l.kt)("inlineCode",{parentName:"p"},"16"),")."),(0,l.kt)("p",null,"During the half-bar compaction ",(0,l.kt)("inlineCode",{parentName:"p"},"X"),":"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_max")," of each input table is truncated to ",(0,l.kt)("inlineCode",{parentName:"li"},"Y-1")," (",(0,l.kt)("inlineCode",{parentName:"li"},"15"),")."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_min")," of each output table is initialized to ",(0,l.kt)("inlineCode",{parentName:"li"},"Y")," (",(0,l.kt)("inlineCode",{parentName:"li"},"16"),")."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_max")," of each output table is initialized to ",(0,l.kt)("inlineCode",{parentName:"li"},"\u221e"),".")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"0 4 8 12 16 20 24 (op, snapshot)\n\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\n ####\n\xb7\xb7\xb7\xb7\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500X\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\xb7\xb7\xb7\xb7 (input tables, before compaction)\n\xb7\xb7\xb7\xb7\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 (input tables, after compaction)\n Y\u2500\u2500\u2500\u2500\xb7\xb7\xb7\xb7 (output tables, after compaction)\n")),(0,l.kt)("p",null,"Beginning from the next op after the compaction (",(0,l.kt)("inlineCode",{parentName:"p"},"Y"),"; ",(0,l.kt)("inlineCode",{parentName:"p"},"16"),"):"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The output tables of the above compaction ",(0,l.kt)("inlineCode",{parentName:"li"},"X")," are visible."),(0,l.kt)("li",{parentName:"ul"},"The input tables of the above compaction ",(0,l.kt)("inlineCode",{parentName:"li"},"X")," are invisible."),(0,l.kt)("li",{parentName:"ul"},"Therefore, it will lookup from the output tables, but ignore the input tables."),(0,l.kt)("li",{parentName:"ul"},"Callers must not query from the output tables of ",(0,l.kt)("inlineCode",{parentName:"li"},"X")," before the compaction half-bar has finished\n(i.e. before the end of beat ",(0,l.kt)("inlineCode",{parentName:"li"},"Y-1")," (",(0,l.kt)("inlineCode",{parentName:"li"},"15"),")), since those tables are incomplete.")),(0,l.kt)("p",null,"At this point the input tables can be removed if they are invisible to all persistent snapshots."),(0,l.kt)("h4",{id:"snapshot-queries"},"Snapshot Queries"),(0,l.kt)("p",null,"Each query targets a particular snapshot, either:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"the current snapshot (",(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_latest"),"), or"),(0,l.kt)("li",{parentName:"ul"},"a ",(0,l.kt)("a",{parentName:"li",href:"#persistent-snapshots"},"persisted snapshot"),".")),(0,l.kt)("h5",{id:"persistent-snapshots"},"Persistent Snapshots"),(0,l.kt)("p",null,"TODO(Persistent Snapshots): Expand this section."),(0,l.kt)("h4",{id:"snapshot-values"},"Snapshot Values"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The on-disk tables visible to a snapshot ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," do not contain the updates from the commit with op ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),"."),(0,l.kt)("li",{parentName:"ul"},"Rather, snapshot ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," is first visible to a prefetch from the commit with op ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),".")),(0,l.kt)("p",null,"Consider the following diagram (",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_compaction_ops=8"),"):"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"0 4 8 12 16 20 24 28 (op, snapshot)\n\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\n ,,,,,,,,........\n \u2191A \u2191B \u2191C\n")),(0,l.kt)("p",null,"Compaction is driven by the commits of ops ",(0,l.kt)("inlineCode",{parentName:"p"},"B\u2192C")," (",(0,l.kt)("inlineCode",{parentName:"p"},"16\u202623"),"). While these ops are being committed:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"0\u2192A")," (",(0,l.kt)("inlineCode",{parentName:"li"},"0\u20267"),") are on-disk."),(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"A\u2192B")," (",(0,l.kt)("inlineCode",{parentName:"li"},"8\u202615"),") are in the immutable table.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"These updates were moved to the immutable table from the immutable table at the end of op ",(0,l.kt)("inlineCode",{parentName:"li"},"B-1"),"\n(",(0,l.kt)("inlineCode",{parentName:"li"},"15"),")."),(0,l.kt)("li",{parentName:"ul"},"These updates will exist in the immutable table until it is reset at the end of op ",(0,l.kt)("inlineCode",{parentName:"li"},"C-1")," (",(0,l.kt)("inlineCode",{parentName:"li"},"23"),")."))),(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"B\u2192C")," (",(0,l.kt)("inlineCode",{parentName:"li"},"16\u202623"),") are added to the mutable table (by the respective commit)."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tree.lookup_snapshot_max")," is ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," when committing op ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),"."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tree.lookup_snapshot_max")," is ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," when committing op ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," (for ",(0,l.kt)("inlineCode",{parentName:"li"},"x \u2208 {16,17,\u2026,23}"),").")),(0,l.kt)("p",null,"At the end of the last beat of the compaction bar (",(0,l.kt)("inlineCode",{parentName:"p"},"23"),"):"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"0\u2192B")," (",(0,l.kt)("inlineCode",{parentName:"li"},"0\u202615"),") are on disk."),(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"B\u2192C")," (",(0,l.kt)("inlineCode",{parentName:"li"},"16\u202623"),") are moved from the mutable table to the immutable table."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tree.lookup_snapshot_max")," is ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," when committing op ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," (for ",(0,l.kt)("inlineCode",{parentName:"li"},"x \u2208 {24,25,\u2026}"),").")),(0,l.kt)("h3",{id:"manifest"},"Manifest"),(0,l.kt)("p",null,"The manifest is a tree's index of table locations and metadata."),(0,l.kt)("p",null,"Each manifest has two components:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"a single ",(0,l.kt)("a",{parentName:"li",href:"#manifest-log"},(0,l.kt)("inlineCode",{parentName:"a"},"ManifestLog"))," shared by all trees and levels, and"),(0,l.kt)("li",{parentName:"ul"},"one ",(0,l.kt)("a",{parentName:"li",href:"#manifest-level"},(0,l.kt)("inlineCode",{parentName:"a"},"ManifestLevel"))," for each on-disk level.")),(0,l.kt)("h4",{id:"manifest-log"},"Manifest Log"),(0,l.kt)("p",null,"The manifest log is an on-disk log of all updates to the trees' table indexes."),(0,l.kt)("p",null,"The manifest log tracks:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"tables created as compaction output"),(0,l.kt)("li",{parentName:"ul"},"tables updated as compaction input (modifying their ",(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_max"),")"),(0,l.kt)("li",{parentName:"ul"},"tables moved between levels by compaction"),(0,l.kt)("li",{parentName:"ul"},"tables deleted after compaction")),(0,l.kt)("p",null,"Updates are accumulated in-memory before being flushed:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"incrementally during compaction, or"),(0,l.kt)("li",{parentName:"ul"},"in their entirety during checkpoint.")),(0,l.kt)("p",null,"The manifest log is periodically compacted to remove older entries that have been superseded by\nnewer entries. For example, if a table is created and later deleted, manifest log compaction\nwill eventually remove any reference to the table from the log blocks."),(0,l.kt)("p",null,'Each manifest block has a reference to the (chronologically) previous manifest block.\nThe superblock stores the head and tail address/checksum of this linked list.\nThe reference on the header of the head manifest block "dangles" \u2013 the block it references has already been compacted.'),(0,l.kt)("h4",{id:"manifest-level"},"Manifest Level"),(0,l.kt)("p",null,"A ",(0,l.kt)("inlineCode",{parentName:"p"},"ManifestLevel")," is an in-memory collection of the table metadata for a single level of a tree."),(0,l.kt)("p",null,"For a given level and snapshot, there may be gaps in the key ranges of the visible tables,\nbut the key ranges are disjoint."),(0,l.kt)("p",null,"Manifest levels are queried for tables at a target snapshot and within a key range."),(0,l.kt)("h5",{id:"example"},"Example"),(0,l.kt)("p",null,"Given the ",(0,l.kt)("inlineCode",{parentName:"p"},"ManifestLevel")," tables (with values chosen for visualization, not realism):"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"}," label A B C D E F G H I J K L M\n key_min 0 4 12 16 4 8 12 26 4 25 4 16 24\n key_max 3 11 15 19 7 11 15 27 7 27 11 19 27\nsnapshot_min 1 1 1 1 3 3 3 3 5 5 7 7 7\nsnapshot_max 9 3 3 7 5 7 9 5 7 7 9 9 9\n")),(0,l.kt)("p",null,"A level's tables can be visualized in 2D as a partitioned rectangle:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"}," 0 1 2\n 0 4 8 2 6 0 4 8\n9\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2510\n \u2502 \u2502 K \u2502 \u2502 L \u2502###\u2502 M \u2502\n7\u2502 \u251c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2524 \u251c\u2500\u2500\u2500\u2524###\u2514\u252c\u2500\u2500\u2524\n \u2502 \u2502 I \u2502 \u2502 G \u2502 \u2502####\u2502 J\u2502\n5\u2502 A \u251c\u2500\u2500\u2500\u2524 F \u2502 \u2502 \u2502####\u2514\u252c\u2500\u2524\n \u2502 \u2502 E \u2502 \u2502 \u2502 D \u2502#####\u2502H\u2502\n3\u2502 \u251c\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524 \u2502#####\u2514\u2500\u2524\n \u2502 \u2502 B \u2502 C \u2502 \u2502#######\u2502\n1\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n")),(0,l.kt)("p",null,"Example iterations:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"visibility snapshots direction key_min key_max tables\n visible 2 ascending 0 28 A, B, C, D\n visible 4 ascending 0 28 A, E, F, G, D, H\n visible 6 descending 12 28 J, D, G\n visible 8 ascending 0 28 A, K, G, L, M\n invisible 2, 4, 6 ascending 0 28 K, L, M\n")),(0,l.kt)("p",null,"Legend:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"#")," represents a gap \u2014 no tables cover these keys during the snapshot."),(0,l.kt)("li",{parentName:"ul"},"The horizontal axis represents the key range."),(0,l.kt)("li",{parentName:"ul"},"The vertical axis represents the snapshot range."),(0,l.kt)("li",{parentName:"ul"},"Each rectangle is a table within the manifest level."),(0,l.kt)("li",{parentName:"ul"},"The sides of each rectangle depict:",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"left: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_min")," (the diagram is inclusive, and the ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_min")," is inclusive)"),(0,l.kt)("li",{parentName:"ul"},"right: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_max")," (the diagram is EXCLUSIVE, but the ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_max")," is INCLUSIVE)"),(0,l.kt)("li",{parentName:"ul"},"bottom: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.snapshot_min")," (inclusive)"),(0,l.kt)("li",{parentName:"ul"},"top: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.snapshot_max")," (inclusive)"))),(0,l.kt)("li",{parentName:"ul"},"(Not depicted: tables may have ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_min == table.key_max"),".)"),(0,l.kt)("li",{parentName:"ul"},"(Not depicted: the newest set of tables would have ",(0,l.kt)("inlineCode",{parentName:"li"},"table.snapshot_max == maxInt(u64)"),".)")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8388e350.a1ec4c84.js b/assets/js/8388e350.a1ec4c84.js deleted file mode 100644 index 9c2ef7e0..00000000 --- a/assets/js/8388e350.a1ec4c84.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6641],{3905:(e,t,a)=>{a.d(t,{Zo:()=>m,kt:()=>h});var n=a(7294);function l(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(l[a]=e[a]);return l}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(l[a]=e[a])}return l}var p=n.createContext({}),s=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},m=function(e){var t=s(e.components);return n.createElement(p.Provider,{value:t},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var a=e.components,l=e.mdxType,i=e.originalType,p=e.parentName,m=r(e,["components","mdxType","originalType","parentName"]),c=s(a),d=l,h=c["".concat(p,".").concat(d)]||c[d]||u[d]||i;return a?n.createElement(h,o(o({ref:t},m),{},{components:a})):n.createElement(h,o({ref:t},m))}));function h(e,t){var a=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var i=a.length,o=new Array(i);o[0]=d;var r={};for(var p in t)hasOwnProperty.call(t,p)&&(r[p]=t[p]);r.originalType=e,r[c]="string"==typeof e?e:l,o[1]=r;for(var s=2;s{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>r,toc:()=>s});var n=a(7462),l=(a(7294),a(3905));const i={sidebar_position:3},o="LSM",r={unversionedId:"about/internals/lsm",id:"about/internals/lsm",title:"LSM",description:"Documentation for (roughly) code in the src/lsm directory.",source:"@site/pages/about/internals/lsm.md",sourceDirName:"about/internals",slug:"/about/internals/lsm",permalink:"/about/internals/lsm",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/lsm.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Data File",permalink:"/about/internals/data_file"},next:{title:"State Sync",permalink:"/about/internals/sync"}},p={},s=[{value:"Tables",id:"tables",level:2},{value:"Compaction",id:"compaction",level:2},{value:"Compaction Selection Policy",id:"compaction-selection-policy",level:3},{value:"Compaction Move Table",id:"compaction-move-table",level:4},{value:"Compaction Table Overlap",id:"compaction-table-overlap",level:4},{value:"Snapshots",id:"snapshots",level:2},{value:"Snapshots and Compaction",id:"snapshots-and-compaction",level:3},{value:"Snapshot Queries",id:"snapshot-queries",level:3},{value:"Persistent Snapshots",id:"persistent-snapshots",level:4},{value:"Snapshot Values",id:"snapshot-values",level:3},{value:"Manifest",id:"manifest",level:2},{value:"Manifest Log",id:"manifest-log",level:3},{value:"Manifest Level",id:"manifest-level",level:3},{value:"Example",id:"example",level:4}],m={toc:s},c="wrapper";function u(e){let{components:t,...a}=e;return(0,l.kt)(c,(0,n.Z)({},m,a,{components:t,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"lsm"},"LSM"),(0,l.kt)("p",null,"Documentation for (roughly) code in the ",(0,l.kt)("inlineCode",{parentName:"p"},"src/lsm")," directory."),(0,l.kt)("h1",{id:"glossary"},"Glossary"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"bar"),": ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_compaction_ops")," beats; unit of incremental compaction."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"beat"),": ",(0,l.kt)("inlineCode",{parentName:"li"},"op % lsm_compaction_ops"),"; Single step of an incremental compaction."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"groove"),": A collection of LSM trees, storing objects and their indices."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"immutable table"),": In-memory table; one per tree. Used to periodically flush the mutable table to\ndisk."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"level"),": A collection of on-disk tables, numbering between ",(0,l.kt)("inlineCode",{parentName:"li"},"0")," and ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_levels - 1")," (usually ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_levels = 7"),")."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"forest"),": A collection of grooves."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"manifest"),": Index of table and level metadata; one per tree."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"mutable table"),": In-memory table; one per tree. All tree updates (e.g. ",(0,l.kt)("inlineCode",{parentName:"li"},"Tree.put"),") directly modify just this table."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("em",{parentName:"li"},"snapshot"),": Sequence number which selects the queryable partition of on-disk tables.")),(0,l.kt)("h1",{id:"tree"},"Tree"),(0,l.kt)("h2",{id:"tables"},"Tables"),(0,l.kt)("p",null,"A tree is a hierarchy of in-memory and on-disk tables. There are three categories of tables:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/table_memory.zig"},"mutable table")," is an in-memory table.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Each tree has a single mutable table."),(0,l.kt)("li",{parentName:"ul"},"All tree updates, inserts, and removes are applied to the mutable table."),(0,l.kt)("li",{parentName:"ul"},"The mutable table's size is allocated to accommodate a full bar of updates."))),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/table_memory.zig"},"immutable table")," is an in-memory table.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Each tree has a single immutable table."),(0,l.kt)("li",{parentName:"ul"},"The mutable table's contents are periodically moved to the immutable table,\nwhere they are stored while being flushed to level ",(0,l.kt)("inlineCode",{parentName:"li"},"0"),"."))),(0,l.kt)("li",{parentName:"ul"},"Level ",(0,l.kt)("inlineCode",{parentName:"li"},"0")," \u2026 level ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_levels - 1")," each contain an exponentially increasing number of\nimmutable on-disk tables.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Each tree has as many as ",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_growth_factor ^ (level + 1)")," tables per level.\n(",(0,l.kt)("inlineCode",{parentName:"li"},"config.lsm_growth_factor")," is typically 8)."),(0,l.kt)("li",{parentName:"ul"},"Within a given level and snapshot, the tables' key ranges are ",(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/manifest_level.zig"},"disjoint"),".")))),(0,l.kt)("h2",{id:"compaction"},"Compaction"),(0,l.kt)("p",null,"Tree compaction runs to the sound of music!"),(0,l.kt)("p",null,"Compacting LSM trees involves merging and moving tables into the next levels as needed.\nTo avoid write amplification stalls and bound latency, compaction is done incrementally."),(0,l.kt)("p",null,"A full compaction phase is denoted as a bar, using terms from music notation.\nEach bar consists of ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_compaction_ops"),' beats or "compaction ticks" of work.\nA compaction tick executes asynchronously immediately after every commit, with\n',(0,l.kt)("inlineCode",{parentName:"p"},"beat = commit.op % lsm_compaction_ops"),"."),(0,l.kt)("p",null,'A bar is split in half according to the "first" beat and "middle" beat.\nThe first half of the bar compacts even levels while the latter compacts odd levels.\nMutable table changes are sorted and compacted into the immutable table.\nThe immutable table is compacted into level 0 during the odd level half of the bar.'),(0,l.kt)("p",null,"At any given point, there are at most ",(0,l.kt)("inlineCode",{parentName:"p"},"\u2308levels/2\u2309")," compactions running concurrently.\nThe source level is denoted as ",(0,l.kt)("inlineCode",{parentName:"p"},"level_a")," and the target level as ",(0,l.kt)("inlineCode",{parentName:"p"},"level_b"),".\nThe last level in the LSM tree has no target level so it is never a source level.\nEach compaction compacts a ",(0,l.kt)("a",{parentName:"p",href:"#compaction-selection-policy"},"single table")," from ",(0,l.kt)("inlineCode",{parentName:"p"},"level_a")," into all tables in\n",(0,l.kt)("inlineCode",{parentName:"p"},"level_b")," which intersect the ",(0,l.kt)("inlineCode",{parentName:"p"},"level_a")," table's key range."),(0,l.kt)("p",null,"Invariants:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"At the end of every beat, there is space in mutable table for the next beat."),(0,l.kt)("li",{parentName:"ul"},"The manifest log is compacted during every half-bar."),(0,l.kt)("li",{parentName:"ul"},"The compactions' output tables are not ",(0,l.kt)("a",{parentName:"li",href:"#snapshots-and-compaction"},"visible")," until the compaction has finished.")),(0,l.kt)("ol",null,(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},'First half-bar, first beat ("first beat"):'),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Assert no compactions are currently running."),(0,l.kt)("li",{parentName:"ul"},"Allow the per-level table limits to overflow if needed (for example, if we may compact a table\nfrom level ",(0,l.kt)("inlineCode",{parentName:"li"},"A")," to level ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),", where level ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," is already full)."),(0,l.kt)("li",{parentName:"ul"},"Start compactions from even levels that have reached their table limit."),(0,l.kt)("li",{parentName:"ul"},"Acquire reservations from the Free Set for all blocks (upper-bound) that will be written\nduring this half-bar."))),(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},"First half-bar, last beat:"),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Finish ticking any incomplete even-level compactions."),(0,l.kt)("li",{parentName:"ul"},"Assert on callback completion that all compactions are complete."),(0,l.kt)("li",{parentName:"ul"},"Release reservations from the Free Set."))),(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},'Second half-bar, first beat ("middle beat"):'),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Assert no compactions are currently running."),(0,l.kt)("li",{parentName:"ul"},"Start compactions from odd levels that have reached their table limit."),(0,l.kt)("li",{parentName:"ul"},"Compact the immutable table if it contains any sorted values (it might be empty)."),(0,l.kt)("li",{parentName:"ul"},"Acquire reservations from the Free Set for all blocks (upper-bound) that will be written\nduring this half-bar."))),(0,l.kt)("li",{parentName:"ol"},(0,l.kt)("p",{parentName:"li"},"Second half-bar, last beat:"),(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"Finish ticking any incomplete odd-level and immutable table compactions."),(0,l.kt)("li",{parentName:"ul"},"Assert on callback completion that all compactions are complete."),(0,l.kt)("li",{parentName:"ul"},"Assert on callback completion that no level's table count overflows."),(0,l.kt)("li",{parentName:"ul"},"Flush, clear, and sort mutable table values into immutable table for next bar."),(0,l.kt)("li",{parentName:"ul"},"Remove input tables that are invisible to all current and persisted snapshots."),(0,l.kt)("li",{parentName:"ul"},"Release reservations from the Free Set.")))),(0,l.kt)("h3",{id:"compaction-selection-policy"},"Compaction Selection Policy"),(0,l.kt)("p",null,"Compaction selects the table from level ",(0,l.kt)("inlineCode",{parentName:"p"},"A")," which overlaps the fewest visible tables of level ",(0,l.kt)("inlineCode",{parentName:"p"},"B"),"."),(0,l.kt)("p",null,"For example, in the following table (with ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_growth_factor=2"),"), each table is depicted as the range of keys it includes. The tables with uppercase letters would be chosen for compaction next."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"Level 0 A\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500H l\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500z\nLevel 1 a\u2500\u2500\u2500\u2500\u2500\u2500\u2500e L\u2500M o\u2500\u2500\u2500\u2500\u2500\u2500\u2500s u\u2500\u2500\u2500\u2500\u2500\u2500\u2500y\nLevel 2 b\u2500\u2500\u2500d e\u2500\u2500\u2500\u2500\u2500h i\u2500\u2500\u2500k l\u2500\u2500\u2500n o\u2500p q\u2500\u2500\u2500s u\u2500v w\u2500\u2500\u2500\u2500\u2500z\n(Keys) a b c d e f g h i j k l m n o p q r s t u v w x y z\n")),(0,l.kt)("p",null,"Links:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/lsm/manifest.zig"},(0,l.kt)("inlineCode",{parentName:"a"},"Manifest.compaction_table"))),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("a",{parentName:"li",href:"http://vldb.org/pvldb/vol14/p2216-sarkar.pdf"},"Constructing and Analyzing the LSM Compaction Design Space"),' describes the tradeoffs of various data movement policies. TigerBeetle implements the "least overlapping with parent" policy.'),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("a",{parentName:"li",href:"https://rocksdb.org/blog/2016/01/29/compaction_pri.html"},"Option of Compaction Priority"))),(0,l.kt)("h4",{id:"compaction-move-table"},"Compaction Move Table"),(0,l.kt)("p",null,"When the ",(0,l.kt)("a",{parentName:"p",href:"#compaction-selection-policy"},"selected input table")," from level ",(0,l.kt)("inlineCode",{parentName:"p"},"A")," does not overlap ",(0,l.kt)("em",{parentName:"p"},"any"),"\ninput tables in level ",(0,l.kt)("inlineCode",{parentName:"p"},"B"),', the input table can be "moved" to level ',(0,l.kt)("inlineCode",{parentName:"p"},"B"),".\nThat is, instead of sort-merging ",(0,l.kt)("inlineCode",{parentName:"p"},"A")," and ",(0,l.kt)("inlineCode",{parentName:"p"},"B"),", just update the input table's metadata in the manifest."),(0,l.kt)("p",null,"This is referred to as the ",(0,l.kt)("em",{parentName:"p"},"move table")," optimization."),(0,l.kt)("p",null,"Where a tree performs inserts mostly in sort order, with a minimum of updates, this ",(0,l.kt)("em",{parentName:"p"},"move table"),"\noptimization should enable the tree's performance to approach that of an append-only log."),(0,l.kt)("h4",{id:"compaction-table-overlap"},"Compaction Table Overlap"),(0,l.kt)("p",null,"Applying ",(0,l.kt)("a",{parentName:"p",href:"#compaction-selection-policy"},"this"),' selection policy while compacting a table\nfrom level A to level B, what is the maximum number of level-B tables that may overlap with the\nselected level-A table (i.e. the "worst case")?'),(0,l.kt)("p",null,"Perhaps surprisingly, this is ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_growth_factor"),":"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Tables within a level are disjoint."),(0,l.kt)("li",{parentName:"ul"},"Level ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," has at most ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_growth_factor")," times as many tables as level ",(0,l.kt)("inlineCode",{parentName:"li"},"A"),"."),(0,l.kt)("li",{parentName:"ul"},"To trigger compaction, level ",(0,l.kt)("inlineCode",{parentName:"li"},"A"),"'s visible-table count exceeds\n",(0,l.kt)("inlineCode",{parentName:"li"},"table_count_max_for_level(lsm_growth_factor, level_a)"),"."),(0,l.kt)("li",{parentName:"ul"},"The ",(0,l.kt)("a",{parentName:"li",href:"#compaction-selection-policy"},"selection policy")," chooses the table from level ",(0,l.kt)("inlineCode",{parentName:"li"},"A"),"\nwhich overlaps the fewest visible tables in level ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),"."),(0,l.kt)("li",{parentName:"ul"},"If any table in level ",(0,l.kt)("inlineCode",{parentName:"li"},"A")," overlaps ",(0,l.kt)("em",{parentName:"li"},"more than")," ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_growth_factor")," tables in level ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),",\nthat implies the existence of a table in level ",(0,l.kt)("inlineCode",{parentName:"li"},"A")," with ",(0,l.kt)("em",{parentName:"li"},"less than")," ",(0,l.kt)("inlineCode",{parentName:"li"},"lsm_growth_factor")," overlap.\nThe latter table would be selected over the former.")),(0,l.kt)("h2",{id:"snapshots"},"Snapshots"),(0,l.kt)("p",null,"Each table has a minimum and maximum integer snapshot (",(0,l.kt)("inlineCode",{parentName:"p"},"snapshot_min")," and ",(0,l.kt)("inlineCode",{parentName:"p"},"snapshot_max"),")."),(0,l.kt)("p",null,"Each query targets a particular snapshot. A table ",(0,l.kt)("inlineCode",{parentName:"p"},"T")," is ",(0,l.kt)("em",{parentName:"p"},"visible")," to a snapshot ",(0,l.kt)("inlineCode",{parentName:"p"},"S")," when"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"T.snapshot_min \u2264 S \u2264 T.snapshot_max\n")),(0,l.kt)("p",null,"and is ",(0,l.kt)("em",{parentName:"p"},"invisible")," to the snapshot otherwise."),(0,l.kt)("p",null,"Compaction does not modify tables in place \u2014 it copies data. Snapshots control and distinguish\nwhich copies are useful, and which can be deleted. Snapshots can also be persisted, enabling\nqueries against past states of the tree (unimplemented; future work)."),(0,l.kt)("h3",{id:"snapshots-and-compaction"},"Snapshots and Compaction"),(0,l.kt)("p",null,"Consider the half-bar compaction beginning at op=",(0,l.kt)("inlineCode",{parentName:"p"},"X")," (",(0,l.kt)("inlineCode",{parentName:"p"},"12"),"), with ",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_compaction_ops=M")," (",(0,l.kt)("inlineCode",{parentName:"p"},"8"),").\nEach half-bar contains ",(0,l.kt)("inlineCode",{parentName:"p"},"N=M/2")," (",(0,l.kt)("inlineCode",{parentName:"p"},"4"),") beats. The next half-bar begins at ",(0,l.kt)("inlineCode",{parentName:"p"},"Y=X+N")," (",(0,l.kt)("inlineCode",{parentName:"p"},"16"),")."),(0,l.kt)("p",null,"During the half-bar compaction ",(0,l.kt)("inlineCode",{parentName:"p"},"X"),":"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_max")," of each input table is truncated to ",(0,l.kt)("inlineCode",{parentName:"li"},"Y-1")," (",(0,l.kt)("inlineCode",{parentName:"li"},"15"),")."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_min")," of each output table is initialized to ",(0,l.kt)("inlineCode",{parentName:"li"},"Y")," (",(0,l.kt)("inlineCode",{parentName:"li"},"16"),")."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_max")," of each output table is initialized to ",(0,l.kt)("inlineCode",{parentName:"li"},"\u221e"),".")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"0 4 8 12 16 20 24 (op, snapshot)\n\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\n ####\n\xb7\xb7\xb7\xb7\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500X\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\xb7\xb7\xb7\xb7 (input tables, before compaction)\n\xb7\xb7\xb7\xb7\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 (input tables, after compaction)\n Y\u2500\u2500\u2500\u2500\xb7\xb7\xb7\xb7 (output tables, after compaction)\n")),(0,l.kt)("p",null,"Beginning from the next op after the compaction (",(0,l.kt)("inlineCode",{parentName:"p"},"Y"),"; ",(0,l.kt)("inlineCode",{parentName:"p"},"16"),"):"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The output tables of the above compaction ",(0,l.kt)("inlineCode",{parentName:"li"},"X")," are visible."),(0,l.kt)("li",{parentName:"ul"},"The input tables of the above compaction ",(0,l.kt)("inlineCode",{parentName:"li"},"X")," are invisible."),(0,l.kt)("li",{parentName:"ul"},"Therefore, it will lookup from the output tables, but ignore the input tables."),(0,l.kt)("li",{parentName:"ul"},"Callers must not query from the output tables of ",(0,l.kt)("inlineCode",{parentName:"li"},"X")," before the compaction half-bar has finished\n(i.e. before the end of beat ",(0,l.kt)("inlineCode",{parentName:"li"},"Y-1")," (",(0,l.kt)("inlineCode",{parentName:"li"},"15"),")), since those tables are incomplete.")),(0,l.kt)("p",null,"At this point the input tables can be removed if they are invisible to all persistent snapshots."),(0,l.kt)("h3",{id:"snapshot-queries"},"Snapshot Queries"),(0,l.kt)("p",null,"Each query targets a particular snapshot, either:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"the current snapshot (",(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_latest"),"), or"),(0,l.kt)("li",{parentName:"ul"},"a ",(0,l.kt)("a",{parentName:"li",href:"#persistent-snapshots"},"persisted snapshot"),".")),(0,l.kt)("h4",{id:"persistent-snapshots"},"Persistent Snapshots"),(0,l.kt)("p",null,"TODO(Persistent Snapshots): Expand this section."),(0,l.kt)("h3",{id:"snapshot-values"},"Snapshot Values"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"The on-disk tables visible to a snapshot ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," do not contain the updates from the commit with op ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),"."),(0,l.kt)("li",{parentName:"ul"},"Rather, snapshot ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," is first visible to a prefetch from the commit with op ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),".")),(0,l.kt)("p",null,"Consider the following diagram (",(0,l.kt)("inlineCode",{parentName:"p"},"lsm_compaction_ops=8"),"):"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"0 4 8 12 16 20 24 28 (op, snapshot)\n\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u252c\n ,,,,,,,,........\n \u2191A \u2191B \u2191C\n")),(0,l.kt)("p",null,"Compaction is driven by the commits of ops ",(0,l.kt)("inlineCode",{parentName:"p"},"B\u2192C")," (",(0,l.kt)("inlineCode",{parentName:"p"},"16\u202623"),"). While these ops are being committed:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"0\u2192A")," (",(0,l.kt)("inlineCode",{parentName:"li"},"0\u20267"),") are on-disk."),(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"A\u2192B")," (",(0,l.kt)("inlineCode",{parentName:"li"},"8\u202615"),") are in the immutable table.",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"These updates were moved to the immutable table from the immutable table at the end of op ",(0,l.kt)("inlineCode",{parentName:"li"},"B-1"),"\n(",(0,l.kt)("inlineCode",{parentName:"li"},"15"),")."),(0,l.kt)("li",{parentName:"ul"},"These updates will exist in the immutable table until it is reset at the end of op ",(0,l.kt)("inlineCode",{parentName:"li"},"C-1")," (",(0,l.kt)("inlineCode",{parentName:"li"},"23"),")."))),(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"B\u2192C")," (",(0,l.kt)("inlineCode",{parentName:"li"},"16\u202623"),") are added to the mutable table (by the respective commit)."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tree.lookup_snapshot_max")," is ",(0,l.kt)("inlineCode",{parentName:"li"},"B")," when committing op ",(0,l.kt)("inlineCode",{parentName:"li"},"B"),"."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tree.lookup_snapshot_max")," is ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," when committing op ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," (for ",(0,l.kt)("inlineCode",{parentName:"li"},"x \u2208 {16,17,\u2026,23}"),").")),(0,l.kt)("p",null,"At the end of the last beat of the compaction bar (",(0,l.kt)("inlineCode",{parentName:"p"},"23"),"):"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"0\u2192B")," (",(0,l.kt)("inlineCode",{parentName:"li"},"0\u202615"),") are on disk."),(0,l.kt)("li",{parentName:"ul"},"Updates from ops ",(0,l.kt)("inlineCode",{parentName:"li"},"B\u2192C")," (",(0,l.kt)("inlineCode",{parentName:"li"},"16\u202623"),") are moved from the mutable table to the immutable table."),(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tree.lookup_snapshot_max")," is ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," when committing op ",(0,l.kt)("inlineCode",{parentName:"li"},"x")," (for ",(0,l.kt)("inlineCode",{parentName:"li"},"x \u2208 {24,25,\u2026}"),").")),(0,l.kt)("h2",{id:"manifest"},"Manifest"),(0,l.kt)("p",null,"The manifest is a tree's index of table locations and metadata."),(0,l.kt)("p",null,"Each manifest has two components:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"a single ",(0,l.kt)("a",{parentName:"li",href:"#manifest-log"},(0,l.kt)("inlineCode",{parentName:"a"},"ManifestLog"))," shared by all trees and levels, and"),(0,l.kt)("li",{parentName:"ul"},"one ",(0,l.kt)("a",{parentName:"li",href:"#manifest-level"},(0,l.kt)("inlineCode",{parentName:"a"},"ManifestLevel"))," for each on-disk level.")),(0,l.kt)("h3",{id:"manifest-log"},"Manifest Log"),(0,l.kt)("p",null,"The manifest log is an on-disk log of all updates to the trees' table indexes."),(0,l.kt)("p",null,"The manifest log tracks:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"tables created as compaction output"),(0,l.kt)("li",{parentName:"ul"},"tables updated as compaction input (modifying their ",(0,l.kt)("inlineCode",{parentName:"li"},"snapshot_max"),")"),(0,l.kt)("li",{parentName:"ul"},"tables moved between levels by compaction"),(0,l.kt)("li",{parentName:"ul"},"tables deleted after compaction")),(0,l.kt)("p",null,"Updates are accumulated in-memory before being flushed:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},"incrementally during compaction, or"),(0,l.kt)("li",{parentName:"ul"},"in their entirety during checkpoint.")),(0,l.kt)("p",null,"The manifest log is periodically compacted to remove older entries that have been superseded by\nnewer entries. For example, if a table is created and later deleted, manifest log compaction\nwill eventually remove any reference to the table from the log blocks."),(0,l.kt)("p",null,'Each manifest block has a reference to the (chronologically) previous manifest block.\nThe superblock stores the head and tail address/checksum of this linked list.\nThe reference on the header of the head manifest block "dangles" \u2013 the block it references has already been compacted.'),(0,l.kt)("h3",{id:"manifest-level"},"Manifest Level"),(0,l.kt)("p",null,"A ",(0,l.kt)("inlineCode",{parentName:"p"},"ManifestLevel")," is an in-memory collection of the table metadata for a single level of a tree."),(0,l.kt)("p",null,"For a given level and snapshot, there may be gaps in the key ranges of the visible tables,\nbut the key ranges are disjoint."),(0,l.kt)("p",null,"Manifest levels are queried for tables at a target snapshot and within a key range."),(0,l.kt)("h4",{id:"example"},"Example"),(0,l.kt)("p",null,"Given the ",(0,l.kt)("inlineCode",{parentName:"p"},"ManifestLevel")," tables (with values chosen for visualization, not realism):"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"}," label A B C D E F G H I J K L M\n key_min 0 4 12 16 4 8 12 26 4 25 4 16 24\n key_max 3 11 15 19 7 11 15 27 7 27 11 19 27\nsnapshot_min 1 1 1 1 3 3 3 3 5 5 7 7 7\nsnapshot_max 9 3 3 7 5 7 9 5 7 7 9 9 9\n")),(0,l.kt)("p",null,"A level's tables can be visualized in 2D as a partitioned rectangle:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"}," 0 1 2\n 0 4 8 2 6 0 4 8\n9\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2510\n \u2502 \u2502 K \u2502 \u2502 L \u2502###\u2502 M \u2502\n7\u2502 \u251c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2524 \u251c\u2500\u2500\u2500\u2524###\u2514\u252c\u2500\u2500\u2524\n \u2502 \u2502 I \u2502 \u2502 G \u2502 \u2502####\u2502 J\u2502\n5\u2502 A \u251c\u2500\u2500\u2500\u2524 F \u2502 \u2502 \u2502####\u2514\u252c\u2500\u2524\n \u2502 \u2502 E \u2502 \u2502 \u2502 D \u2502#####\u2502H\u2502\n3\u2502 \u251c\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2524 \u2502#####\u2514\u2500\u2524\n \u2502 \u2502 B \u2502 C \u2502 \u2502#######\u2502\n1\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n")),(0,l.kt)("p",null,"Example iterations:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre"},"visibility snapshots direction key_min key_max tables\n visible 2 ascending 0 28 A, B, C, D\n visible 4 ascending 0 28 A, E, F, G, D, H\n visible 6 descending 12 28 J, D, G\n visible 8 ascending 0 28 A, K, G, L, M\n invisible 2, 4, 6 ascending 0 28 K, L, M\n")),(0,l.kt)("p",null,"Legend:"),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"#")," represents a gap \u2014 no tables cover these keys during the snapshot."),(0,l.kt)("li",{parentName:"ul"},"The horizontal axis represents the key range."),(0,l.kt)("li",{parentName:"ul"},"The vertical axis represents the snapshot range."),(0,l.kt)("li",{parentName:"ul"},"Each rectangle is a table within the manifest level."),(0,l.kt)("li",{parentName:"ul"},"The sides of each rectangle depict:",(0,l.kt)("ul",{parentName:"li"},(0,l.kt)("li",{parentName:"ul"},"left: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_min")," (the diagram is inclusive, and the ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_min")," is inclusive)"),(0,l.kt)("li",{parentName:"ul"},"right: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_max")," (the diagram is EXCLUSIVE, but the ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_max")," is INCLUSIVE)"),(0,l.kt)("li",{parentName:"ul"},"bottom: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.snapshot_min")," (inclusive)"),(0,l.kt)("li",{parentName:"ul"},"top: ",(0,l.kt)("inlineCode",{parentName:"li"},"table.snapshot_max")," (inclusive)"))),(0,l.kt)("li",{parentName:"ul"},"(Not depicted: tables may have ",(0,l.kt)("inlineCode",{parentName:"li"},"table.key_min == table.key_max"),".)"),(0,l.kt)("li",{parentName:"ul"},"(Not depicted: the newest set of tables would have ",(0,l.kt)("inlineCode",{parentName:"li"},"table.snapshot_max == maxInt(u64)"),".)")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8cb2464c.6002416e.js b/assets/js/8cb2464c.6002416e.js deleted file mode 100644 index c6209cdf..00000000 --- a/assets/js/8cb2464c.6002416e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[333],{3905:(e,t,a)=>{a.d(t,{Zo:()=>m,kt:()=>k});var r=a(7294);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function l(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var p=r.createContext({}),s=function(e){var t=r.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},m=function(e){var t=s(e.components);return r.createElement(p.Provider,{value:t},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},h=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,i=e.originalType,p=e.parentName,m=o(e,["components","mdxType","originalType","parentName"]),c=s(a),h=n,k=c["".concat(p,".").concat(h)]||c[h]||d[h]||i;return a?r.createElement(k,l(l({ref:t},m),{},{components:a})):r.createElement(k,l({ref:t},m))}));function k(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=a.length,l=new Array(i);l[0]=h;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[c]="string"==typeof e?e:n,l[1]=o;for(var s=2;s{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>s});var r=a(7462),n=(a(7294),a(3905));const i={sidebar_position:1},l="VSR",o={unversionedId:"about/internals/vsr",id:"about/internals/vsr",title:"VSR",description:"Documentation for (roughly) code in the src/vsr directory.",source:"@site/pages/about/internals/vsr.md",sourceDirName:"about/internals",slug:"/about/internals/vsr",permalink:"/about/internals/vsr",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/vsr.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Internals",permalink:"/about/internals/"},next:{title:"Data File",permalink:"/about/internals/data_file"}},p={},s=[{value:"Commands",id:"commands",level:3},{value:"Recovery",id:"recovery",level:3},{value:"Protocol: Ping (Replica-Replica)",id:"protocol-ping-replica-replica",level:2},{value:"Protocol: Ping (Replica-Client)",id:"protocol-ping-replica-client",level:2},{value:"Protocol: Normal",id:"protocol-normal",level:2},{value:"Protocol: Start-View-Change",id:"protocol-start-view-change",level:2},{value:"Protocol: View-Change",id:"protocol-view-change",level:2},{value:"Protocol: Request/Start View",id:"protocol-requeststart-view",level:2},{value:"request_start_view",id:"request_start_view",level:3},{value:"start_view",id:"start_view",level:3},{value:"Protocol: Repair Journal",id:"protocol-repair-journal",level:2},{value:"Protocol: Repair WAL",id:"protocol-repair-wal",level:2},{value:"Protocol: Repair Client Replies",id:"protocol-repair-client-replies",level:2},{value:"Protocol: Client",id:"protocol-client",level:2},{value:"Protocol: Repair Grid",id:"protocol-repair-grid",level:2},{value:"Protocol: Sync Superblock",id:"protocol-sync-superblock",level:2},{value:"Protocol: Sync Client Replies",id:"protocol-sync-client-replies",level:2},{value:"Protocol: Sync Forest",id:"protocol-sync-forest",level:2},{value:"Protocol: Reconfiguration",id:"protocol-reconfiguration",level:2},{value:"Further reading",id:"further-reading",level:2}],m={toc:s},c="wrapper";function d(e){let{components:t,...a}=e;return(0,n.kt)(c,(0,r.Z)({},m,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"vsr"},"VSR"),(0,n.kt)("p",null,"Documentation for (roughly) code in the ",(0,n.kt)("inlineCode",{parentName:"p"},"src/vsr")," directory."),(0,n.kt)("h1",{id:"glossary"},"Glossary"),(0,n.kt)("p",null,"Consensus:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"checkpoint"),": Ensure that all updates from the past wrap of the WAL are durable in the ",(0,n.kt)("em",{parentName:"li"},"grid"),", then advance the replica's recovery point by updating the superblock. After a checkpoint, the checkpointed WAL entries are safe to be overwritten by the next wrap. (Sidenote: in consensus literature this is sometimes called snapshotting. But we use that term to mean something else.)"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"header"),": Identifier for many kinds of messages, including each entry in the VSR log. Passed around instead of the entry when the full entry is not needed (such as view change)."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"journal"),": The in-memory data structure that manages the WAL."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"nack"),": Short for negative acknowledgement. Used to determine (during a view change) which entries can be truncated from the log. See ",(0,n.kt)("a",{parentName:"li",href:"https://www.usenix.org/system/files/conference/fast18/fast18-alagappan.pdf"},"Protocol Aware Recovery"),"."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"op"),": Short for op-number. An op is assigned to each request that is submitted by the user before being stored in the log. An op is a monotonically increasing integer identifying each message to be handled by consensus. When messages with the same op in different views conflict, view change picks one version to commit. Each user batch (which may contain many batch entries) corresponds to one op. Each op is identified (once inside the VSR log) by a ",(0,n.kt)("em",{parentName:"li"},"header"),"."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"superblock"),": All local state for the replica that cannot be replicated remotely. Loss is protected against by storing ",(0,n.kt)("inlineCode",{parentName:"li"},"config.superblock_copies")," copies of the superblock."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"view"),": A replica is ",(0,n.kt)("em",{parentName:"li"},"primary")," for one view. Views are monotonically increasing integers that are incremented each time a new primary is selected.")),(0,n.kt)("p",null,"Storage:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"zone"),": The TigerBeetle data file is made up of zones. The superblock is one zone."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"grid"),": The zone on disk where LSM trees and metadata for them reside."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"WAL"),": Write-ahead log. It is implemented as two on-disk ring buffers. Entries are only overwritten after they have been checkpointed."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"state sync"),": The process of syncing checkpointed data (LSM root information, the ",(0,n.kt)("em",{parentName:"li"},"grid"),", and the superblock freeset). When a replica lags behind the cluster far enough that their WALs no longer intersect, the lagging replica must state sync to catch up.")),(0,n.kt)("h1",{id:"protocols"},"Protocols"),(0,n.kt)("h3",{id:"commands"},"Commands"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"th"},"vsr.Header.Command")),(0,n.kt)("th",{parentName:"tr",align:"right"},"Source"),(0,n.kt)("th",{parentName:"tr",align:"right"},"Target"),(0,n.kt)("th",{parentName:"tr",align:null},"Protocols"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"ping")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-replica"},"Ping (Replica-Replica)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"pong")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-replica"},"Ping (Replica-Replica)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"ping_client")),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-client"},"Ping (Replica-Client)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"pong_client")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-client"},"Ping (Replica-Client)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request")),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"prepare")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-wal"},"Repair WAL"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"prepare_ok")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-wal"},"Repair WAL"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"reply")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-client-replies"},"Repair Client Replies"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-client-replies"},"Sync Client Replies"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"commit")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"start_view_change")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"all replicas"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-start-view-change"},"Start-View-Change"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"do_view_change")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"all replicas"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-view-change"},"View-Change"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"start_view")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-requeststart-view"},"Request/Start View"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_start_view")),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-requeststart-view"},"Request/Start View"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_headers")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-journal"},"Repair Journal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_prepare")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-wal"},"Repair WAL"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_reply")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-client-replies"},"Repair Client Replies"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-client-replies"},"Sync Client Replies"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"headers")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-journal"},"Repair Journal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"eviction")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-client"},"Client"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_blocks")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-forest"},"Sync Forest"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-grid"},"Repair Grid"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"block")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-forest"},"Sync Forest"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-grid"},"Repair Grid"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_sync_checkpoint")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-superblock"},"Sync Superblock"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"sync_checkpoint")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-superblock"},"Sync Superblock"))))),(0,n.kt)("h3",{id:"recovery"},"Recovery"),(0,n.kt)("p",null,"Unlike ",(0,n.kt)("a",{parentName:"p",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"VRR"),", TigerBeetle does not implement Recovery Protocol (see \xa74.3).\nInstead, replicas persist their VSR state to the superblock.\nThis ensures that a recovering replica never backtracks to an older view (from the point of view of the cluster)."),(0,n.kt)("h2",{id:"protocol-ping-replica-replica"},"Protocol: Ping (Replica-Replica)"),(0,n.kt)("p",null,"Replicas send ",(0,n.kt)("inlineCode",{parentName:"p"},"command=ping"),"/",(0,n.kt)("inlineCode",{parentName:"p"},"command=pong")," messages to one another to synchronize clocks."),(0,n.kt)("h2",{id:"protocol-ping-replica-client"},"Protocol: Ping (Replica-Client)"),(0,n.kt)("p",null,"Clients send ",(0,n.kt)("inlineCode",{parentName:"p"},"command=ping_client")," (and receive ",(0,n.kt)("inlineCode",{parentName:"p"},"command=pong_client"),") messages to (from) replicas to learn the cluster's current view."),(0,n.kt)("h2",{id:"protocol-normal"},"Protocol: Normal"),(0,n.kt)("p",null,"Normal protocol prepares and commits requests (from clients) and sends replies (to clients)."),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"The client sends a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request")," message to the primary. (If the client's view is outdated, the receiver will forward the message on to the actual primary)."),(0,n.kt)("li",{parentName:"ol"},"The primary converts the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request")," to a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare")," (assigning it an ",(0,n.kt)("inlineCode",{parentName:"li"},"op")," and ",(0,n.kt)("inlineCode",{parentName:"li"},"timestamp"),")."),(0,n.kt)("li",{parentName:"ol"},"Each replica (in a chain beginning with the primary) performs the following steps concurrently:",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"Write the prepare to the WAL."),(0,n.kt)("li",{parentName:"ul"},"Forward the prepare to the next replica in the chain."))),(0,n.kt)("li",{parentName:"ol"},"Each replica sends a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare_ok")," message to the primary once it has written the prepare to the WAL."),(0,n.kt)("li",{parentName:"ol"},"When a primary collects a ",(0,n.kt)("a",{parentName:"li",href:"#quorums"},"replication quorum")," of ",(0,n.kt)("inlineCode",{parentName:"li"},"prepare_ok"),"s ",(0,n.kt)("em",{parentName:"li"},"and")," it has committed all preceding prepares, it commits the prepare."),(0,n.kt)("li",{parentName:"ol"},"The primary replies to the client."),(0,n.kt)("li",{parentName:"ol"},"The backups are informed that the prepare was committed by either:",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"a subsequent prepare, or"),(0,n.kt)("li",{parentName:"ul"},"a periodic ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit")," heartbeat message.")))),(0,n.kt)("mermaid",{value:"sequenceDiagram\n participant C0 as Client\n participant R0 as Replica 0 (primary)\n participant R1 as Replica 1 (backup)\n participant R2 as Replica 2 (backup)\n\n C0->>R0: Request A\n\n R0->>+R0: Prepare A\n R0->>+R1: Prepare A\n R1->>+R2: Prepare A\n\n R0->>-R0: Prepare-Ok A\n R1->>-R0: Prepare-Ok A\n R0->>C0: Reply A\n R2->>-R0: Prepare-Ok A"}),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"VRR")," \xa74.1")),(0,n.kt)("h2",{id:"protocol-start-view-change"},"Protocol: Start-View-Change"),(0,n.kt)("p",null,"Start-View-Change (SVC) protocol initiates ",(0,n.kt)("a",{parentName:"p",href:"#protocol-view-change"},"view-changes")," with minimal disruption."),(0,n.kt)("p",null,"Unlike the Start-View-Change described in ",(0,n.kt)("a",{parentName:"p",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"VRR")," \xa74.2, this protocol runs in both ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal")," and ",(0,n.kt)("inlineCode",{parentName:"p"},"status=view_change")," (not just ",(0,n.kt)("inlineCode",{parentName:"p"},"status=view_change"),")."),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"Depending on the replica's status:",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"status=normal")," & primary: When the replica has not recently received a ",(0,n.kt)("inlineCode",{parentName:"li"},"prepare_ok")," (and it has a prepare in flight), pause broadcasting ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit"),"."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"status=normal")," & backup: When the replica has not recently received a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit"),", broadcast ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view_change")," to all replicas (including self)."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"status=view_change"),": If the replica has not completed a view-change recently, send a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view_change")," to all replicas (including self)."))),(0,n.kt)("li",{parentName:"ol"},"(Periodically retry sending the SVC)."),(0,n.kt)("li",{parentName:"ol"},"If the backup receives a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit")," or changes views (respectively), stop the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view_change")," retries."),(0,n.kt)("li",{parentName:"ol"},"If the replica collects a ",(0,n.kt)("a",{parentName:"li",href:"#quorums"},"view-change quorum")," of SVC messages, transition to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=view_change")," for the next view. (That is, increment the replica's view and start sending a DVC).")),(0,n.kt)("p",null,"This protocol approach enables liveness under asymmetric network partitions. For example, a replica which can send to the cluster but not receive may send SVCs, but if the remainder of the cluster is healthy, they will never achieve a quorum, so the view is stable. When the partition heals, the formerly-isolated replica may rejoin the original view (if it was isolated in ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal"),") or a new view (if it was isolated in ",(0,n.kt)("inlineCode",{parentName:"p"},"status=view_change"),")."),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://decentralizedthoughts.github.io/2020-12-12-raft-liveness-full-omission/"},"Raft does not Guarantee Liveness in the face of Network Faults"),' ("PreVote and CheckQuorum")'),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf"},'"Consensus: Bridging Theory and Practice"'),' \xa76.2 "Leaders" describes periodically committing a heartbeat to detect stale leaders.')),(0,n.kt)("h2",{id:"protocol-view-change"},"Protocol: View-Change"),(0,n.kt)("p",null,"A replica sends ",(0,n.kt)("inlineCode",{parentName:"p"},"command=do_view_change")," to all replicas, with the ",(0,n.kt)("inlineCode",{parentName:"p"},"view")," it is attempting to start."),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"primary")," of the ",(0,n.kt)("inlineCode",{parentName:"li"},"view")," collects a ",(0,n.kt)("a",{parentName:"li",href:"#quorums"},"view-change quorum")," of DVCs."),(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"backup")," of the ",(0,n.kt)("inlineCode",{parentName:"li"},"view")," uses to ",(0,n.kt)("inlineCode",{parentName:"li"},"do_view_change")," to updates its current ",(0,n.kt)("inlineCode",{parentName:"li"},"view")," (transitioning to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=view_change"),").")),(0,n.kt)("p",null,"DVCs include headers from prepares which are:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"present"),": A valid header, corresponding to a valid prepare in the replica's WAL."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"missing"),": A valid header, corresponding to a prepare that the replica has not prepared/acked."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"corrupt"),": A valid header, corresponding to a corrupt prepare in the replica's WAL."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"blank"),": A placeholder (fake) header, corresponding to a header that the replica has never seen."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"fault"),": A placeholder (fake) header, corresponding to a header that the replica ",(0,n.kt)("em",{parentName:"li"},"may have")," prepared/acked.")),(0,n.kt)("p",null,"If the new primary collects a ",(0,n.kt)("em",{parentName:"p"},"nack quorum")," of ",(0,n.kt)("em",{parentName:"p"},"blank")," headers for a particular possibly-uncommitted op, it truncates the log."),(0,n.kt)("p",null,"These cases are farther distinguished during ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-wal"},"WAL repair"),"."),(0,n.kt)("p",null,"When the primary collects its DVC quorum:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},'If any DVC in the quorum is ahead of the primary by more than one checkpoint,\nthe new primary "forfeits" (that is, it immediately triggers another view change).'),(0,n.kt)("li",{parentName:"ol"},"If any DVC in the quorum is ahead of the primary by more than one checkpoint,\nand any messages in the next checkpoint are possibly committed,\nthe new primary forfeits."),(0,n.kt)("li",{parentName:"ol"},"The primary installs the headers to its suffix."),(0,n.kt)("li",{parentName:"ol"},"Then the primary repairs its headers. (",(0,n.kt)("a",{parentName:"li",href:"#protocol-repair-journal"},"Protocol: Repair Journal"),")."),(0,n.kt)("li",{parentName:"ol"},"Then the primary repairs its prepares. (",(0,n.kt)("a",{parentName:"li",href:"#protocol-repair-wal"},"Protocol: Repair WAL"),") (and potentially truncates uncommitted ops)."),(0,n.kt)("li",{parentName:"ol"},"Then primary commits all prepares which are not known to be uncommitted."),(0,n.kt)("li",{parentName:"ol"},"Then the primary transitions to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=normal")," and broadcasts a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view"),".")),(0,n.kt)("h2",{id:"protocol-requeststart-view"},"Protocol: Request/Start View"),(0,n.kt)("h3",{id:"request_start_view"},(0,n.kt)("inlineCode",{parentName:"h3"},"request_start_view")),(0,n.kt)("p",null,"A backup sends a ",(0,n.kt)("inlineCode",{parentName:"p"},"command=request_start_view")," to the primary of a view when any of the following occur:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"the backup learns about a newer view via a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit")," message, or"),(0,n.kt)("li",{parentName:"ul"},"the backup learns about a newer view via a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare")," message, or"),(0,n.kt)("li",{parentName:"ul"},"the backup discovers ",(0,n.kt)("inlineCode",{parentName:"li"},"commit_max")," exceeds ",(0,n.kt)("inlineCode",{parentName:"li"},"min(op_head, op_checkpoint_next_trigger)")," (during repair), or"),(0,n.kt)("li",{parentName:"ul"},"a replica recovers to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=recovering_head"))),(0,n.kt)("h3",{id:"start_view"},(0,n.kt)("inlineCode",{parentName:"h3"},"start_view")),(0,n.kt)("p",null,"When a ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal")," primary receives ",(0,n.kt)("inlineCode",{parentName:"p"},"command=request_start_view"),", it replies with a ",(0,n.kt)("inlineCode",{parentName:"p"},"command=start_view"),".\n",(0,n.kt)("inlineCode",{parentName:"p"},"command=start_view")," includes the view's current suffix \u2014 the headers of the latest messages in the view."),(0,n.kt)("p",null,"Upon receiving a ",(0,n.kt)("inlineCode",{parentName:"p"},"start_view")," for the new view, the backup installs the suffix, transitions to ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal"),", and begins repair."),(0,n.kt)("p",null,"A ",(0,n.kt)("inlineCode",{parentName:"p"},"start_view")," contains the following headers (which may overlap):"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The suffix: ",(0,n.kt)("inlineCode",{parentName:"li"},"pipeline_prepare_queue_max")," headers from the head op down."),(0,n.kt)("li",{parentName:"ul"},'The "hooks": the header of any previous checkpoint triggers within our repairable range.\nThis helps a lagging replica catch up. (There are at most 2).')),(0,n.kt)("h2",{id:"protocol-repair-journal"},"Protocol: Repair Journal"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"request_headers")," and ",(0,n.kt)("inlineCode",{parentName:"p"},"headers")," repair gaps or breaks in a replica's journal headers.\nRepaired headers are a prerequisite for ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-wal"},"repairing prepares"),"."),(0,n.kt)("p",null,"Because the headers are repaired backwards (from the head) by hash-chaining, it is safe for both backups and transitioning primaries."),(0,n.kt)("p",null,"Gaps/breaks in a replica's journal headers may occur:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"On a backup, receiving nonconsecutive ops, leaving a gap in its headers."),(0,n.kt)("li",{parentName:"ul"},"On a backup, which has not finished repair."),(0,n.kt)("li",{parentName:"ul"},"On a new primary during a view-change, which has not finished repair.")),(0,n.kt)("h2",{id:"protocol-repair-wal"},"Protocol: Repair WAL"),(0,n.kt)("p",null,"The replica's journal tracks which prepares the WAL requires \u2014 i.e. headers for which either:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"no prepare was ever received, or"),(0,n.kt)("li",{parentName:"ul"},"the prepare was received and written, but was since discovered to be corrupt")),(0,n.kt)("p",null,"During repair, missing/damaged prepares are requested & repaired chronologically, which:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"improves the chances that older entries will be available, i.e. not yet overwritten"),(0,n.kt)("li",{parentName:"ul"},"enables better pipelining of repair and commit.")),(0,n.kt)("p",null,"In response to a ",(0,n.kt)("inlineCode",{parentName:"p"},"request_prepare"),":"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Reply the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare")," with the requested prepare, if available and valid."),(0,n.kt)("li",{parentName:"ul"},"Otherwise do not reply. (e.g. the corresponding slot in the WAL is corrupt)")),(0,n.kt)("p",null,"Per ",(0,n.kt)("a",{parentName:"p",href:"https://www.usenix.org/system/files/conference/fast18/fast18-alagappan.pdf"},"PAR's CTRL Protocol"),", we do not nack corrupt entries, since they ",(0,n.kt)("em",{parentName:"p"},"might")," be the prepare being requested."),(0,n.kt)("h2",{id:"protocol-repair-client-replies"},"Protocol: Repair Client Replies"),(0,n.kt)("p",null,"The replica stores the latest reply to each active client."),(0,n.kt)("p",null,"During repair, corrupt client replies are requested & repaired."),(0,n.kt)("p",null,"In response to a ",(0,n.kt)("inlineCode",{parentName:"p"},"request_reply"),":"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Respond with the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=reply")," (the requested reply), if available and valid."),(0,n.kt)("li",{parentName:"ul"},"Otherwise do not reply.")),(0,n.kt)("h2",{id:"protocol-client"},"Protocol: Client"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"Client sends ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request operation=register")," to registers with the cluster by starting a new request-reply hashchain. (See also: ",(0,n.kt)("a",{parentName:"li",href:"#protocol-normal"},"Protocol: Normal"),")."),(0,n.kt)("li",{parentName:"ol"},"Client receives ",(0,n.kt)("inlineCode",{parentName:"li"},"command=reply operation=register")," from the cluster. (If the cluster is at the maximum number of clients, it evicts the oldest)."),(0,n.kt)("li",{parentName:"ol"},"Repeat:",(0,n.kt)("ol",{parentName:"li"},(0,n.kt)("li",{parentName:"ol"},"Send ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request")," to cluster."),(0,n.kt)("li",{parentName:"ol"},"If the client has been evicted, receive ",(0,n.kt)("inlineCode",{parentName:"li"},"command=eviction")," from the cluster. (The client must re-register before sending more requests.)"),(0,n.kt)("li",{parentName:"ol"},"If the client has not been evicted, receive ",(0,n.kt)("inlineCode",{parentName:"li"},"command=reply")," from cluster.")))),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/reference/sessions#lifecycle"},"Integration: Client Session Lifecycle")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/reference/sessions#eviction"},"Integration: Client Session Eviction"))),(0,n.kt)("h2",{id:"protocol-repair-grid"},"Protocol: Repair Grid"),(0,n.kt)("p",null,"Grid repair is triggered when a replica discovers a corrupt (or missing) grid block."),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"The repairing replica sends a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request_blocks")," to any other replica. The message body contains a list of block ",(0,n.kt)("inlineCode",{parentName:"li"},"address"),"/",(0,n.kt)("inlineCode",{parentName:"li"},"checksum"),"s."),(0,n.kt)("li",{parentName:"ol"},"Upon receiving a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request_blocks"),", a replica reads its own grid to check for the requested blocks. For each matching block found, reply with the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=block")," message (the block itself)."),(0,n.kt)("li",{parentName:"ol"},"Upon receiving a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=block"),", a replica writes the block to its grid, and resolves the reads that were blocked on it.")),(0,n.kt)("p",null,"Note that ",(0,n.kt)("em",{parentName:"p"},"both sides")," of grid repair can run while the grid is being opened during replica startup.\nThat is, a replica can help other replicas repair and repair itself simultaneously."),(0,n.kt)("p",null,"TODO Describe state sync fallback."),(0,n.kt)("h2",{id:"protocol-sync-superblock"},"Protocol: Sync Superblock"),(0,n.kt)("p",null,"State sync synchronizes the state of a lagging replica with the healthy cluster."),(0,n.kt)("p",null,"State sync is used when when a lagging replica's log no longer intersects with the cluster's current log \u2014\n",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-wal"},"WAL repair")," cannot catch the replica up."),(0,n.kt)("p",null,"This protocol updates the replica's superblock with a more recent one."),(0,n.kt)("p",null,"See ",(0,n.kt)("a",{parentName:"p",href:"/about/internals/sync"},"State Sync")," for details."),(0,n.kt)("h2",{id:"protocol-sync-client-replies"},"Protocol: Sync Client Replies"),(0,n.kt)("p",null,"Sync missed client replies using ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-client-replies"},"Protocol: Repair Grid"),"."),(0,n.kt)("p",null,"(Runs immediately after ",(0,n.kt)("a",{parentName:"p",href:"#protocol-sync-superblock"},"Protocol: Sync Superblock"),".)\nSee ",(0,n.kt)("a",{parentName:"p",href:"/about/internals/sync"},"State Sync")," for details."),(0,n.kt)("h2",{id:"protocol-sync-forest"},"Protocol: Sync Forest"),(0,n.kt)("p",null,"Sync missed LSM manifest and table blocks using ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-grid"},"Protocol: Repair Grid"),"."),(0,n.kt)("p",null,"(Runs immediately after ",(0,n.kt)("a",{parentName:"p",href:"#protocol-sync-superblock"},"Protocol: Sync Superblock"),".)\nSee ",(0,n.kt)("a",{parentName:"p",href:"/about/internals/sync"},"State Sync")," for details."),(0,n.kt)("h2",{id:"protocol-reconfiguration"},"Protocol: Reconfiguration"),(0,n.kt)("p",null,"TODO (Unimplemented)"),(0,n.kt)("h1",{id:"quorums"},"Quorums"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"replication quorum")," is the minimum number of replicas required to complete a commit."),(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"view-change quorum")," is the minimum number of replicas required to complete a view-change."),(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"nack quorum")," is the minimum number of unique nacks required to truncate an uncommitted op.")),(0,n.kt)("p",null,"With the default configuration:"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"th"},"Replica Count")),(0,n.kt)("th",{parentName:"tr",align:"right"},"1"),(0,n.kt)("th",{parentName:"tr",align:"right"},"2"),(0,n.kt)("th",{parentName:"tr",align:"right"},"3"),(0,n.kt)("th",{parentName:"tr",align:"right"},"4"),(0,n.kt)("th",{parentName:"tr",align:"right"},"5"),(0,n.kt)("th",{parentName:"tr",align:"right"},"6"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"Replication Quorum")),(0,n.kt)("td",{parentName:"tr",align:"right"},"1"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"View-Change Quorum")),(0,n.kt)("td",{parentName:"tr",align:"right"},"1"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"4")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"Nack Quorum")),(0,n.kt)("td",{parentName:"tr",align:"right"},"1"),(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"1")),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"4")))),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"constants.quorum_replication_max")," for configuration."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://fpaxos.github.io/"},"Flexible Paxos"))),(0,n.kt)("h2",{id:"further-reading"},"Further reading"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"Viewstamped Replication Revisited")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://www.usenix.org/system/files/conference/fast18/fast18-alagappan.pdf"},"Protocol Aware Recovery"))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8cb2464c.60d68d0d.js b/assets/js/8cb2464c.60d68d0d.js new file mode 100644 index 00000000..cdfb0e0f --- /dev/null +++ b/assets/js/8cb2464c.60d68d0d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[333],{3905:(e,t,a)=>{a.d(t,{Zo:()=>m,kt:()=>k});var r=a(7294);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function l(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var p=r.createContext({}),s=function(e){var t=r.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},m=function(e){var t=s(e.components);return r.createElement(p.Provider,{value:t},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},h=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,i=e.originalType,p=e.parentName,m=o(e,["components","mdxType","originalType","parentName"]),c=s(a),h=n,k=c["".concat(p,".").concat(h)]||c[h]||d[h]||i;return a?r.createElement(k,l(l({ref:t},m),{},{components:a})):r.createElement(k,l({ref:t},m))}));function k(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=a.length,l=new Array(i);l[0]=h;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[c]="string"==typeof e?e:n,l[1]=o;for(var s=2;s{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>s});var r=a(7462),n=(a(7294),a(3905));const i={sidebar_position:1},l="VSR",o={unversionedId:"about/internals/vsr",id:"about/internals/vsr",title:"VSR",description:"Documentation for (roughly) code in the src/vsr directory.",source:"@site/pages/about/internals/vsr.md",sourceDirName:"about/internals",slug:"/about/internals/vsr",permalink:"/about/internals/vsr",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/vsr.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Internals",permalink:"/about/internals/"},next:{title:"Data File",permalink:"/about/internals/data_file"}},p={},s=[{value:"Glossary",id:"glossary",level:2},{value:"Protocols",id:"protocols",level:2},{value:"Commands",id:"commands",level:3},{value:"Recovery",id:"recovery",level:3},{value:"Protocol: Ping (Replica-Replica)",id:"protocol-ping-replica-replica",level:3},{value:"Protocol: Ping (Replica-Client)",id:"protocol-ping-replica-client",level:3},{value:"Protocol: Normal",id:"protocol-normal",level:3},{value:"Protocol: Start-View-Change",id:"protocol-start-view-change",level:3},{value:"Protocol: View-Change",id:"protocol-view-change",level:3},{value:"Protocol: Request/Start View",id:"protocol-requeststart-view",level:3},{value:"request_start_view",id:"request_start_view",level:4},{value:"start_view",id:"start_view",level:4},{value:"Protocol: Repair Journal",id:"protocol-repair-journal",level:3},{value:"Protocol: Repair WAL",id:"protocol-repair-wal",level:3},{value:"Protocol: Repair Client Replies",id:"protocol-repair-client-replies",level:3},{value:"Protocol: Client",id:"protocol-client",level:3},{value:"Protocol: Repair Grid",id:"protocol-repair-grid",level:3},{value:"Protocol: Sync Client Replies",id:"protocol-sync-client-replies",level:3},{value:"Protocol: Sync Forest",id:"protocol-sync-forest",level:3},{value:"Protocol: Reconfiguration",id:"protocol-reconfiguration",level:3},{value:"Quorums",id:"quorums",level:2},{value:"Further reading",id:"further-reading",level:2}],m={toc:s},c="wrapper";function d(e){let{components:t,...a}=e;return(0,n.kt)(c,(0,r.Z)({},m,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"vsr"},"VSR"),(0,n.kt)("p",null,"Documentation for (roughly) code in the ",(0,n.kt)("inlineCode",{parentName:"p"},"src/vsr")," directory."),(0,n.kt)("h2",{id:"glossary"},"Glossary"),(0,n.kt)("p",null,"Consensus:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"checkpoint"),": Ensure that all updates from the past wrap of the WAL are durable in the ",(0,n.kt)("em",{parentName:"li"},"grid"),", then advance the replica's recovery point by updating the superblock. After a checkpoint, the checkpointed WAL entries are safe to be overwritten by the next wrap. (Sidenote: in consensus literature this is sometimes called snapshotting. But we use that term to mean something else.)"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"header"),": Identifier for many kinds of messages, including each entry in the VSR log. Passed around instead of the entry when the full entry is not needed (such as view change)."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"journal"),": The in-memory data structure that manages the WAL."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"nack"),": Short for negative acknowledgement. Used to determine (during a view change) which entries can be truncated from the log. See ",(0,n.kt)("a",{parentName:"li",href:"https://www.usenix.org/system/files/conference/fast18/fast18-alagappan.pdf"},"Protocol Aware Recovery"),"."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"op"),": Short for op-number. An op is assigned to each request that is submitted by the user before being stored in the log. An op is a monotonically increasing integer identifying each message to be handled by consensus. When messages with the same op in different views conflict, view change picks one version to commit. Each user batch (which may contain many batch entries) corresponds to one op. Each op is identified (once inside the VSR log) by a ",(0,n.kt)("em",{parentName:"li"},"header"),"."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"superblock"),": All local state for the replica that cannot be replicated remotely. Loss is protected against by storing ",(0,n.kt)("inlineCode",{parentName:"li"},"config.superblock_copies")," copies of the superblock."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"view"),": A replica is ",(0,n.kt)("em",{parentName:"li"},"primary")," for one view. Views are monotonically increasing integers that are incremented each time a new primary is selected.")),(0,n.kt)("p",null,"Storage:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"zone"),": The TigerBeetle data file is made up of zones. The superblock is one zone."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"grid"),": The zone on disk where LSM trees and metadata for them reside."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"WAL"),": Write-ahead log. It is implemented as two on-disk ring buffers. Entries are only overwritten after they have been checkpointed."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"state sync"),": The process of syncing checkpointed data (LSM root information, the ",(0,n.kt)("em",{parentName:"li"},"grid"),", and the superblock freeset). When a replica lags behind the cluster far enough that their WALs no longer intersect, the lagging replica must state sync to catch up.")),(0,n.kt)("h2",{id:"protocols"},"Protocols"),(0,n.kt)("h3",{id:"commands"},"Commands"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"th"},"vsr.Header.Command")),(0,n.kt)("th",{parentName:"tr",align:"right"},"Source"),(0,n.kt)("th",{parentName:"tr",align:"right"},"Target"),(0,n.kt)("th",{parentName:"tr",align:null},"Protocols"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"ping")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-replica"},"Ping (Replica-Replica)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"pong")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-replica"},"Ping (Replica-Replica)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"ping_client")),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-client"},"Ping (Replica-Client)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"pong_client")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-ping-replica-client"},"Ping (Replica-Client)"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request")),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"prepare")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-wal"},"Repair WAL"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"prepare_ok")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-wal"},"Repair WAL"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"reply")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-client-replies"},"Repair Client Replies"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-client-replies"},"Sync Client Replies"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"commit")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-normal"},"Normal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"start_view_change")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"all replicas"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-start-view-change"},"Start-View-Change"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"do_view_change")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"all replicas"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-view-change"},"View-Change"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"start_view")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-requeststart-view"},"Request/Start View"),", ",(0,n.kt)("a",{parentName:"td",href:"/about/internals/sync"},"State Sync"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_start_view")),(0,n.kt)("td",{parentName:"tr",align:"right"},"backup"),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-requeststart-view"},"Request/Start View"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_headers")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-journal"},"Repair Journal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_prepare")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-wal"},"Repair WAL"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_reply")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-client-replies"},"Repair Client Replies"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-client-replies"},"Sync Client Replies"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"headers")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-journal"},"Repair Journal"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"eviction")),(0,n.kt)("td",{parentName:"tr",align:"right"},"primary"),(0,n.kt)("td",{parentName:"tr",align:"right"},"client"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-client"},"Client"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"request_blocks")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-forest"},"Sync Forest"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-grid"},"Repair Grid"))),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("inlineCode",{parentName:"td"},"block")),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:"right"},"replica"),(0,n.kt)("td",{parentName:"tr",align:null},(0,n.kt)("a",{parentName:"td",href:"#protocol-sync-forest"},"Sync Forest"),", ",(0,n.kt)("a",{parentName:"td",href:"#protocol-repair-grid"},"Repair Grid"))))),(0,n.kt)("h3",{id:"recovery"},"Recovery"),(0,n.kt)("p",null,"Unlike ",(0,n.kt)("a",{parentName:"p",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"VRR"),", TigerBeetle does not implement Recovery Protocol (see \xa74.3).\nInstead, replicas persist their VSR state to the superblock.\nThis ensures that a recovering replica never backtracks to an older view (from the point of view of the cluster)."),(0,n.kt)("h3",{id:"protocol-ping-replica-replica"},"Protocol: Ping (Replica-Replica)"),(0,n.kt)("p",null,"Replicas send ",(0,n.kt)("inlineCode",{parentName:"p"},"command=ping"),"/",(0,n.kt)("inlineCode",{parentName:"p"},"command=pong")," messages to one another to synchronize clocks."),(0,n.kt)("h3",{id:"protocol-ping-replica-client"},"Protocol: Ping (Replica-Client)"),(0,n.kt)("p",null,"Clients send ",(0,n.kt)("inlineCode",{parentName:"p"},"command=ping_client")," (and receive ",(0,n.kt)("inlineCode",{parentName:"p"},"command=pong_client"),") messages to (from) replicas to learn the cluster's current view."),(0,n.kt)("h3",{id:"protocol-normal"},"Protocol: Normal"),(0,n.kt)("p",null,"Normal protocol prepares and commits requests (from clients) and sends replies (to clients)."),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"The client sends a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request")," message to the primary. (If the client's view is outdated, the receiver will forward the message on to the actual primary)."),(0,n.kt)("li",{parentName:"ol"},"The primary converts the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request")," to a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare")," (assigning it an ",(0,n.kt)("inlineCode",{parentName:"li"},"op")," and ",(0,n.kt)("inlineCode",{parentName:"li"},"timestamp"),")."),(0,n.kt)("li",{parentName:"ol"},"Each replica (in a chain beginning with the primary) performs the following steps concurrently:",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"Write the prepare to the WAL."),(0,n.kt)("li",{parentName:"ul"},"Forward the prepare to the next replica in the chain."))),(0,n.kt)("li",{parentName:"ol"},"Each replica sends a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare_ok")," message to the primary once it has written the prepare to the WAL."),(0,n.kt)("li",{parentName:"ol"},"When a primary collects a ",(0,n.kt)("a",{parentName:"li",href:"#quorums"},"replication quorum")," of ",(0,n.kt)("inlineCode",{parentName:"li"},"prepare_ok"),"s ",(0,n.kt)("em",{parentName:"li"},"and")," it has committed all preceding prepares, it commits the prepare."),(0,n.kt)("li",{parentName:"ol"},"The primary replies to the client."),(0,n.kt)("li",{parentName:"ol"},"The backups are informed that the prepare was committed by either:",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},"a subsequent prepare, or"),(0,n.kt)("li",{parentName:"ul"},"a periodic ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit")," heartbeat message.")))),(0,n.kt)("mermaid",{value:"sequenceDiagram\n participant C0 as Client\n participant R0 as Replica 0 (primary)\n participant R1 as Replica 1 (backup)\n participant R2 as Replica 2 (backup)\n\n C0->>R0: Request A\n\n R0->>+R0: Prepare A\n R0->>+R1: Prepare A\n R1->>+R2: Prepare A\n\n R0->>-R0: Prepare-Ok A\n R1->>-R0: Prepare-Ok A\n R0->>C0: Reply A\n R2->>-R0: Prepare-Ok A"}),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"VRR")," \xa74.1")),(0,n.kt)("h3",{id:"protocol-start-view-change"},"Protocol: Start-View-Change"),(0,n.kt)("p",null,"Start-View-Change (SVC) protocol initiates ",(0,n.kt)("a",{parentName:"p",href:"#protocol-view-change"},"view-changes")," with minimal disruption."),(0,n.kt)("p",null,"Unlike the Start-View-Change described in ",(0,n.kt)("a",{parentName:"p",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"VRR")," \xa74.2, this protocol runs in both ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal")," and ",(0,n.kt)("inlineCode",{parentName:"p"},"status=view_change")," (not just ",(0,n.kt)("inlineCode",{parentName:"p"},"status=view_change"),")."),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"Depending on the replica's status:",(0,n.kt)("ul",{parentName:"li"},(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"status=normal")," & primary: When the replica has not recently received a ",(0,n.kt)("inlineCode",{parentName:"li"},"prepare_ok")," (and it has a prepare in flight), pause broadcasting ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit"),"."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"status=normal")," & backup: When the replica has not recently received a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit"),", broadcast ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view_change")," to all replicas (including self)."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"status=view_change"),": If the replica has not completed a view-change recently, send a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view_change")," to all replicas (including self)."))),(0,n.kt)("li",{parentName:"ol"},"(Periodically retry sending the SVC)."),(0,n.kt)("li",{parentName:"ol"},"If the backup receives a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit")," or changes views (respectively), stop the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view_change")," retries."),(0,n.kt)("li",{parentName:"ol"},"If the replica collects a ",(0,n.kt)("a",{parentName:"li",href:"#quorums"},"view-change quorum")," of SVC messages, transition to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=view_change")," for the next view. (That is, increment the replica's view and start sending a DVC).")),(0,n.kt)("p",null,"This protocol approach enables liveness under asymmetric network partitions. For example, a replica which can send to the cluster but not receive may send SVCs, but if the remainder of the cluster is healthy, they will never achieve a quorum, so the view is stable. When the partition heals, the formerly-isolated replica may rejoin the original view (if it was isolated in ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal"),") or a new view (if it was isolated in ",(0,n.kt)("inlineCode",{parentName:"p"},"status=view_change"),")."),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://decentralizedthoughts.github.io/2020-12-12-raft-liveness-full-omission/"},"Raft does not Guarantee Liveness in the face of Network Faults"),' ("PreVote and CheckQuorum")'),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf"},'"Consensus: Bridging Theory and Practice"'),' \xa76.2 "Leaders" describes periodically committing a heartbeat to detect stale leaders.')),(0,n.kt)("h3",{id:"protocol-view-change"},"Protocol: View-Change"),(0,n.kt)("p",null,"A replica sends ",(0,n.kt)("inlineCode",{parentName:"p"},"command=do_view_change")," to all replicas, with the ",(0,n.kt)("inlineCode",{parentName:"p"},"view")," it is attempting to start."),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"primary")," of the ",(0,n.kt)("inlineCode",{parentName:"li"},"view")," collects a ",(0,n.kt)("a",{parentName:"li",href:"#quorums"},"view-change quorum")," of DVCs."),(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"backup")," of the ",(0,n.kt)("inlineCode",{parentName:"li"},"view")," uses to ",(0,n.kt)("inlineCode",{parentName:"li"},"do_view_change")," to update its current ",(0,n.kt)("inlineCode",{parentName:"li"},"view")," (transitioning to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=view_change"),").")),(0,n.kt)("p",null,"DVCs include headers from prepares which are:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"present"),": A valid header, corresponding to a valid prepare in the replica's WAL."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"missing"),": A valid header, corresponding to a prepare that the replica has not prepared/acked."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"corrupt"),": A valid header, corresponding to a corrupt prepare in the replica's WAL."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"blank"),": A placeholder (fake) header, corresponding to a header that the replica has never seen."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("em",{parentName:"li"},"fault"),": A placeholder (fake) header, corresponding to a header that the replica ",(0,n.kt)("em",{parentName:"li"},"may have")," prepared/acked.")),(0,n.kt)("p",null,"If the new primary collects a ",(0,n.kt)("em",{parentName:"p"},"nack quorum")," of ",(0,n.kt)("em",{parentName:"p"},"blank")," headers for a particular possibly-uncommitted op, it truncates the log."),(0,n.kt)("p",null,"These cases are farther distinguished during ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-wal"},"WAL repair"),"."),(0,n.kt)("p",null,"When the primary collects its DVC quorum:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},'If any DVC in the quorum is ahead of the primary by more than one checkpoint,\nthe new primary "forfeits" (that is, it immediately triggers another view change).'),(0,n.kt)("li",{parentName:"ol"},"If any DVC in the quorum is ahead of the primary by more than one checkpoint,\nand any messages in the next checkpoint are possibly committed,\nthe new primary forfeits."),(0,n.kt)("li",{parentName:"ol"},"The primary installs the headers to its suffix."),(0,n.kt)("li",{parentName:"ol"},"Then the primary repairs its headers. (",(0,n.kt)("a",{parentName:"li",href:"#protocol-repair-journal"},"Protocol: Repair Journal"),")."),(0,n.kt)("li",{parentName:"ol"},"Then the primary repairs its prepares. (",(0,n.kt)("a",{parentName:"li",href:"#protocol-repair-wal"},"Protocol: Repair WAL"),") (and potentially truncates uncommitted ops)."),(0,n.kt)("li",{parentName:"ol"},"Then primary commits all prepares which are not known to be uncommitted."),(0,n.kt)("li",{parentName:"ol"},"Then the primary transitions to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=normal")," and broadcasts a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=start_view"),".")),(0,n.kt)("h3",{id:"protocol-requeststart-view"},"Protocol: Request/Start View"),(0,n.kt)("h4",{id:"request_start_view"},(0,n.kt)("inlineCode",{parentName:"h4"},"request_start_view")),(0,n.kt)("p",null,"A backup sends a ",(0,n.kt)("inlineCode",{parentName:"p"},"command=request_start_view")," to the primary of a view when any of the following occur:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"the backup learns about a newer view via a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=commit")," message, or"),(0,n.kt)("li",{parentName:"ul"},"the backup learns about a newer view via a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare")," message, or"),(0,n.kt)("li",{parentName:"ul"},"the backup discovers ",(0,n.kt)("inlineCode",{parentName:"li"},"commit_max")," exceeds ",(0,n.kt)("inlineCode",{parentName:"li"},"min(op_head, op_checkpoint_next_trigger)")," (during repair),"),(0,n.kt)("li",{parentName:"ul"},"the backup can't make progress committing and needs to state sync, or"),(0,n.kt)("li",{parentName:"ul"},"a replica recovers to ",(0,n.kt)("inlineCode",{parentName:"li"},"status=recovering_head"))),(0,n.kt)("h4",{id:"start_view"},(0,n.kt)("inlineCode",{parentName:"h4"},"start_view")),(0,n.kt)("p",null,"When a ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal")," primary receives ",(0,n.kt)("inlineCode",{parentName:"p"},"command=request_start_view"),", it replies with a ",(0,n.kt)("inlineCode",{parentName:"p"},"command=start_view"),".\n",(0,n.kt)("inlineCode",{parentName:"p"},"command=start_view")," includes:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The view's current suffix \u2014 the headers of the latest messages in the view."),(0,n.kt)("li",{parentName:"ul"},"The current checkpoint (see ",(0,n.kt)("a",{parentName:"li",href:"/about/internals/sync"},"State Sync"),").")),(0,n.kt)("p",null,"Together, the checkpoint and the view headers fully specify the logical and physical state of the view."),(0,n.kt)("p",null,"Upon receiving a ",(0,n.kt)("inlineCode",{parentName:"p"},"start_view")," for the new view, the backup installs the checkpoint if needed, installs the suffix, transitions to ",(0,n.kt)("inlineCode",{parentName:"p"},"status=normal"),", and begins repair."),(0,n.kt)("p",null,"A ",(0,n.kt)("inlineCode",{parentName:"p"},"start_view")," contains the following headers (which may overlap):"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The suffix: ",(0,n.kt)("inlineCode",{parentName:"li"},"pipeline_prepare_queue_max")," headers from the head op down."),(0,n.kt)("li",{parentName:"ul"},'The "hooks": the header of any previous checkpoint triggers within our repairable range.\nThis helps a lagging replica catch up. (There are at most 2).')),(0,n.kt)("h3",{id:"protocol-repair-journal"},"Protocol: Repair Journal"),(0,n.kt)("p",null,(0,n.kt)("inlineCode",{parentName:"p"},"request_headers")," and ",(0,n.kt)("inlineCode",{parentName:"p"},"headers")," repair gaps or breaks in a replica's journal headers.\nRepaired headers are a prerequisite for ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-wal"},"repairing prepares"),"."),(0,n.kt)("p",null,"Because the headers are repaired backwards (from the head) by hash-chaining, it is safe for both backups and transitioning primaries."),(0,n.kt)("p",null,"Gaps/breaks in a replica's journal headers may occur:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"On a backup, receiving nonconsecutive ops, leaving a gap in its headers."),(0,n.kt)("li",{parentName:"ul"},"On a backup, which has not finished repair."),(0,n.kt)("li",{parentName:"ul"},"On a new primary during a view-change, which has not finished repair.")),(0,n.kt)("h3",{id:"protocol-repair-wal"},"Protocol: Repair WAL"),(0,n.kt)("p",null,"The replica's journal tracks which prepares the WAL requires \u2014 i.e. headers for which either:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"no prepare was ever received, or"),(0,n.kt)("li",{parentName:"ul"},"the prepare was received and written, but was since discovered to be corrupt")),(0,n.kt)("p",null,"During repair, missing/damaged prepares are requested & repaired chronologically, which:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"improves the chances that older entries will be available, i.e. not yet overwritten"),(0,n.kt)("li",{parentName:"ul"},"enables better pipelining of repair and commit.")),(0,n.kt)("p",null,"In response to a ",(0,n.kt)("inlineCode",{parentName:"p"},"request_prepare"),":"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Reply the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=prepare")," with the requested prepare, if available and valid."),(0,n.kt)("li",{parentName:"ul"},"Otherwise do not reply. (e.g. the corresponding slot in the WAL is corrupt)")),(0,n.kt)("p",null,"Per ",(0,n.kt)("a",{parentName:"p",href:"https://www.usenix.org/system/files/conference/fast18/fast18-alagappan.pdf"},"PAR's CTRL Protocol"),", we do not nack corrupt entries, since they ",(0,n.kt)("em",{parentName:"p"},"might")," be the prepare being requested."),(0,n.kt)("p",null,"See also ",(0,n.kt)("a",{parentName:"p",href:"/about/internals/sync"},"State Sync")," protocol \u2014 the extent of WAL that the replica can/should repair\ndepends on the checkpoint."),(0,n.kt)("h3",{id:"protocol-repair-client-replies"},"Protocol: Repair Client Replies"),(0,n.kt)("p",null,"The replica stores the latest reply to each active client."),(0,n.kt)("p",null,"During repair, corrupt client replies are requested & repaired."),(0,n.kt)("p",null,"In response to a ",(0,n.kt)("inlineCode",{parentName:"p"},"request_reply"),":"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Respond with the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=reply")," (the requested reply), if available and valid."),(0,n.kt)("li",{parentName:"ul"},"Otherwise do not reply.")),(0,n.kt)("h3",{id:"protocol-client"},"Protocol: Client"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"Client sends ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request operation=register")," to registers with the cluster by starting a new request-reply hashchain. (See also: ",(0,n.kt)("a",{parentName:"li",href:"#protocol-normal"},"Protocol: Normal"),")."),(0,n.kt)("li",{parentName:"ol"},"Client receives ",(0,n.kt)("inlineCode",{parentName:"li"},"command=reply operation=register")," from the cluster. (If the cluster is at the maximum number of clients, it evicts the oldest)."),(0,n.kt)("li",{parentName:"ol"},"Repeat:",(0,n.kt)("ol",{parentName:"li"},(0,n.kt)("li",{parentName:"ol"},"Send ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request")," to cluster."),(0,n.kt)("li",{parentName:"ol"},"If the client has been evicted, receive ",(0,n.kt)("inlineCode",{parentName:"li"},"command=eviction")," from the cluster. (The client must re-register before sending more requests.)"),(0,n.kt)("li",{parentName:"ol"},"If the client has not been evicted, receive ",(0,n.kt)("inlineCode",{parentName:"li"},"command=reply")," from cluster.")))),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/reference/sessions#lifecycle"},"Integration: Client Session Lifecycle")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/reference/sessions#eviction"},"Integration: Client Session Eviction"))),(0,n.kt)("h3",{id:"protocol-repair-grid"},"Protocol: Repair Grid"),(0,n.kt)("p",null,"Grid repair is triggered when a replica discovers a corrupt (or missing) grid block."),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"The repairing replica sends a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request_blocks")," to any other replica. The message body contains a list of block ",(0,n.kt)("inlineCode",{parentName:"li"},"address"),"/",(0,n.kt)("inlineCode",{parentName:"li"},"checksum"),"s."),(0,n.kt)("li",{parentName:"ol"},"Upon receiving a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=request_blocks"),", a replica reads its own grid to check for the requested blocks. For each matching block found, reply with the ",(0,n.kt)("inlineCode",{parentName:"li"},"command=block")," message (the block itself)."),(0,n.kt)("li",{parentName:"ol"},"Upon receiving a ",(0,n.kt)("inlineCode",{parentName:"li"},"command=block"),", a replica writes the block to its grid, and resolves the reads that were blocked on it.")),(0,n.kt)("p",null,"Note that ",(0,n.kt)("em",{parentName:"p"},"both sides")," of grid repair can run while the grid is being opened during replica startup.\nThat is, a replica can help other replicas repair and repair itself simultaneously."),(0,n.kt)("p",null,"TODO Describe state sync fallback."),(0,n.kt)("h3",{id:"protocol-sync-client-replies"},"Protocol: Sync Client Replies"),(0,n.kt)("p",null,"Sync missed client replies using ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-client-replies"},"Protocol: Repair Grid"),"."),(0,n.kt)("p",null,"See ",(0,n.kt)("a",{parentName:"p",href:"/about/internals/sync"},"State Sync")," for details."),(0,n.kt)("h3",{id:"protocol-sync-forest"},"Protocol: Sync Forest"),(0,n.kt)("p",null,"Sync missed LSM manifest and table blocks using ",(0,n.kt)("a",{parentName:"p",href:"#protocol-repair-grid"},"Protocol: Repair Grid"),"."),(0,n.kt)("p",null,"See ",(0,n.kt)("a",{parentName:"p",href:"/about/internals/sync"},"State Sync")," for details."),(0,n.kt)("h3",{id:"protocol-reconfiguration"},"Protocol: Reconfiguration"),(0,n.kt)("p",null,"TODO (Unimplemented)"),(0,n.kt)("h2",{id:"quorums"},"Quorums"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"replication quorum")," is the minimum number of replicas required to complete a commit."),(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"view-change quorum")," is the minimum number of replicas required to complete a view-change."),(0,n.kt)("li",{parentName:"ul"},"The ",(0,n.kt)("em",{parentName:"li"},"nack quorum")," is the minimum number of unique nacks required to truncate an uncommitted op.")),(0,n.kt)("p",null,"With the default configuration:"),(0,n.kt)("table",null,(0,n.kt)("thead",{parentName:"table"},(0,n.kt)("tr",{parentName:"thead"},(0,n.kt)("th",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"th"},"Replica Count")),(0,n.kt)("th",{parentName:"tr",align:"right"},"1"),(0,n.kt)("th",{parentName:"tr",align:"right"},"2"),(0,n.kt)("th",{parentName:"tr",align:"right"},"3"),(0,n.kt)("th",{parentName:"tr",align:"right"},"4"),(0,n.kt)("th",{parentName:"tr",align:"right"},"5"),(0,n.kt)("th",{parentName:"tr",align:"right"},"6"))),(0,n.kt)("tbody",{parentName:"table"},(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"Replication Quorum")),(0,n.kt)("td",{parentName:"tr",align:"right"},"1"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"View-Change Quorum")),(0,n.kt)("td",{parentName:"tr",align:"right"},"1"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"4")),(0,n.kt)("tr",{parentName:"tbody"},(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"Nack Quorum")),(0,n.kt)("td",{parentName:"tr",align:"right"},"1"),(0,n.kt)("td",{parentName:"tr",align:"right"},(0,n.kt)("strong",{parentName:"td"},"1")),(0,n.kt)("td",{parentName:"tr",align:"right"},"2"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"3"),(0,n.kt)("td",{parentName:"tr",align:"right"},"4")))),(0,n.kt)("p",null,"See also:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("inlineCode",{parentName:"li"},"constants.quorum_replication_max")," for configuration."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://fpaxos.github.io/"},"Flexible Paxos"))),(0,n.kt)("h2",{id:"further-reading"},"Further reading"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://pmg.csail.mit.edu/papers/vr-revisited.pdf"},"Viewstamped Replication Revisited")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://www.usenix.org/system/files/conference/fast18/fast18-alagappan.pdf"},"Protocol Aware Recovery"))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9b0eb4fb.ae095492.js b/assets/js/9b0eb4fb.ae095492.js new file mode 100644 index 00000000..31733e92 --- /dev/null +++ b/assets/js/9b0eb4fb.ae095492.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[885],{3905:(t,e,n)=>{n.d(e,{Zo:()=>m,kt:()=>s});var a=n(7294);function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,a)}return n}function l(t){for(var e=1;e=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}var o=a.createContext({}),p=function(t){var e=a.useContext(o),n=e;return t&&(n="function"==typeof t?t(e):l(l({},e),t)),n},m=function(t){var e=p(t.components);return a.createElement(o.Provider,{value:e},t.children)},g="mdxType",k={inlineCode:"code",wrapper:function(t){var e=t.children;return a.createElement(a.Fragment,{},e)}},c=a.forwardRef((function(t,e){var n=t.components,r=t.mdxType,i=t.originalType,o=t.parentName,m=d(t,["components","mdxType","originalType","parentName"]),g=p(n),c=r,s=g["".concat(o,".").concat(c)]||g[c]||k[c]||i;return n?a.createElement(s,l(l({ref:e},m),{},{components:n})):a.createElement(s,l({ref:e},m))}));function s(t,e){var n=arguments,r=e&&e.mdxType;if("string"==typeof t||r){var i=n.length,l=new Array(i);l[0]=c;var d={};for(var o in e)hasOwnProperty.call(e,o)&&(d[o]=e[o]);d.originalType=t,d[g]="string"==typeof t?t:r,l[1]=d;for(var p=2;p{n.r(e),n.d(e,{assets:()=>o,contentTitle:()=>l,default:()=>k,frontMatter:()=>i,metadata:()=>d,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const i={sidebar_position:2},l="Multi-Debit, Multi-Credit Transfers",d={unversionedId:"coding/recipes/multi-debit-credit-transfers",id:"coding/recipes/multi-debit-credit-transfers",title:"Multi-Debit, Multi-Credit Transfers",description:"TigerBeetle is designed for maximum performance. In order to keep it lean, the database only",source:"@site/pages/coding/recipes/multi-debit-credit-transfers.md",sourceDirName:"coding/recipes",slug:"/coding/recipes/multi-debit-credit-transfers",permalink:"/coding/recipes/multi-debit-credit-transfers",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/coding/recipes/multi-debit-credit-transfers.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Currency Exchange",permalink:"/coding/recipes/currency-exchange"},next:{title:"Close Account",permalink:"/coding/recipes/close-account"}},o={},p=[{value:"One-to-Many Transfers",id:"one-to-many-transfers",level:2},{value:"Single Debit, Multiple Credits",id:"single-debit-multiple-credits",level:3},{value:"Multiple Debits, Single Credit",id:"multiple-debits-single-credit",level:3},{value:"Multiple Debits, Single Credit, Balancing debits",id:"multiple-debits-single-credit-balancing-debits",level:3},{value:"Many-to-Many Transfers",id:"many-to-many-transfers",level:2}],m={toc:p},g="wrapper";function k(t){let{components:e,...n}=t;return(0,r.kt)(g,(0,a.Z)({},m,n,{components:e,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"multi-debit-multi-credit-transfers"},"Multi-Debit, Multi-Credit Transfers"),(0,r.kt)("p",null,"TigerBeetle is designed for maximum performance. In order to keep it lean, the database only\nsupports simple transfers with a single debit and a single credit."),(0,r.kt)("p",null,"However, you'll probably run into cases where you want transactions with multiple debits and/or\ncredits. For example, you might have a transfer where you want to extract fees and/or taxes."),(0,r.kt)("p",null,"Read on to see how to implement one-to-many and many-to-many transfers!"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note that all of these examples use the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},"Linked Transfers flag (",(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked"),")")," to ensure\nthat all of the transfers succeed or fail together.")),(0,r.kt)("h2",{id:"one-to-many-transfers"},"One-to-Many Transfers"),(0,r.kt)("p",null,"Transactions that involve multiple debits and a single credit OR a single debit and multiple credits\nare relatively straightforward."),(0,r.kt)("p",null,"You can use multiple linked transfers as depicted below."),(0,r.kt)("h3",{id:"single-debit-multiple-credits"},"Single Debit, Multiple Credits"),(0,r.kt)("p",null,"This example debits a single account and credits multiple accounts. It uses the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"source account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A"),", on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"destination accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"Y"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"Z"),", on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"th"},"flags.linked")))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Y")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Z")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("h3",{id:"multiple-debits-single-credit"},"Multiple Debits, Single Credit"),(0,r.kt)("p",null,"This example debits multiple accounts and credits a single account. It uses the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"source accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"B"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"C")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"th"},"flags.linked")))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"B")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"C")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("h3",{id:"multiple-debits-single-credit-balancing-debits"},"Multiple Debits, Single Credit, Balancing debits"),(0,r.kt)("p",null,"This example debits multiple accounts and credits a single account.\nThe total amount to transfer to the credit account is known (in this case, ",(0,r.kt)("inlineCode",{parentName:"p"},"100"),"), but the balances\nof the individual debit accounts are not known. That is, each debit account should contribute as\nmuch as possible (in order of precedence) up to the target, cumulative transfer amount."),(0,r.kt)("p",null,"It uses the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"source accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"B"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"C"),", with ",(0,r.kt)("a",{parentName:"li",href:"/reference/account#flagsdebits_must_not_exceed_credits"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.debits_must_not_exceed_credits")),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X"),"."),(0,r.kt)("li",{parentName:"ul"},"A control account ",(0,r.kt)("inlineCode",{parentName:"li"},"LIMIT"),", with ",(0,r.kt)("a",{parentName:"li",href:"/reference/account#flagsdebits_must_not_exceed_credits"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.debits_must_not_exceed_credits")),"."),(0,r.kt)("li",{parentName:"ul"},"A control account ",(0,r.kt)("inlineCode",{parentName:"li"},"SETUP"),", for setting up the ",(0,r.kt)("inlineCode",{parentName:"li"},"LIMIT")," account.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Id"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"left"},"Flags"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"1"),(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"SETUP")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"LIMIT")),(0,r.kt)("td",{parentName:"tr",align:"right"},"100"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"2"),(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"SETUP")),(0,r.kt)("td",{parentName:"tr",align:"right"},"100"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"linked")),", ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_debit")),", ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_credit")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"3"),(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"B")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"SETUP")),(0,r.kt)("td",{parentName:"tr",align:"right"},"100"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"linked")),", ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_debit")),", ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_credit")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"4"),(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"C")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"SETUP")),(0,r.kt)("td",{parentName:"tr",align:"right"},"100"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"linked")),", ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_debit")),", ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_credit")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"5"),(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"SETUP")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"100"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"6"),(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"LIMIT")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"SETUP")),(0,r.kt)("td",{parentName:"tr",align:"right"},"100"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_credit")))))),(0,r.kt)("p",null,"If the cumulative ",(0,r.kt)("a",{parentName:"p",href:"/coding/data-modeling#credit-balances"},"credit balance")," of ",(0,r.kt)("inlineCode",{parentName:"p"},"A + B + C")," is less than\n",(0,r.kt)("inlineCode",{parentName:"p"},"100"),", the chain will fail (transfer ",(0,r.kt)("inlineCode",{parentName:"p"},"6")," will return ",(0,r.kt)("inlineCode",{parentName:"p"},"exceeds_credits"),")."),(0,r.kt)("h2",{id:"many-to-many-transfers"},"Many-to-Many Transfers"),(0,r.kt)("p",null,"Transactions with multiple debits and multiple credits are a bit more involved (but you got this!)."),(0,r.kt)("p",null,"This is where the accounting concept of a Control Account comes in handy. We can use this as an\nintermediary account, as illustrated below."),(0,r.kt)("p",null,"In this example, we'll use the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Two ",(0,r.kt)("em",{parentName:"li"},"source accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"B")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"destination accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"Y"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"Z"),", on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"compound entry control account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"Control")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"th"},"flags.linked")))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"B")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"9000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Y")),(0,r.kt)("td",{parentName:"tr",align:"right"},"1000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Z")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("p",null,"Here, we use two transfers to debit accounts ",(0,r.kt)("inlineCode",{parentName:"p"},"A")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"B")," and credit the ",(0,r.kt)("inlineCode",{parentName:"p"},"Control")," account, and\nanother three transfers to credit accounts ",(0,r.kt)("inlineCode",{parentName:"p"},"X"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"Y"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"Z"),"."),(0,r.kt)("p",null,"If you looked closely at this example, you may have noticed that we could have debited ",(0,r.kt)("inlineCode",{parentName:"p"},"B")," and\ncredited ",(0,r.kt)("inlineCode",{parentName:"p"},"Z")," directly because the amounts happened to line up. That is true!"),(0,r.kt)("p",null,"For a little more extreme performance, you ",(0,r.kt)("em",{parentName:"p"},"might")," consider implementing logic to circumvent the\ncontrol account where possible, to reduce the number of transfers to implement a compound journal\nentry."),(0,r.kt)("p",null,"However, if you're just getting started, you can avoid premature optimizations (we've all been\nthere!). You may find it easier to program these compound journal entries ",(0,r.kt)("em",{parentName:"p"},"always")," using a control\naccount -- and you can then come back to squeeze this performance out later!"))}k.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9b0eb4fb.bc411fef.js b/assets/js/9b0eb4fb.bc411fef.js deleted file mode 100644 index 03c3ccc6..00000000 --- a/assets/js/9b0eb4fb.bc411fef.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[885],{3905:(t,e,n)=>{n.d(e,{Zo:()=>m,kt:()=>u});var a=n(7294);function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,a)}return n}function l(t){for(var e=1;e=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}var d=a.createContext({}),p=function(t){var e=a.useContext(d),n=e;return t&&(n="function"==typeof t?t(e):l(l({},e),t)),n},m=function(t){var e=p(t.components);return a.createElement(d.Provider,{value:e},t.children)},s="mdxType",g={inlineCode:"code",wrapper:function(t){var e=t.children;return a.createElement(a.Fragment,{},e)}},c=a.forwardRef((function(t,e){var n=t.components,r=t.mdxType,i=t.originalType,d=t.parentName,m=o(t,["components","mdxType","originalType","parentName"]),s=p(n),c=r,u=s["".concat(d,".").concat(c)]||s[c]||g[c]||i;return n?a.createElement(u,l(l({ref:e},m),{},{components:n})):a.createElement(u,l({ref:e},m))}));function u(t,e){var n=arguments,r=e&&e.mdxType;if("string"==typeof t||r){var i=n.length,l=new Array(i);l[0]=c;var o={};for(var d in e)hasOwnProperty.call(e,d)&&(o[d]=e[d]);o.originalType=t,o[s]="string"==typeof t?t:r,l[1]=o;for(var p=2;p{n.r(e),n.d(e,{assets:()=>d,contentTitle:()=>l,default:()=>g,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const i={sidebar_position:2},l="Multi-Debit, Multi-Credit Transfers",o={unversionedId:"coding/recipes/multi-debit-credit-transfers",id:"coding/recipes/multi-debit-credit-transfers",title:"Multi-Debit, Multi-Credit Transfers",description:"TigerBeetle is designed for maximum performance. In order to keep it lean, the database only",source:"@site/pages/coding/recipes/multi-debit-credit-transfers.md",sourceDirName:"coding/recipes",slug:"/coding/recipes/multi-debit-credit-transfers",permalink:"/coding/recipes/multi-debit-credit-transfers",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/coding/recipes/multi-debit-credit-transfers.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Currency Exchange",permalink:"/coding/recipes/currency-exchange"},next:{title:"Close Account",permalink:"/coding/recipes/close-account"}},d={},p=[{value:"One-to-Many Transfers",id:"one-to-many-transfers",level:2},{value:"Single Debit, Multiple Credits",id:"single-debit-multiple-credits",level:3},{value:"Multiple Debits, Single Credit",id:"multiple-debits-single-credit",level:3},{value:"Many-to-Many Transfers",id:"many-to-many-transfers",level:2}],m={toc:p},s="wrapper";function g(t){let{components:e,...n}=t;return(0,r.kt)(s,(0,a.Z)({},m,n,{components:e,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"multi-debit-multi-credit-transfers"},"Multi-Debit, Multi-Credit Transfers"),(0,r.kt)("p",null,"TigerBeetle is designed for maximum performance. In order to keep it lean, the database only\nsupports simple transfers with a single debit and a single credit."),(0,r.kt)("p",null,"However, you'll probably run into cases where you want transactions with multiple debits and/or\ncredits. For example, you might have a transfer where you want to extract fees and/or taxes."),(0,r.kt)("p",null,"Read on to see how to implement one-to-many and many-to-many transfers!"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note that all of these examples use the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},"Linked Transfers flag (",(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked"),")")," to ensure\nthat all of the transfers succeed or fail together.")),(0,r.kt)("h2",{id:"one-to-many-transfers"},"One-to-Many Transfers"),(0,r.kt)("p",null,"Transactions that involve multiple debits and a single credit OR a single debit and multiple credits\nare relatively straightforward."),(0,r.kt)("p",null,"You can use multiple linked transfers as depicted below."),(0,r.kt)("h3",{id:"single-debit-multiple-credits"},"Single Debit, Multiple Credits"),(0,r.kt)("p",null,"This example debits a single account and credits multiple accounts. It uses the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"source account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A"),", on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"destination accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"Y"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"Z"),", on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"th"},"flags.linked")))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Y")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Z")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("h3",{id:"multiple-debits-single-credit"},"Multiple Debits, Single Credit"),(0,r.kt)("p",null,"This example debits multiple accounts and credits a single account. It uses the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"source accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"B"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"C")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"th"},"flags.linked")))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"B")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"C")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("h2",{id:"many-to-many-transfers"},"Many-to-Many Transfers"),(0,r.kt)("p",null,"Transactions with multiple debits and multiple credits are a bit more involved (but you got this!)."),(0,r.kt)("p",null,"This is where the accounting concept of a Control Account comes in handy. We can use this as an\nintermediary account, as illustrated below."),(0,r.kt)("p",null,"In this example, we'll use the following accounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Two ",(0,r.kt)("em",{parentName:"li"},"source accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"B")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"Three ",(0,r.kt)("em",{parentName:"li"},"destination accounts")," ",(0,r.kt)("inlineCode",{parentName:"li"},"X"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"Y"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"Z"),", on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"compound entry control account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"Control")," on the ",(0,r.kt)("inlineCode",{parentName:"li"},"USD")," ledger.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"right"},"Ledger"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:"right"},"Amount"),(0,r.kt)("th",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"th"},"flags.linked")))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"B")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"X")),(0,r.kt)("td",{parentName:"tr",align:"right"},"9000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Y")),(0,r.kt)("td",{parentName:"tr",align:"right"},"1000"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"USD"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Control")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"Z")),(0,r.kt)("td",{parentName:"tr",align:"right"},"50"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("p",null,"Here, we use two transfers to debit accounts ",(0,r.kt)("inlineCode",{parentName:"p"},"A")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"B")," and credit the ",(0,r.kt)("inlineCode",{parentName:"p"},"Control")," account, and\nanother three transfers to credit accounts ",(0,r.kt)("inlineCode",{parentName:"p"},"X"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"Y"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"Z"),"."),(0,r.kt)("p",null,"If you looked closely at this example, you may have noticed that we could have debited ",(0,r.kt)("inlineCode",{parentName:"p"},"B")," and\ncredited ",(0,r.kt)("inlineCode",{parentName:"p"},"Z")," directly because the amounts happened to line up. That is true!"),(0,r.kt)("p",null,"For a little more extreme performance, you ",(0,r.kt)("em",{parentName:"p"},"might")," consider implementing logic to circumvent the\ncontrol account where possible, to reduce the number of transfers to implement a compound journal\nentry."),(0,r.kt)("p",null,"However, if you're just getting started, you can avoid premature optimizations (we've all been\nthere!). You may find it easier to program these compound journal entries ",(0,r.kt)("em",{parentName:"p"},"always")," using a control\naccount -- and you can then come back to squeeze this performance out later!"))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ac624c80.6b6801d8.js b/assets/js/ac624c80.6b6801d8.js deleted file mode 100644 index a7226abc..00000000 --- a/assets/js/ac624c80.6b6801d8.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5511],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var o=a.createContext({}),c=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},s=function(e){var t=c(e.components);return a.createElement(o.Provider,{value:t},e.children)},m="mdxType",k={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,o=e.parentName,s=p(e,["components","mdxType","originalType","parentName"]),m=c(n),u=r,h=m["".concat(o,".").concat(u)]||m[u]||k[u]||i;return n?a.createElement(h,l(l({ref:t},s),{},{components:n})):a.createElement(h,l({ref:t},s))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=u;var p={};for(var o in t)hasOwnProperty.call(t,o)&&(p[o]=t[o]);p.originalType=e,p[m]="string"==typeof e?e:r,l[1]=p;for(var c=2;c{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>k,frontMatter:()=>i,metadata:()=>p,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const i={sidebar_position:4},l="State Sync",p={unversionedId:"about/internals/sync",id:"about/internals/sync",title:"State Sync",description:"State sync synchronizes the state of a lagging replica with the healthy cluster.",source:"@site/pages/about/internals/sync.md",sourceDirName:"about/internals",slug:"/about/internals/sync",permalink:"/about/internals/sync",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/sync.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"LSM",permalink:"/about/internals/lsm"},next:{title:"Testing",permalink:"/about/internals/testing"}},o={},c=[{value:"Glossary",id:"glossary",level:2},{value:"Algorithm",id:"algorithm",level:2},{value:"0: Scenarios",id:"0-scenarios",level:3},{value:"1: Triggers",id:"1-triggers",level:3},{value:"6: Request Superblock Checkpoint State",id:"6-request-superblock-checkpoint-state",level:3},{value:"Concepts",id:"concepts",level:2},{value:"Syncing Replica",id:"syncing-replica",level:3},{value:"Syncing Replicas write prepares to their WAL.",id:"syncing-replicas-write-prepares-to-their-wal",level:4},{value:"Syncing Replicas don't ack prepares.",id:"syncing-replicas-dont-ack-prepares",level:4},{value:"Checkpoint Identifier",id:"checkpoint-identifier",level:3},{value:"Sync Target",id:"sync-target",level:3},{value:"Storage Determinism",id:"storage-determinism",level:3}],s={toc:c},m="wrapper";function k(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"state-sync"},"State Sync"),(0,r.kt)("p",null,"State sync synchronizes the state of a lagging replica with the healthy cluster."),(0,r.kt)("p",null,"State sync is used when when a lagging replica's log no longer intersects with the cluster's current\nlog \u2014 ",(0,r.kt)("a",{parentName:"p",href:"/about/internals/vsr#protocol-repair-wal"},"WAL repair")," cannot catch the replica up."),(0,r.kt)("p",null,'(VRR refers to state sync as "state transfer", but we already have\n',(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},"transfers")," elsewhere.)"),(0,r.kt)("p",null,'In the context of state sync, "state" refers to:'),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"the superblock ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.checkpoint")),(0,r.kt)("li",{parentName:"ol"},"the grid (manifest, free set, and client sessions blocks)"),(0,r.kt)("li",{parentName:"ol"},"the grid (LSM table data; acquired blocks only)"),(0,r.kt)("li",{parentName:"ol"},"client replies")),(0,r.kt)("p",null,"State sync consists of four protocols:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-superblock"},"Sync Superblock")," (syncs 1)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-repair-grid"},"Repair Grid")," (syncs 2)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-forest"},"Sync Forest")," (syncs 3)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-client-replies"},"Sync Client Replies")," (syncs 4)")),(0,r.kt)("p",null,"The target of superblock-sync is the latest checkpoint of the healthy cluster. When we catch up to\nthe latest checkpoint (or very close to it), then we can transition back to a healthy state."),(0,r.kt)("h2",{id:"glossary"},"Glossary"),(0,r.kt)("p",null,"Replica roles:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("em",{parentName:"li"},"syncing replica"),": A replica performing superblock-sync. (Any step within ",(0,r.kt)("em",{parentName:"li"},"1"),"-",(0,r.kt)("em",{parentName:"li"},"10")," of the\n",(0,r.kt)("a",{parentName:"li",href:"#algorithm"},"sync algorithm"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("em",{parentName:"li"},"healthy replica"),": A replica ",(0,r.kt)("em",{parentName:"li"},"not")," performing superblock-sync \u2014 part of the active cluster."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("em",{parentName:"li"},"divergent replica"),": A replica with a checkpoint that is (and can never be) canonical.")),(0,r.kt)("p",null,"Checkpoints:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#checkpoint-identifier"},(0,r.kt)("em",{parentName:"a"},"checkpoint id"),"/",(0,r.kt)("em",{parentName:"a"},"checkpoint identifier")),": Uniquely identifies a\nparticular checkpoint reproducibly across replicas."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#sync-target"},(0,r.kt)("em",{parentName:"a"},"sync target")),": The checkpoint identifier of the target of superblock-sync.")),(0,r.kt)("h2",{id:"algorithm"},"Algorithm"),(0,r.kt)("ol",{start:0},(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("a",{parentName:"li",href:"#0-scenarios"},"Sync is needed"),"."),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("a",{parentName:"li",href:"#1-triggers"},"Trigger sync"),"."),(0,r.kt)("li",{parentName:"ol"},"Wait for non-grid commit operation to finish."),(0,r.kt)("li",{parentName:"ol"},"Wait for grid IO to finish. (See ",(0,r.kt)("inlineCode",{parentName:"li"},"Grid.cancel()"),".)"),(0,r.kt)("li",{parentName:"ol"},"Wait for a usable sync target to arrive. (Usually we already have one.)"),(0,r.kt)("li",{parentName:"ol"},"Begin ",(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-superblock"},"sync-superblock protocol"),"."),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("a",{parentName:"li",href:"#6-request-superblock-checkpoint-state"},"Request superblock checkpoint state"),"."),(0,r.kt)("li",{parentName:"ol"},"Update the superblock headers with:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"Bump ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.checkpoint.header")," to the sync target header."),(0,r.kt)("li",{parentName:"ul"},"Bump ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.checkpoint.parent_checkpoint_id")," to the checkpoint id that is previous to our\nsync target (i.e. it isn't ",(0,r.kt)("em",{parentName:"li"},"our")," previous checkpoint)."),(0,r.kt)("li",{parentName:"ul"},"Bump ",(0,r.kt)("inlineCode",{parentName:"li"},"replica.commit_min"),". (If ",(0,r.kt)("inlineCode",{parentName:"li"},"replica.commit_min")," exceeds ",(0,r.kt)("inlineCode",{parentName:"li"},"replica.op"),", transition to\n",(0,r.kt)("inlineCode",{parentName:"li"},"status=recovering_head"),")."),(0,r.kt)("li",{parentName:"ul"},"Set ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_min")," to the minimum op which has not been repaired."),(0,r.kt)("li",{parentName:"ul"},"Set ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_max")," to the maximum op which has not been repaired."))),(0,r.kt)("li",{parentName:"ol"},"Sync-superblock protocol is done."),(0,r.kt)("li",{parentName:"ol"},"Repair ",(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-client-replies"},"replies"),",\n",(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-repair-grid"},"free set, client sessions, and manifest blocks"),", and\n",(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-forest"},"table blocks")," that were created within the ",(0,r.kt)("inlineCode",{parentName:"li"},"sync_op_{min,max}"),"\nrange."),(0,r.kt)("li",{parentName:"ol"},"Update the superblock with:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"Set ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_min = 0")),(0,r.kt)("li",{parentName:"ul"},"Set ",(0,r.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_max = 0"))))),(0,r.kt)("p",null,"If a newer sync target is discovered during steps ",(0,r.kt)("em",{parentName:"p"},"5"),"-",(0,r.kt)("em",{parentName:"p"},"6")," or ",(0,r.kt)("em",{parentName:"p"},"9"),", go to step ",(0,r.kt)("em",{parentName:"p"},"4"),"."),(0,r.kt)("p",null,"If the replica starts up with ",(0,r.kt)("inlineCode",{parentName:"p"},"vsr_state.sync_op_max \u2260 0"),", go to step ",(0,r.kt)("em",{parentName:"p"},"9"),"."),(0,r.kt)("h3",{id:"0-scenarios"},"0: Scenarios"),(0,r.kt)("p",null,"Scenarios requiring state sync:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"A replica was down/partitioned/slow for a while and the rest of the cluster moved on. The lagging\nreplica is too far behind to catch up via WAL repair."),(0,r.kt)("li",{parentName:"ol"},"A replica was just formatted and is being added to the cluster (i.e. via\n",(0,r.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-reconfiguration"},"reconfiguration"),"). The new replica is too far behind to catch\nup via WAL repair.")),(0,r.kt)("p",null,"Causes of number 3:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A storage determinism bug."),(0,r.kt)("li",{parentName:"ul"},"An upgraded replica (e.g. a canary) running a different version of the code from the remainder of\nthe cluster, which unexpectedly changes its history. (The change either has a bug or should have\nbeen gated behind a feature flag.)")),(0,r.kt)("h3",{id:"1-triggers"},"1: Triggers"),(0,r.kt)("p",null,"State sync is initially triggered by any of the following:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"The replica receives a SV which indicates that it has lagged so far behind the cluster that its\nlog cannot possibly intersect."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"repair_sync_timeout")," fires, and:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"a WAL or grid repair is in progress and,"),(0,r.kt)("li",{parentName:"ul"},"the replica's checkpoint is lagging behind the cluster's (far enough that the repair may never\ncomplete).")))),(0,r.kt)("h3",{id:"6-request-superblock-checkpoint-state"},"6: Request Superblock Checkpoint State"),(0,r.kt)("p",null,"The syncing replica sends ",(0,r.kt)("inlineCode",{parentName:"p"},"command=request_sync_checkpoint")," messages (with the sync target\nidentifier attached to each) until it receives a ",(0,r.kt)("inlineCode",{parentName:"p"},"command=sync_checkpoint")," with a matching\ncheckpoint identifier."),(0,r.kt)("h2",{id:"concepts"},"Concepts"),(0,r.kt)("h3",{id:"syncing-replica"},"Syncing Replica"),(0,r.kt)("p",null,"Syncing replicas may:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#syncing-replicas-write-prepares-to-their-wal"},"write prepares to their WAL")),(0,r.kt)("li",{parentName:"ul"},"assist with grid repair"),(0,r.kt)("li",{parentName:"ul"},"join new views"),(0,r.kt)("li",{parentName:"ul"},"send a ",(0,r.kt)("inlineCode",{parentName:"li"},"do_view_change"))),(0,r.kt)("p",null,"Syncing replicas must not:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#syncing-replicas-dont-ack-prepares"},"ack")),(0,r.kt)("li",{parentName:"ul"},"commit prepares"),(0,r.kt)("li",{parentName:"ul"},"be a primary")),(0,r.kt)("h4",{id:"syncing-replicas-write-prepares-to-their-wal"},"Syncing Replicas write prepares to their WAL."),(0,r.kt)("p",null,"When the replica completes superblock-sync, an up-to-date WAL and journal allow it to quickly catch\nup (i.e. commit) to the current cluster state."),(0,r.kt)("h4",{id:"syncing-replicas-dont-ack-prepares"},"Syncing Replicas don't ack prepares."),(0,r.kt)("p",null,"If syncing replicas ",(0,r.kt)("em",{parentName:"p"},"did")," ack prepares:"),(0,r.kt)("p",null,"Consider a cluster of 3 replicas:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"the ",(0,r.kt)("em",{parentName:"li"},"primary"),","),(0,r.kt)("li",{parentName:"ul"},"a ",(0,r.kt)("em",{parentName:"li"},"normal backup"),", and"),(0,r.kt)("li",{parentName:"ul"},"a ",(0,r.kt)("em",{parentName:"li"},"syncing backup"),".")),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("em",{parentName:"li"},"Primary")," prepares many ops..."),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("em",{parentName:"li"},"Syncing backup")," prepares and acknowledges all of those messages."),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("em",{parentName:"li"},"Normal backup")," is partitioned \u2014 its not seeing any of these prepares."),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("em",{parentName:"li"},"Primary")," is receiving ",(0,r.kt)("inlineCode",{parentName:"li"},"prepare_ok"),"s from the ",(0,r.kt)("em",{parentName:"li"},"syncing backup"),", so it is committing."),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("em",{parentName:"li"},"Primary")," eventually checkpoints."),(0,r.kt)("li",{parentName:"ol"},"(This cycle repeats \u2014 ",(0,r.kt)("em",{parentName:"li"},"primary")," keeps preparing/committing, ",(0,r.kt)("em",{parentName:"li"},"syncing backup")," keeps preparing, and\n",(0,r.kt)("em",{parentName:"li"},"normal backup")," is still partitioned.)")),(0,r.kt)("p",null,"But now ",(0,r.kt)("em",{parentName:"p"},"primary")," is so far ahead that the ",(0,r.kt)("em",{parentName:"p"},"normal backup")," needs to sync! Having 2/3 replicas\nsyncing means that a single grid-block corruption on the primary could make the cluster permanently\nunavailable."),(0,r.kt)("h3",{id:"checkpoint-identifier"},"Checkpoint Identifier"),(0,r.kt)("p",null,"A ",(0,r.kt)("em",{parentName:"p"},"checkpoint id")," is a hash of the superblock ",(0,r.kt)("inlineCode",{parentName:"p"},"CheckpointState"),"."),(0,r.kt)("p",null,"A checkpoint identifier is attached to the following message types:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"command=commit"),": Current checkpoint identifier of sender."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"command=ping"),": Current checkpoint identifier of sender."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"command=prepare"),": The attached checkpoint id is the checkpoint id during which the corresponding\nprepare was originally prepared."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"command=prepare_ok"),": The attached checkpoint id is the checkpoint id during which the\ncorresponding prepare was originally prepared."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"command=request_sync_checkpoint"),": Requested checkpoint identifier."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"command=sync_checkpoint"),": Current checkpoint identifier of sender.")),(0,r.kt)("h3",{id:"sync-target"},"Sync Target"),(0,r.kt)("p",null,"A ",(0,r.kt)("em",{parentName:"p"},"sync target")," is the ",(0,r.kt)("a",{parentName:"p",href:"#checkpoint-identifier"},"checkpoint identifier")," of the checkpoint that the\nsuperblock-sync is syncing towards."),(0,r.kt)("p",null,"Not all checkpoint identifiers are valid sync targets."),(0,r.kt)("p",null,"Every sync target ",(0,r.kt)("strong",{parentName:"p"},"must"),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"have an op greater than the syncing replica's current checkpoint op."),(0,r.kt)("li",{parentName:"ul"},"either:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"be committed atop \u2013 i.e. the syncing replica can sync to ",(0,r.kt)("inlineCode",{parentName:"li"},"healthy_replica.checkpoint_op")," when\n",(0,r.kt)("inlineCode",{parentName:"li"},"trigger_for_checkpoint(healthy_replica.checkpoint_op) < healthy_replica.commit_min")," \u2013 ensuring\nthat the checkpoint has been reached by a quorum of replicas, or"),(0,r.kt)("li",{parentName:"ul"},"be more than 1 checkpoint ahead of our current checkpoint.")))),(0,r.kt)("h3",{id:"storage-determinism"},"Storage Determinism"),(0,r.kt)("p",null,"When everything works, storage is deterministic. If non-determinism is detected (via checkpoint id\nmismatches) the replica which detects the mismatch will panic. This scenario should prompt operator\ninvestigation and manual intervention."))}k.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ac624c80.87bfbbd1.js b/assets/js/ac624c80.87bfbbd1.js new file mode 100644 index 00000000..c38ea59d --- /dev/null +++ b/assets/js/ac624c80.87bfbbd1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5511],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>k});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},m="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=p(n),u=i,k=m["".concat(s,".").concat(u)]||m[u]||h[u]||r;return n?a.createElement(k,l(l({ref:t},c),{},{components:n})):a.createElement(k,l({ref:t},c))}));function k(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,l=new Array(r);l[0]=u;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:i,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>h,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={sidebar_position:4},l="State Sync",o={unversionedId:"about/internals/sync",id:"about/internals/sync",title:"State Sync",description:"State sync synchronizes the state of a lagging replica with the healthy cluster.",source:"@site/pages/about/internals/sync.md",sourceDirName:"about/internals",slug:"/about/internals/sync",permalink:"/about/internals/sync",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/sync.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"LSM",permalink:"/about/internals/lsm"},next:{title:"Testing",permalink:"/about/internals/testing"}},s={},p=[{value:"Glossary",id:"glossary",level:2},{value:"Algorithm",id:"algorithm",level:2},{value:"0: Scenarios",id:"0-scenarios",level:3},{value:"1: Triggers",id:"1-triggers",level:3},{value:"Concepts",id:"concepts",level:2},{value:"Syncing Replica",id:"syncing-replica",level:3},{value:"Checkpoint Identifier",id:"checkpoint-identifier",level:3},{value:"Storage Determinism",id:"storage-determinism",level:3}],c={toc:p},m="wrapper";function h(e){let{components:t,...n}=e;return(0,i.kt)(m,(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"state-sync"},"State Sync"),(0,i.kt)("p",null,"State sync synchronizes the state of a lagging replica with the healthy cluster."),(0,i.kt)("p",null,"State sync is used when a lagging replica's log no longer intersects with the cluster's current\nlog \u2014 ",(0,i.kt)("a",{parentName:"p",href:"/about/internals/vsr#protocol-repair-wal"},"WAL repair")," cannot catch the replica up."),(0,i.kt)("p",null,'(VRR refers to state sync as "state transfer", but we already have\n',(0,i.kt)("a",{parentName:"p",href:"/reference/transfer"},"transfers")," elsewhere.)"),(0,i.kt)("p",null,'In the context of state sync, "state" refers to:'),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"the superblock ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.checkpoint")),(0,i.kt)("li",{parentName:"ol"},"the grid (manifest, free set, and client sessions blocks)"),(0,i.kt)("li",{parentName:"ol"},"the grid (LSM table data; acquired blocks only)"),(0,i.kt)("li",{parentName:"ol"},"client replies")),(0,i.kt)("p",null,"State sync consists of four protocols:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-requeststart-view"},"Sync Superblock")," (syncs 1)"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-repair-grid"},"Repair Grid")," (syncs 2)"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-forest"},"Sync Forest")," (syncs 3)"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-client-replies"},"Sync Client Replies")," (syncs 4)")),(0,i.kt)("p",null,"The target of superblock-sync is the latest checkpoint of the healthy cluster. When we catch up to\nthe latest checkpoint (or very close to it), then we can transition back to a healthy state."),(0,i.kt)("p",null,"State sync is lazy \u2014 logically, sync is completed when the superblock is synced. The data\npointed to by the new superblock can be transferred on-demand."),(0,i.kt)("p",null,"The state (superblock) and the WAL are updated atomically \u2014 ",(0,i.kt)("a",{parentName:"p",href:"/about/internals/vsr#start_view"},(0,i.kt)("inlineCode",{parentName:"a"},"start_view")),"\nmessage includes both."),(0,i.kt)("h2",{id:"glossary"},"Glossary"),(0,i.kt)("p",null,"Replica roles:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("em",{parentName:"li"},"syncing replica"),": A replica performing superblock-sync. (Any step within ",(0,i.kt)("em",{parentName:"li"},"1"),"-",(0,i.kt)("em",{parentName:"li"},"5")," of the\n",(0,i.kt)("a",{parentName:"li",href:"#algorithm"},"sync algorithm"),")"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("em",{parentName:"li"},"healthy replica"),": A replica ",(0,i.kt)("em",{parentName:"li"},"not")," performing superblock-sync \u2014 part of the active cluster.")),(0,i.kt)("p",null,"Checkpoints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"#checkpoint-identifier"},(0,i.kt)("em",{parentName:"a"},"checkpoint id"),"/",(0,i.kt)("em",{parentName:"a"},"checkpoint identifier")),": Uniquely identifies a\nparticular checkpoint reproducibly across replicas. It is a hash over the entire state."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("em",{parentName:"li"},"Durable checkpoint"),": A checkpoint whose state is present on at least replication quorum different\nreplicas.")),(0,i.kt)("h2",{id:"algorithm"},"Algorithm"),(0,i.kt)("ol",{start:0},(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#0-scenarios"},"Sync is needed"),"."),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#1-triggers"},"Trigger sync in response to ",(0,i.kt)("inlineCode",{parentName:"a"},"start_view")),"."),(0,i.kt)("li",{parentName:"ol"},"Interrupt the in-progress commit process:\n2.1. Wait for write operations to finish.\n2.2. Cancel potentially stalled read operations. (See ",(0,i.kt)("inlineCode",{parentName:"li"},"Grid.cancel()"),".)\n2.3. Wait for cancellation to finish."),(0,i.kt)("li",{parentName:"ol"},"Install the new checkpoint and matching headers into the superblock:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Bump ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.checkpoint.header")," to the sync target header."),(0,i.kt)("li",{parentName:"ul"},"Bump ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.checkpoint.parent_checkpoint_id")," to the checkpoint id that is previous to our\nsync target (i.e. it isn't ",(0,i.kt)("em",{parentName:"li"},"our")," previous checkpoint)."),(0,i.kt)("li",{parentName:"ul"},"Bump ",(0,i.kt)("inlineCode",{parentName:"li"},"replica.commit_min"),"."),(0,i.kt)("li",{parentName:"ul"},"Set ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_min")," to the minimum op which has not been repaired."),(0,i.kt)("li",{parentName:"ul"},"Set ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_max")," to the maximum op which has not been repaired."))),(0,i.kt)("li",{parentName:"ol"},"Repair ",(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-client-replies"},"replies"),",\n",(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-repair-grid"},"free set, client sessions, and manifest blocks"),", and\n",(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-sync-forest"},"table blocks")," that were created within the ",(0,i.kt)("inlineCode",{parentName:"li"},"sync_op_{min,max}"),"\nrange."),(0,i.kt)("li",{parentName:"ol"},"Update the superblock with:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Set ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_min = 0")),(0,i.kt)("li",{parentName:"ul"},"Set ",(0,i.kt)("inlineCode",{parentName:"li"},"vsr_state.sync_op_max = 0"))))),(0,i.kt)("p",null,"If the replica starts up with ",(0,i.kt)("inlineCode",{parentName:"p"},"vsr_state.sync_op_max \u2260 0"),", go to step ",(0,i.kt)("em",{parentName:"p"},"4"),"."),(0,i.kt)("h3",{id:"0-scenarios"},"0: Scenarios"),(0,i.kt)("p",null,"Scenarios requiring state sync:"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"A replica was down/partitioned/slow for a while and the rest of the cluster moved on. The lagging\nreplica is too far behind to catch up via WAL repair."),(0,i.kt)("li",{parentName:"ol"},"A replica was just formatted and is being added to the cluster (i.e. via\n",(0,i.kt)("a",{parentName:"li",href:"/about/internals/vsr#protocol-reconfiguration"},"reconfiguration"),"). The new replica is too far behind to catch\nup via WAL repair.")),(0,i.kt)("p",null,"Deciding between between WAL repair and state sync:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"If a replica lags by more than one checkpoint behind the primary, it must use state sync."),(0,i.kt)("li",{parentName:"ul"},"If a replica is on the same checkpoint as the primary, it can only repair WAL."),(0,i.kt)("li",{parentName:"ul"},"If a replica is just one checkpoint behind, either WAL repair or state sync might be necessary:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"State sync is incorrect if there is only a single other replica on the next checkpoint --- the\nreplica that is ahead could have its state corrupted."),(0,i.kt)("li",{parentName:"ul"},"WAL repair is incorrect if all reachable peer replicas have already wrapped their logs and\nevicted some prepares from the preceding checkpoint."),(0,i.kt)("li",{parentName:"ul"},"Summarizing, if the next checkpoint is durable (replicated on a quorum of replicas), the\nlagging replica must eventually state sync.")))),(0,i.kt)("h3",{id:"1-triggers"},"1: Triggers"),(0,i.kt)("p",null,"State sync is triggered when a replica receives a ",(0,i.kt)("inlineCode",{parentName:"p"},"start_view")," message with a more advanced\ncheckpoint."),(0,i.kt)("p",null,"If a replica isn't making progress committing because a grid block or a prepare can't be repaired\nfor some time, the replica proactively sends ",(0,i.kt)("inlineCode",{parentName:"p"},"request_start_view")," to initiate the sync (see\n",(0,i.kt)("inlineCode",{parentName:"p"},"repair_sync_timeout"),")."),(0,i.kt)("h2",{id:"concepts"},"Concepts"),(0,i.kt)("h3",{id:"syncing-replica"},"Syncing Replica"),(0,i.kt)("p",null,"Syncing replicas participate in replication normally. They can append prepares, commit, and are\neligible to become primaries. In particular, a syncing replica can advance its own checkpoint as a\npart of the normal commit process."),(0,i.kt)("p",null,"The only restriction is that syncing replicas don't contribute to their checkpoint's replication\nquorum. That is, for the cluster as a whole to advance the checkpoint, there must be at least a\nreplication quorum of healthy replicas."),(0,i.kt)("p",null,"The mechanism for discovering sufficiently replicated (durable) checkpoints uses ",(0,i.kt)("inlineCode",{parentName:"p"},"prepare_ok"),"\nmessages. Sending a ",(0,i.kt)("inlineCode",{parentName:"p"},"prepare_ok")," signals that the replica has a recent checkpoint fully synced. As a\nconsequence, observing a ",(0,i.kt)("inlineCode",{parentName:"p"},"commit_max")," sufficiently ahead of a checkpoint signifies the durability of\nthe checkpoint."),(0,i.kt)("p",null,"For this reason, syncing replicas withhold ",(0,i.kt)("inlineCode",{parentName:"p"},"prepare_ok")," until ",(0,i.kt)("inlineCode",{parentName:"p"},"commit_max")," confirms that their\ncheckpoint is fully replicated on a quorum of different replicas. See ",(0,i.kt)("inlineCode",{parentName:"p"},"op_prepare_max"),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"op_prepare_ok_max")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"op_repair_min")," for details."),(0,i.kt)("h3",{id:"checkpoint-identifier"},"Checkpoint Identifier"),(0,i.kt)("p",null,"A ",(0,i.kt)("em",{parentName:"p"},"checkpoint id")," is a hash of the superblock ",(0,i.kt)("inlineCode",{parentName:"p"},"CheckpointState"),"."),(0,i.kt)("p",null,"A checkpoint identifier is attached to the following message types:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"command=commit"),": Current checkpoint identifier of sender."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"command=ping"),": Current checkpoint identifier of sender."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"command=prepare"),": The attached checkpoint id is the checkpoint id during which the corresponding\nprepare was originally prepared."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"command=prepare_ok"),": The attached checkpoint id is the checkpoint id during which the\ncorresponding prepare was originally prepared.")),(0,i.kt)("h3",{id:"storage-determinism"},"Storage Determinism"),(0,i.kt)("p",null,"When everything works, storage is deterministic. If non-determinism is detected (via checkpoint id\nmismatches) the replica which detects the mismatch will panic. This scenario should prompt operator\ninvestigation and manual intervention."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b2d6321c.c88352dd.js b/assets/js/b2d6321c.c1915e3d.js similarity index 51% rename from assets/js/b2d6321c.c88352dd.js rename to assets/js/b2d6321c.c1915e3d.js index 25234378..7bc499e4 100644 --- a/assets/js/b2d6321c.c88352dd.js +++ b/assets/js/b2d6321c.c1915e3d.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[8775],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>k});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var o=a.createContext({}),d=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},p=function(e){var t=d(e.components);return a.createElement(o.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,o=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),m=d(n),c=i,k=m["".concat(o,".").concat(c)]||m[c]||u[c]||r;return n?a.createElement(k,l(l({ref:t},p),{},{components:n})):a.createElement(k,l({ref:t},p))}));function k(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,l=new Array(r);l[0]=c;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[m]="string"==typeof e?e:i,l[1]=s;for(var d=2;d{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>s,toc:()=>d});var a=n(7462),i=(n(7294),n(3905));const r={sidebar_position:2},l="Transfer",s={unversionedId:"reference/transfer",id:"reference/transfer",title:"Transfer",description:"A transfer is an immutable record of a financial transaction between two accounts.",source:"@site/pages/reference/transfer.md",sourceDirName:"reference",slug:"/reference/transfer",permalink:"/reference/transfer",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/transfer.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Account",permalink:"/reference/account"},next:{title:"AccountBalance",permalink:"/reference/account-balance"}},o={},d=[{value:"Updates",id:"updates",level:3},{value:"Deletion",id:"deletion",level:3},{value:"Guarantees",id:"guarantees",level:3},{value:"Modes",id:"modes",level:2},{value:"Fields",id:"fields",level:2},{value:"id",id:"id",level:3},{value:"debit_account_id",id:"debit_account_id",level:3},{value:"credit_account_id",id:"credit_account_id",level:3},{value:"amount",id:"amount",level:3},{value:"Examples",id:"examples",level:4},{value:"pending_id",id:"pending_id",level:3},{value:"user_data_128",id:"user_data_128",level:3},{value:"user_data_64",id:"user_data_64",level:3},{value:"user_data_32",id:"user_data_32",level:3},{value:"timeout",id:"timeout",level:3},{value:"ledger",id:"ledger",level:3},{value:"code",id:"code",level:3},{value:"flags",id:"flags",level:3},{value:"flags.linked",id:"flagslinked",level:4},{value:"Examples",id:"examples-1",level:5},{value:"flags.pending",id:"flagspending",level:4},{value:"flags.post_pending_transfer",id:"flagspost_pending_transfer",level:4},{value:"flags.void_pending_transfer",id:"flagsvoid_pending_transfer",level:4},{value:"flags.balancing_debit",id:"flagsbalancing_debit",level:4},{value:"Examples",id:"examples-2",level:5},{value:"flags.balancing_credit",id:"flagsbalancing_credit",level:4},{value:"Examples",id:"examples-3",level:5},{value:"flags.closing_debit",id:"flagsclosing_debit",level:4},{value:"flags.closing_credit",id:"flagsclosing_credit",level:4},{value:"flags.imported",id:"flagsimported",level:4},{value:"timestamp",id:"timestamp",level:3},{value:"Internals",id:"internals",level:2}],p={toc:d},m="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(m,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"transfer"},(0,i.kt)("inlineCode",{parentName:"h1"},"Transfer")),(0,i.kt)("p",null,"A ",(0,i.kt)("inlineCode",{parentName:"p"},"transfer")," is an immutable record of a financial transaction between two accounts."),(0,i.kt)("p",null,'In TigerBeetle, financial transactions are called "transfers" instead of "transactions" because the\nlatter term is heavily overloaded in the context of databases.'),(0,i.kt)("p",null,"Note that transfers debit a single account and credit a single account on the same ledger. You can\ncompose these into more complex transactions using the methods described in\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/currency-exchange"},"Currency Exchange")," and\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/multi-debit-credit-transfers"},"Multi-Debit, Multi-Credit Transfers"),"."),(0,i.kt)("h3",{id:"updates"},"Updates"),(0,i.kt)("p",null,"Transfers ",(0,i.kt)("em",{parentName:"p"},"cannot be modified")," after creation."),(0,i.kt)("p",null,"If a detail of a transfer is incorrect and needs to be modified, this is done using\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/correcting-transfers"},"correcting transfers"),"."),(0,i.kt)("h3",{id:"deletion"},"Deletion"),(0,i.kt)("p",null,"Transfers ",(0,i.kt)("em",{parentName:"p"},"cannot be deleted")," after creation."),(0,i.kt)("p",null,"If a transfer is made in error, its effects can be reversed using a\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/correcting-transfers"},"correcting transfer"),"."),(0,i.kt)("h3",{id:"guarantees"},"Guarantees"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Transfers are immutable. They are never modified once they are successfully created."),(0,i.kt)("li",{parentName:"ul"},"There is at most one ",(0,i.kt)("inlineCode",{parentName:"li"},"Transfer")," with a particular ",(0,i.kt)("a",{parentName:"li",href:"#id"},(0,i.kt)("inlineCode",{parentName:"a"},"id")),"."),(0,i.kt)("li",{parentName:"ul"},"A ",(0,i.kt)("a",{parentName:"li",href:"/coding/two-phase-transfers#reserve-funds-pending-transfer"},"pending transfer")," resolves at\nmost once."),(0,i.kt)("li",{parentName:"ul"},"Transfer ",(0,i.kt)("a",{parentName:"li",href:"#timeout"},"timeouts")," are deterministic, driven by the\n",(0,i.kt)("a",{parentName:"li",href:"/coding/time#why-tigerbeetle-manages-timestamps"},"cluster's timestamp"),".")),(0,i.kt)("h2",{id:"modes"},"Modes"),(0,i.kt)("p",null,"Transfers can either be Single-Phase, where they are executed immediately, or Two-Phase, where they\nare first put in a Pending state and then either Posted or Voided. For more details on the latter,\nsee the ",(0,i.kt)("a",{parentName:"p",href:"/coding/two-phase-transfers"},"Two-Phase Transfer guide"),"."),(0,i.kt)("p",null,"Fields used by each mode of transfer:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Field"),(0,i.kt)("th",{parentName:"tr",align:null},"Single-Phase"),(0,i.kt)("th",{parentName:"tr",align:null},"Pending"),(0,i.kt)("th",{parentName:"tr",align:null},"Post-Pending"),(0,i.kt)("th",{parentName:"tr",align:null},"Void-Pending"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"id")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"debit_account_id")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"credit_account_id")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"amount")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pending_id")),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"user_data_128")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"user_data_64")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"user_data_32")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"timeout")),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"optional\xb9"),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"none")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"ledger")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"code")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.linked")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.pending")),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.post_pending_transfer")),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.void_pending_transfer")),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"true")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.balancing_debit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.balancing_credit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.closing_debit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.closing_credit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.imported")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"timestamp")),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2"),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2"),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2"),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2")))),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},(0,i.kt)("em",{parentName:"p"},"\xb9 None if ",(0,i.kt)("inlineCode",{parentName:"em"},"flags.imported")," is set."),(0,i.kt)("br",null),"\n",(0,i.kt)("em",{parentName:"p"},"\xb2 Required if ",(0,i.kt)("inlineCode",{parentName:"em"},"flags.imported")," is set."))),(0,i.kt)("h2",{id:"fields"},"Fields"),(0,i.kt)("h3",{id:"id"},(0,i.kt)("inlineCode",{parentName:"h3"},"id")),(0,i.kt)("p",null,"This is a unique identifier for the transaction."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"Must not be zero or ",(0,i.kt)("inlineCode",{parentName:"li"},"2^128 - 1")),(0,i.kt)("li",{parentName:"ul"},"Must not conflict with another transfer in the cluster")),(0,i.kt)("p",null,"See the ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#id"},(0,i.kt)("inlineCode",{parentName:"a"},"id")," section in the data modeling doc")," for more\nrecommendations on choosing an ID scheme."),(0,i.kt)("p",null,"Note that transfer IDs are unique for the cluster -- not the ledger. If you want to store a\nrelationship between multiple transfers, such as indicating that multiple transfers on different\nledgers were part of a single transaction, you should store a transaction ID in one of the\n",(0,i.kt)("a",{parentName:"p",href:"#user_data_128"},(0,i.kt)("inlineCode",{parentName:"a"},"user_data"))," fields."),(0,i.kt)("h3",{id:"debit_account_id"},(0,i.kt)("inlineCode",{parentName:"h3"},"debit_account_id")),(0,i.kt)("p",null,"This refers to the account to debit the transfer's ",(0,i.kt)("a",{parentName:"p",href:"#amount"},(0,i.kt)("inlineCode",{parentName:"a"},"amount")),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are ",(0,i.kt)("em",{parentName:"li"},"not")," set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Must match an existing account"),(0,i.kt)("li",{parentName:"ul"},"Must not be the same as ",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id")))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id")," is zero, it will be automatically set to the pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id")," is nonzero, it must match the corresponding pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.imported")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The matching account's ",(0,i.kt)("a",{parentName:"li",href:"/reference/account#timestamp"},"timestamp")," must be less than or equal to the\ntransfer's ",(0,i.kt)("a",{parentName:"li",href:"#timestamp"},"timestamp"),".")))),(0,i.kt)("h3",{id:"credit_account_id"},(0,i.kt)("inlineCode",{parentName:"h3"},"credit_account_id")),(0,i.kt)("p",null,"This refers to the account to credit the transfer's ",(0,i.kt)("a",{parentName:"p",href:"#amount"},(0,i.kt)("inlineCode",{parentName:"a"},"amount")),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are ",(0,i.kt)("em",{parentName:"li"},"not")," set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Must match an existing account"),(0,i.kt)("li",{parentName:"ul"},"Must not be the same as ",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id")))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id")," is zero, it will be automatically set to the pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id")," is nonzero, it must match the corresponding pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.imported")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The matching account's ",(0,i.kt)("a",{parentName:"li",href:"/reference/account#timestamp"},"timestamp")," must be less than or equal to the\ntransfer's ",(0,i.kt)("a",{parentName:"li",href:"#timestamp"},"timestamp"),".")))),(0,i.kt)("h3",{id:"amount"},(0,i.kt)("inlineCode",{parentName:"h3"},"amount")),(0,i.kt)("p",null,"This is how much should be debited from the ",(0,i.kt)("inlineCode",{parentName:"p"},"debit_account_id")," account and credited to the\n",(0,i.kt)("inlineCode",{parentName:"p"},"credit_account_id")," account."),(0,i.kt)("p",null,"Note that this is an unsigned 128-bit integer. You can read more about using\n",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#debits-vs-credits"},"debits and credits")," to represent positive and\nnegative balances as well as\n",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#fractional-amounts-and-asset-scale"},"fractional amounts and asset scales"),"."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_debit")," is set, this is the maximum amount that will be debited/credited,\nwhere the actual transfer amount is determined by the debit account's constraints."),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_credit")," is set, this is the maximum amount that will be debited/credited,\nwhere the actual transfer amount is determined by the credit account's constraints."),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," is set, the amount posted will be:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"the pending transfer's amount, when the posted transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is ",(0,i.kt)("inlineCode",{parentName:"li"},"AMOUNT_MAX")),(0,i.kt)("li",{parentName:"ul"},"the posting transfer's amount, when the posted transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is less than or equal to the\npending transfer's amount.")))),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is zero, it will be automatically be set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is nonzero, it must be equal to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is ",(0,i.kt)("inlineCode",{parentName:"li"},"AMOUNT_MAX")," (",(0,i.kt)("inlineCode",{parentName:"li"},"2^128 - 1"),"), it will automatically be set to the pending\ntransfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is not ",(0,i.kt)("inlineCode",{parentName:"li"},"AMOUNT_MAX"),", it must be less than or equal to the pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),".")))),(0,i.kt)("details",null,(0,i.kt)("summary",null,"Client release < 0.16.0"),(0,i.kt)("p",null,"Additional constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is zero, it will be automatically be set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is nonzero, it must be less than or equal to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_debit")," and/or ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_credit")," is set, if ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is zero, it will\nautomatically be set to the maximum amount that does not violate the corresponding account limits.\n(Equivalent to setting ",(0,i.kt)("inlineCode",{parentName:"li"},"amount = 2^128 - 1"),")."),(0,i.kt)("li",{parentName:"ul"},"When all of the following flags are not set, ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," must be nonzero:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_debit")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_credit")))))),(0,i.kt)("h4",{id:"examples"},"Examples"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"For representing fractional amounts (e.g. ",(0,i.kt)("inlineCode",{parentName:"li"},"$12.34"),"), see\n",(0,i.kt)("a",{parentName:"li",href:"/coding/data-modeling#fractional-amounts-and-asset-scale"},"Fractional Amounts"),"."),(0,i.kt)("li",{parentName:"ul"},"For balancing transfers, see ",(0,i.kt)("a",{parentName:"li",href:"/coding/recipes/close-account"},"Close Account"),".")),(0,i.kt)("h3",{id:"pending_id"},(0,i.kt)("inlineCode",{parentName:"h3"},"pending_id")),(0,i.kt)("p",null,"If this transfer will post or void a pending transfer, ",(0,i.kt)("inlineCode",{parentName:"p"},"pending_id")," references that pending\ntransfer. If this is not a post or void transfer, it must be zero."),(0,i.kt)("p",null,"See the section on ",(0,i.kt)("a",{parentName:"p",href:"/coding/two-phase-transfers"},"Two-Phase Transfers")," for more information on\nhow the ",(0,i.kt)("inlineCode",{parentName:"p"},"pending_id")," is used."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"Must be zero if neither void nor pending transfer flag is set"),(0,i.kt)("li",{parentName:"ul"},"Must match an existing transfer's ",(0,i.kt)("a",{parentName:"li",href:"#id"},(0,i.kt)("inlineCode",{parentName:"a"},"id"))," if non-zero")),(0,i.kt)("h3",{id:"user_data_128"},(0,i.kt)("inlineCode",{parentName:"h3"},"user_data_128")),(0,i.kt)("p",null,"This is an optional 128-bit secondary identifier to link this transfer to an external entity or\nevent."),(0,i.kt)("p",null,"When set to zero, no secondary identifier will be associated with the account, therefore only\nnon-zero values can be used as ",(0,i.kt)("a",{parentName:"p",href:"/reference/query-filter"},"query filter"),"."),(0,i.kt)("p",null,"As an example, you might generate a\n",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#tigerbeetle-time-based-identifiers-recommended"},"TigerBeetle Time-Based Identifier"),"\nthat ties together a group of transfers."),(0,i.kt)("p",null,"For more information, see ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#user_data"},"Data Modeling"),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)")),(0,i.kt)("h3",{id:"user_data_64"},(0,i.kt)("inlineCode",{parentName:"h3"},"user_data_64")),(0,i.kt)("p",null,"This is an optional 64-bit secondary identifier to link this transfer to an external entity or\nevent."),(0,i.kt)("p",null,"When set to zero, no secondary identifier will be associated with the account, therefore only\nnon-zero values can be used as ",(0,i.kt)("a",{parentName:"p",href:"/reference/query-filter"},"query filter"),"."),(0,i.kt)("p",null,"As an example, you might use this field store an external timestamp."),(0,i.kt)("p",null,"For more information, see ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#user_data"},"Data Modeling"),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)")),(0,i.kt)("h3",{id:"user_data_32"},(0,i.kt)("inlineCode",{parentName:"h3"},"user_data_32")),(0,i.kt)("p",null,"This is an optional 32-bit secondary identifier to link this transfer to an external entity or\nevent."),(0,i.kt)("p",null,"When set to zero, no secondary identifier will be associated with the account, therefore only\nnon-zero values can be used as ",(0,i.kt)("a",{parentName:"p",href:"/reference/query-filter"},"query filter"),"."),(0,i.kt)("p",null,"As an example, you might use this field to store a timezone or locale."),(0,i.kt)("p",null,"For more information, see ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#user_data"},"Data Modeling"),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,i.kt)("h3",{id:"timeout"},(0,i.kt)("inlineCode",{parentName:"h3"},"timeout")),(0,i.kt)("p",null,"This is the interval in seconds after a ",(0,i.kt)("a",{parentName:"p",href:"#flagspending"},(0,i.kt)("inlineCode",{parentName:"a"},"pending"))," transfer's\n",(0,i.kt)("a",{parentName:"p",href:"#timestamp"},"arrival at the cluster")," that it may be ",(0,i.kt)("a",{parentName:"p",href:"#flagspost_pending_transfer"},"posted")," or\n",(0,i.kt)("a",{parentName:"p",href:"#flagsvoid_pending_transfer"},"voided"),". Zero denotes absence of timeout."),(0,i.kt)("p",null,"Non-pending transfers cannot have a timeout."),(0,i.kt)("p",null,"Imported transfers cannot have a timeout."),(0,i.kt)("p",null,"TigerBeetle makes a best-effort approach to remove pending balances of expired transfers\nautomatically:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Transfers expire ",(0,i.kt)("em",{parentName:"p"},"exactly")," at their expiry time (",(0,i.kt)("a",{parentName:"p",href:"#timestamp"},(0,i.kt)("inlineCode",{parentName:"a"},"timestamp"))," ",(0,i.kt)("em",{parentName:"p"},"plus")," ",(0,i.kt)("inlineCode",{parentName:"p"},"timeout"),"\nconverted in nanoseconds).")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Pending balances will never be removed before its expiry.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Expired transfers cannot be manually posted or voided.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"It is not guaranteed that the pending balance will be removed exactly at its expiry."),(0,i.kt)("p",{parentName:"li"},"In particular, client requests may observe still-pending balances for expired transfers.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Pending balances are removed in chronological order by expiry. If multiple transfers expire at the\nsame time, then ordered by the transfer's creation ",(0,i.kt)("a",{parentName:"p",href:"#timestamp"},(0,i.kt)("inlineCode",{parentName:"a"},"timestamp")),"."),(0,i.kt)("p",{parentName:"li"},"If a transfer ",(0,i.kt)("inlineCode",{parentName:"p"},"A")," has expiry ",(0,i.kt)("inlineCode",{parentName:"p"},"E\u2081")," and transfer ",(0,i.kt)("inlineCode",{parentName:"p"},"B")," has expiry ",(0,i.kt)("inlineCode",{parentName:"p"},"E\u2082"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"E\u2081{n.d(t,{Zo:()=>d,kt:()=>k});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var o=a.createContext({}),p=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(o.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,o=e.parentName,d=s(e,["components","mdxType","originalType","parentName"]),m=p(n),c=i,k=m["".concat(o,".").concat(c)]||m[c]||u[c]||r;return n?a.createElement(k,l(l({ref:t},d),{},{components:n})):a.createElement(k,l({ref:t},d))}));function k(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,l=new Array(r);l[0]=c;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[m]="string"==typeof e?e:i,l[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={sidebar_position:2},l="Transfer",s={unversionedId:"reference/transfer",id:"reference/transfer",title:"Transfer",description:"A transfer is an immutable record of a financial transaction between two accounts.",source:"@site/pages/reference/transfer.md",sourceDirName:"reference",slug:"/reference/transfer",permalink:"/reference/transfer",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/transfer.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Account",permalink:"/reference/account"},next:{title:"AccountBalance",permalink:"/reference/account-balance"}},o={},p=[{value:"Updates",id:"updates",level:3},{value:"Deletion",id:"deletion",level:3},{value:"Guarantees",id:"guarantees",level:3},{value:"Modes",id:"modes",level:2},{value:"Fields",id:"fields",level:2},{value:"id",id:"id",level:3},{value:"debit_account_id",id:"debit_account_id",level:3},{value:"credit_account_id",id:"credit_account_id",level:3},{value:"amount",id:"amount",level:3},{value:"Examples",id:"examples",level:4},{value:"pending_id",id:"pending_id",level:3},{value:"user_data_128",id:"user_data_128",level:3},{value:"user_data_64",id:"user_data_64",level:3},{value:"user_data_32",id:"user_data_32",level:3},{value:"timeout",id:"timeout",level:3},{value:"ledger",id:"ledger",level:3},{value:"code",id:"code",level:3},{value:"flags",id:"flags",level:3},{value:"flags.linked",id:"flagslinked",level:4},{value:"Examples",id:"examples-1",level:5},{value:"flags.pending",id:"flagspending",level:4},{value:"flags.post_pending_transfer",id:"flagspost_pending_transfer",level:4},{value:"flags.void_pending_transfer",id:"flagsvoid_pending_transfer",level:4},{value:"flags.balancing_debit",id:"flagsbalancing_debit",level:4},{value:"Examples",id:"examples-2",level:5},{value:"flags.balancing_credit",id:"flagsbalancing_credit",level:4},{value:"Examples",id:"examples-3",level:5},{value:"flags.closing_debit",id:"flagsclosing_debit",level:4},{value:"flags.closing_credit",id:"flagsclosing_credit",level:4},{value:"flags.imported",id:"flagsimported",level:4},{value:"timestamp",id:"timestamp",level:3},{value:"Internals",id:"internals",level:2}],d={toc:p},m="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(m,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"transfer"},(0,i.kt)("inlineCode",{parentName:"h1"},"Transfer")),(0,i.kt)("p",null,"A ",(0,i.kt)("inlineCode",{parentName:"p"},"transfer")," is an immutable record of a financial transaction between two accounts."),(0,i.kt)("p",null,'In TigerBeetle, financial transactions are called "transfers" instead of "transactions" because the\nlatter term is heavily overloaded in the context of databases.'),(0,i.kt)("p",null,"Note that transfers debit a single account and credit a single account on the same ledger. You can\ncompose these into more complex transactions using the methods described in\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/currency-exchange"},"Currency Exchange")," and\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/multi-debit-credit-transfers"},"Multi-Debit, Multi-Credit Transfers"),"."),(0,i.kt)("h3",{id:"updates"},"Updates"),(0,i.kt)("p",null,"Transfers ",(0,i.kt)("em",{parentName:"p"},"cannot be modified")," after creation."),(0,i.kt)("p",null,"If a detail of a transfer is incorrect and needs to be modified, this is done using\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/correcting-transfers"},"correcting transfers"),"."),(0,i.kt)("h3",{id:"deletion"},"Deletion"),(0,i.kt)("p",null,"Transfers ",(0,i.kt)("em",{parentName:"p"},"cannot be deleted")," after creation."),(0,i.kt)("p",null,"If a transfer is made in error, its effects can be reversed using a\n",(0,i.kt)("a",{parentName:"p",href:"/coding/recipes/correcting-transfers"},"correcting transfer"),"."),(0,i.kt)("h3",{id:"guarantees"},"Guarantees"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Transfers are immutable. They are never modified once they are successfully created."),(0,i.kt)("li",{parentName:"ul"},"There is at most one ",(0,i.kt)("inlineCode",{parentName:"li"},"Transfer")," with a particular ",(0,i.kt)("a",{parentName:"li",href:"#id"},(0,i.kt)("inlineCode",{parentName:"a"},"id")),"."),(0,i.kt)("li",{parentName:"ul"},"A ",(0,i.kt)("a",{parentName:"li",href:"/coding/two-phase-transfers#reserve-funds-pending-transfer"},"pending transfer")," resolves at\nmost once."),(0,i.kt)("li",{parentName:"ul"},"Transfer ",(0,i.kt)("a",{parentName:"li",href:"#timeout"},"timeouts")," are deterministic, driven by the\n",(0,i.kt)("a",{parentName:"li",href:"/coding/time#why-tigerbeetle-manages-timestamps"},"cluster's timestamp"),".")),(0,i.kt)("h2",{id:"modes"},"Modes"),(0,i.kt)("p",null,"Transfers can either be Single-Phase, where they are executed immediately, or Two-Phase, where they\nare first put in a Pending state and then either Posted or Voided. For more details on the latter,\nsee the ",(0,i.kt)("a",{parentName:"p",href:"/coding/two-phase-transfers"},"Two-Phase Transfer guide"),"."),(0,i.kt)("p",null,"Fields used by each mode of transfer:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Field"),(0,i.kt)("th",{parentName:"tr",align:null},"Single-Phase"),(0,i.kt)("th",{parentName:"tr",align:null},"Pending"),(0,i.kt)("th",{parentName:"tr",align:null},"Post-Pending"),(0,i.kt)("th",{parentName:"tr",align:null},"Void-Pending"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"id")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"debit_account_id")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"credit_account_id")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"amount")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pending_id")),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"user_data_128")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"user_data_64")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"user_data_32")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"timeout")),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"optional\xb9"),(0,i.kt)("td",{parentName:"tr",align:null},"none"),(0,i.kt)("td",{parentName:"tr",align:null},"none")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"ledger")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"code")),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"required"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.linked")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.pending")),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.post_pending_transfer")),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.void_pending_transfer")),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"true")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.balancing_debit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.balancing_credit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.closing_debit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.closing_credit")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"true"),(0,i.kt)("td",{parentName:"tr",align:null},"false"),(0,i.kt)("td",{parentName:"tr",align:null},"false")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"flags.imported")),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional"),(0,i.kt)("td",{parentName:"tr",align:null},"optional")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"timestamp")),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2"),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2"),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2"),(0,i.kt)("td",{parentName:"tr",align:null},"none\xb2")))),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},(0,i.kt)("em",{parentName:"p"},"\xb9 None if ",(0,i.kt)("inlineCode",{parentName:"em"},"flags.imported")," is set."),(0,i.kt)("br",null),"\n",(0,i.kt)("em",{parentName:"p"},"\xb2 Required if ",(0,i.kt)("inlineCode",{parentName:"em"},"flags.imported")," is set."))),(0,i.kt)("h2",{id:"fields"},"Fields"),(0,i.kt)("h3",{id:"id"},(0,i.kt)("inlineCode",{parentName:"h3"},"id")),(0,i.kt)("p",null,"This is a unique identifier for the transaction."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"Must not be zero or ",(0,i.kt)("inlineCode",{parentName:"li"},"2^128 - 1")),(0,i.kt)("li",{parentName:"ul"},"Must not conflict with another transfer in the cluster")),(0,i.kt)("p",null,"See the ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#id"},(0,i.kt)("inlineCode",{parentName:"a"},"id")," section in the data modeling doc")," for more\nrecommendations on choosing an ID scheme."),(0,i.kt)("p",null,"Note that transfer IDs are unique for the cluster -- not the ledger. If you want to store a\nrelationship between multiple transfers, such as indicating that multiple transfers on different\nledgers were part of a single transaction, you should store a transaction ID in one of the\n",(0,i.kt)("a",{parentName:"p",href:"#user_data_128"},(0,i.kt)("inlineCode",{parentName:"a"},"user_data"))," fields."),(0,i.kt)("h3",{id:"debit_account_id"},(0,i.kt)("inlineCode",{parentName:"h3"},"debit_account_id")),(0,i.kt)("p",null,"This refers to the account to debit the transfer's ",(0,i.kt)("a",{parentName:"p",href:"#amount"},(0,i.kt)("inlineCode",{parentName:"a"},"amount")),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are ",(0,i.kt)("em",{parentName:"li"},"not")," set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Must match an existing account"),(0,i.kt)("li",{parentName:"ul"},"Must not be the same as ",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id")))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id")," is zero, it will be automatically set to the pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id")," is nonzero, it must match the corresponding pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.imported")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The matching account's ",(0,i.kt)("a",{parentName:"li",href:"/reference/account#timestamp"},"timestamp")," must be less than or equal to the\ntransfer's ",(0,i.kt)("a",{parentName:"li",href:"#timestamp"},"timestamp"),".")))),(0,i.kt)("h3",{id:"credit_account_id"},(0,i.kt)("inlineCode",{parentName:"h3"},"credit_account_id")),(0,i.kt)("p",null,"This refers to the account to credit the transfer's ",(0,i.kt)("a",{parentName:"p",href:"#amount"},(0,i.kt)("inlineCode",{parentName:"a"},"amount")),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are ",(0,i.kt)("em",{parentName:"li"},"not")," set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Must match an existing account"),(0,i.kt)("li",{parentName:"ul"},"Must not be the same as ",(0,i.kt)("inlineCode",{parentName:"li"},"debit_account_id")))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," are set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id")," is zero, it will be automatically set to the pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id")," is nonzero, it must match the corresponding pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"credit_account_id"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.imported")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The matching account's ",(0,i.kt)("a",{parentName:"li",href:"/reference/account#timestamp"},"timestamp")," must be less than or equal to the\ntransfer's ",(0,i.kt)("a",{parentName:"li",href:"#timestamp"},"timestamp"),".")))),(0,i.kt)("h3",{id:"amount"},(0,i.kt)("inlineCode",{parentName:"h3"},"amount")),(0,i.kt)("p",null,"This is how much should be debited from the ",(0,i.kt)("inlineCode",{parentName:"p"},"debit_account_id")," account and credited to the\n",(0,i.kt)("inlineCode",{parentName:"p"},"credit_account_id")," account."),(0,i.kt)("p",null,"Note that this is an unsigned 128-bit integer. You can read more about using\n",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#debits-vs-credits"},"debits and credits")," to represent positive and\nnegative balances as well as\n",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#fractional-amounts-and-asset-scale"},"fractional amounts and asset scales"),"."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_debit")," is set, this is the maximum amount that will be debited/credited,\nwhere the actual transfer amount is determined by the debit account's constraints."),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_credit")," is set, this is the maximum amount that will be debited/credited,\nwhere the actual transfer amount is determined by the credit account's constraints."),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," is set, the amount posted will be:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"the pending transfer's amount, when the posted transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is ",(0,i.kt)("inlineCode",{parentName:"li"},"AMOUNT_MAX")),(0,i.kt)("li",{parentName:"ul"},"the posting transfer's amount, when the posted transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is less than or equal to the\npending transfer's amount.")))),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is zero, it will be automatically be set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is nonzero, it must be equal to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is ",(0,i.kt)("inlineCode",{parentName:"li"},"AMOUNT_MAX")," (",(0,i.kt)("inlineCode",{parentName:"li"},"2^128 - 1"),"), it will automatically be set to the pending\ntransfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is not ",(0,i.kt)("inlineCode",{parentName:"li"},"AMOUNT_MAX"),", it must be less than or equal to the pending transfer's\n",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),".")))),(0,i.kt)("details",null,(0,i.kt)("summary",null,"Client release < 0.16.0"),(0,i.kt)("p",null,"Additional constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")," is set:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is zero, it will be automatically be set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."),(0,i.kt)("li",{parentName:"ul"},"If ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is nonzero, it must be less than or equal to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"li"},"amount"),"."))),(0,i.kt)("li",{parentName:"ul"},"When ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_debit")," and/or ",(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_credit")," is set, if ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," is zero, it will\nautomatically be set to the maximum amount that does not violate the corresponding account limits.\n(Equivalent to setting ",(0,i.kt)("inlineCode",{parentName:"li"},"amount = 2^128 - 1"),")."),(0,i.kt)("li",{parentName:"ul"},"When all of the following flags are not set, ",(0,i.kt)("inlineCode",{parentName:"li"},"amount")," must be nonzero:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.post_pending_transfer")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.void_pending_transfer")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_debit")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"flags.balancing_credit")))))),(0,i.kt)("h4",{id:"examples"},"Examples"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"For representing fractional amounts (e.g. ",(0,i.kt)("inlineCode",{parentName:"li"},"$12.34"),"), see\n",(0,i.kt)("a",{parentName:"li",href:"/coding/data-modeling#fractional-amounts-and-asset-scale"},"Fractional Amounts"),"."),(0,i.kt)("li",{parentName:"ul"},"For balancing transfers, see ",(0,i.kt)("a",{parentName:"li",href:"/coding/recipes/close-account"},"Close Account"),".")),(0,i.kt)("h3",{id:"pending_id"},(0,i.kt)("inlineCode",{parentName:"h3"},"pending_id")),(0,i.kt)("p",null,"If this transfer will post or void a pending transfer, ",(0,i.kt)("inlineCode",{parentName:"p"},"pending_id")," references that pending\ntransfer. If this is not a post or void transfer, it must be zero."),(0,i.kt)("p",null,"See the section on ",(0,i.kt)("a",{parentName:"p",href:"/coding/two-phase-transfers"},"Two-Phase Transfers")," for more information on\nhow the ",(0,i.kt)("inlineCode",{parentName:"p"},"pending_id")," is used."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,i.kt)("li",{parentName:"ul"},"Must be zero if neither void nor pending transfer flag is set"),(0,i.kt)("li",{parentName:"ul"},"Must match an existing transfer's ",(0,i.kt)("a",{parentName:"li",href:"#id"},(0,i.kt)("inlineCode",{parentName:"a"},"id"))," if non-zero")),(0,i.kt)("h3",{id:"user_data_128"},(0,i.kt)("inlineCode",{parentName:"h3"},"user_data_128")),(0,i.kt)("p",null,"This is an optional 128-bit secondary identifier to link this transfer to an external entity or\nevent."),(0,i.kt)("p",null,"When set to zero, no secondary identifier will be associated with the account, therefore only\nnon-zero values can be used as ",(0,i.kt)("a",{parentName:"p",href:"/reference/query-filter"},"query filter"),"."),(0,i.kt)("p",null,"When set to zero, if\n",(0,i.kt)("a",{parentName:"p",href:"#flagspost_pending_transfer"},(0,i.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))," or\n",(0,i.kt)("a",{parentName:"p",href:"#flagsvoid_pending_transfer"},(0,i.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))," is set, then\nit will be automatically set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"p"},"user_data_128"),"."),(0,i.kt)("p",null,"As an example, you might generate a\n",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#tigerbeetle-time-based-identifiers-recommended"},"TigerBeetle Time-Based Identifier"),"\nthat ties together a group of transfers."),(0,i.kt)("p",null,"For more information, see ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#user_data"},"Data Modeling"),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)")),(0,i.kt)("h3",{id:"user_data_64"},(0,i.kt)("inlineCode",{parentName:"h3"},"user_data_64")),(0,i.kt)("p",null,"This is an optional 64-bit secondary identifier to link this transfer to an external entity or\nevent."),(0,i.kt)("p",null,"When set to zero, no secondary identifier will be associated with the account, therefore only\nnon-zero values can be used as ",(0,i.kt)("a",{parentName:"p",href:"/reference/query-filter"},"query filter"),"."),(0,i.kt)("p",null,"When set to zero, if\n",(0,i.kt)("a",{parentName:"p",href:"#flagspost_pending_transfer"},(0,i.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))," or\n",(0,i.kt)("a",{parentName:"p",href:"#flagsvoid_pending_transfer"},(0,i.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))," is set, then\nit will be automatically set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"p"},"user_data_64"),"."),(0,i.kt)("p",null,"As an example, you might use this field store an external timestamp."),(0,i.kt)("p",null,"For more information, see ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#user_data"},"Data Modeling"),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)")),(0,i.kt)("h3",{id:"user_data_32"},(0,i.kt)("inlineCode",{parentName:"h3"},"user_data_32")),(0,i.kt)("p",null,"This is an optional 32-bit secondary identifier to link this transfer to an external entity or\nevent."),(0,i.kt)("p",null,"When set to zero, no secondary identifier will be associated with the account, therefore only\nnon-zero values can be used as ",(0,i.kt)("a",{parentName:"p",href:"/reference/query-filter"},"query filter"),"."),(0,i.kt)("p",null,"When set to zero, if\n",(0,i.kt)("a",{parentName:"p",href:"#flagspost_pending_transfer"},(0,i.kt)("inlineCode",{parentName:"a"},"flags.post_pending_transfer"))," or\n",(0,i.kt)("a",{parentName:"p",href:"#flagsvoid_pending_transfer"},(0,i.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))," is set, then\nit will be automatically set to the pending transfer's ",(0,i.kt)("inlineCode",{parentName:"p"},"user_data_32"),"."),(0,i.kt)("p",null,"As an example, you might use this field to store a timezone or locale."),(0,i.kt)("p",null,"For more information, see ",(0,i.kt)("a",{parentName:"p",href:"/coding/data-modeling#user_data"},"Data Modeling"),"."),(0,i.kt)("p",null,"Constraints:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,i.kt)("h3",{id:"timeout"},(0,i.kt)("inlineCode",{parentName:"h3"},"timeout")),(0,i.kt)("p",null,"This is the interval in seconds after a ",(0,i.kt)("a",{parentName:"p",href:"#flagspending"},(0,i.kt)("inlineCode",{parentName:"a"},"pending"))," transfer's\n",(0,i.kt)("a",{parentName:"p",href:"#timestamp"},"arrival at the cluster")," that it may be ",(0,i.kt)("a",{parentName:"p",href:"#flagspost_pending_transfer"},"posted")," or\n",(0,i.kt)("a",{parentName:"p",href:"#flagsvoid_pending_transfer"},"voided"),". Zero denotes absence of timeout."),(0,i.kt)("p",null,"Non-pending transfers cannot have a timeout."),(0,i.kt)("p",null,"Imported transfers cannot have a timeout."),(0,i.kt)("p",null,"TigerBeetle makes a best-effort approach to remove pending balances of expired transfers\nautomatically:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Transfers expire ",(0,i.kt)("em",{parentName:"p"},"exactly")," at their expiry time (",(0,i.kt)("a",{parentName:"p",href:"#timestamp"},(0,i.kt)("inlineCode",{parentName:"a"},"timestamp"))," ",(0,i.kt)("em",{parentName:"p"},"plus")," ",(0,i.kt)("inlineCode",{parentName:"p"},"timeout"),"\nconverted in nanoseconds).")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Pending balances will never be removed before its expiry.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Expired transfers cannot be manually posted or voided.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"It is not guaranteed that the pending balance will be removed exactly at its expiry."),(0,i.kt)("p",{parentName:"li"},"In particular, client requests may observe still-pending balances for expired transfers.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Pending balances are removed in chronological order by expiry. If multiple transfers expire at the\nsame time, then ordered by the transfer's creation ",(0,i.kt)("a",{parentName:"p",href:"#timestamp"},(0,i.kt)("inlineCode",{parentName:"a"},"timestamp")),"."),(0,i.kt)("p",{parentName:"li"},"If a transfer ",(0,i.kt)("inlineCode",{parentName:"p"},"A")," has expiry ",(0,i.kt)("inlineCode",{parentName:"p"},"E\u2081")," and transfer ",(0,i.kt)("inlineCode",{parentName:"p"},"B")," has expiry ",(0,i.kt)("inlineCode",{parentName:"p"},"E\u2082"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"E\u2081{r.d(t,{Zo:()=>p,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var o=n.createContext({}),u=function(e){var t=n.useContext(o),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},p=function(e){var t=u(e.components);return n.createElement(o.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=u(r),m=a,f=d["".concat(o,".").concat(m)]||d[m]||c[m]||i;return r?n.createElement(f,l(l({ref:t},p),{},{components:r})):n.createElement(f,l({ref:t},p))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,l=new Array(i);l[0]=m;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[d]="string"==typeof e?e:a,l[1]=s;for(var u=2;u{r.r(t),r.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var n=r(7462),a=(r(7294),r(3905));const i={sidebar_position:5},l="QueryFilter",s={unversionedId:"reference/query-filter",id:"reference/query-filter",title:"QueryFilter",description:"A QueryFilter is a record containing the filter parameters for",source:"@site/pages/reference/query-filter.md",sourceDirName:"reference",slug:"/reference/query-filter",permalink:"/reference/query-filter",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/query-filter.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"AccountFilter",permalink:"/reference/account-filter"},next:{title:"Requests",permalink:"/reference/requests/"}},o={},u=[{value:"Fields",id:"fields",level:2},{value:"user_data_128",id:"user_data_128",level:3},{value:"user_data_64",id:"user_data_64",level:3},{value:"user_data_32",id:"user_data_32",level:3},{value:"ledger",id:"ledger",level:3},{value:"code",id:"code",level:3},{value:"timestamp_min",id:"timestamp_min",level:3},{value:"timestamp_max",id:"timestamp_max",level:3},{value:"limit",id:"limit",level:3},{value:"flags",id:"flags",level:3},{value:"flags.reversed",id:"flagsreversed",level:4},{value:"reserved",id:"reserved",level:3}],p={toc:u},d="wrapper";function c(e){let{components:t,...r}=e;return(0,a.kt)(d,(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"queryfilter"},(0,a.kt)("inlineCode",{parentName:"h1"},"QueryFilter")),(0,a.kt)("p",null,"A ",(0,a.kt)("inlineCode",{parentName:"p"},"QueryFilter")," is a record containing the filter parameters for\n",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/query_accounts"},"querying accounts"),"\nand ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/query_transfers"},"querying transfers"),"."),(0,a.kt)("h2",{id:"fields"},"Fields"),(0,a.kt)("h3",{id:"user_data_128"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_128")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#user_data_128"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.user_data_128"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_128"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.user_data_128")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)")),(0,a.kt)("h3",{id:"user_data_64"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_64")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#user_data_64"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.user_data_64"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_64"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.user_data_64")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)")),(0,a.kt)("h3",{id:"user_data_32"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_32")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#user_data_32"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.user_data_32"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_32"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.user_data_32")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h3",{id:"ledger"},(0,a.kt)("inlineCode",{parentName:"h3"},"ledger")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.ledger"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.ledger")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h3",{id:"code"},(0,a.kt)("inlineCode",{parentName:"h3"},"code")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#code"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.code"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.code")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 16-bit unsigned integer (2 bytes)")),(0,a.kt)("h3",{id:"timestamp_min"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_min")),(0,a.kt)("p",null,"The minimum ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned,\ninclusive range.\nOptional; set to zero to disable the lower-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be ",(0,a.kt)("inlineCode",{parentName:"li"},"2^64 - 1"))),(0,a.kt)("h3",{id:"timestamp_max"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_max")),(0,a.kt)("p",null,"The maximum ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned,\ninclusive range.\nOptional; set to zero to disable the upper-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be ",(0,a.kt)("inlineCode",{parentName:"li"},"2^64 - 1"))),(0,a.kt)("h3",{id:"limit"},(0,a.kt)("inlineCode",{parentName:"h3"},"limit")),(0,a.kt)("p",null,"The maximum number of results that can be returned by this query."),(0,a.kt)("p",null,"Limited by the ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/#batching-events"},"maximum message size"),"."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be zero")),(0,a.kt)("h3",{id:"flags"},(0,a.kt)("inlineCode",{parentName:"h3"},"flags")),(0,a.kt)("p",null,"A bitfield that specifies querying behavior."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h4",{id:"flagsreversed"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.reversed")),(0,a.kt)("p",null,"Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the\nflag is not set, the event that happened first (has the smallest timestamp) will come first. If the\nflag is set, the event that happened last (has the largest timestamp) will come first."),(0,a.kt)("h3",{id:"reserved"},(0,a.kt)("inlineCode",{parentName:"h3"},"reserved")),(0,a.kt)("p",null,"This space may be used for additional data in the future."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 6 bytes"),(0,a.kt)("li",{parentName:"ul"},"Must be zero")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f310e26a.d8e2bec8.js b/assets/js/f310e26a.d8e2bec8.js deleted file mode 100644 index 0ae9bad3..00000000 --- a/assets/js/f310e26a.d8e2bec8.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9491],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var o=n.createContext({}),u=function(e){var t=n.useContext(o),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},p=function(e){var t=u(e.components);return n.createElement(o.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=u(r),m=a,f=d["".concat(o,".").concat(m)]||d[m]||c[m]||i;return r?n.createElement(f,l(l({ref:t},p),{},{components:r})):n.createElement(f,l({ref:t},p))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,l=new Array(i);l[0]=m;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[d]="string"==typeof e?e:a,l[1]=s;for(var u=2;u{r.r(t),r.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var n=r(7462),a=(r(7294),r(3905));const i={sidebar_position:5},l="QueryFilter",s={unversionedId:"reference/query-filter",id:"reference/query-filter",title:"QueryFilter",description:"A QueryFilter is a record containing the filter parameters for",source:"@site/pages/reference/query-filter.md",sourceDirName:"reference",slug:"/reference/query-filter",permalink:"/reference/query-filter",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/query-filter.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"AccountFilter",permalink:"/reference/account-filter"},next:{title:"Requests",permalink:"/reference/requests/"}},o={},u=[{value:"Fields",id:"fields",level:2},{value:"user_data_128",id:"user_data_128",level:3},{value:"user_data_64",id:"user_data_64",level:3},{value:"user_data_32",id:"user_data_32",level:3},{value:"ledger",id:"ledger",level:3},{value:"code",id:"code",level:3},{value:"timestamp_min",id:"timestamp_min",level:3},{value:"timestamp_max",id:"timestamp_max",level:3},{value:"limit",id:"limit",level:3},{value:"flags",id:"flags",level:3},{value:"flags.reversed",id:"flagsreversed",level:4},{value:"reserved",id:"reserved",level:3}],p={toc:u},d="wrapper";function c(e){let{components:t,...r}=e;return(0,a.kt)(d,(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"queryfilter"},(0,a.kt)("inlineCode",{parentName:"h1"},"QueryFilter")),(0,a.kt)("p",null,"A ",(0,a.kt)("inlineCode",{parentName:"p"},"QueryFilter")," is a record containing the filter parameters for\n",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/query_accounts"},"querying accounts"),"\nand ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/query_transfers"},"querying transfers"),"."),(0,a.kt)("h2",{id:"fields"},"Fields"),(0,a.kt)("h3",{id:"user_data_128"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_128")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#user_data_128"},"Account.user_data_128")," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_128"},"Transfer.user_data_128"),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)")),(0,a.kt)("h3",{id:"user_data_64"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_64")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#user_data_64"},"Account.user_data_64")," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_64"},"Transfer.user_data_64"),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)")),(0,a.kt)("h3",{id:"user_data_32"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_32")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#user_data_32"},"Account.user_data_32")," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_32"},"Transfer.user_data_32"),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h3",{id:"ledger"},(0,a.kt)("inlineCode",{parentName:"h3"},"ledger")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#ledger"},"Account.ledger")," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#ledger"},"Transfer.ledger"),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h3",{id:"code"},(0,a.kt)("inlineCode",{parentName:"h3"},"code")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#code"},"Account.code")," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#code"},"Transfer.code"),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 16-bit unsigned integer (2 bytes)")),(0,a.kt)("h3",{id:"timestamp_min"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_min")),(0,a.kt)("p",null,"The minimum ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned,\ninclusive range.\nOptional; set to zero to disable the lower-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be ",(0,a.kt)("inlineCode",{parentName:"li"},"2^64 - 1"))),(0,a.kt)("h3",{id:"timestamp_max"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_max")),(0,a.kt)("p",null,"The maximum ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," or\n",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned,\ninclusive range.\nOptional; set to zero to disable the upper-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be ",(0,a.kt)("inlineCode",{parentName:"li"},"2^64 - 1"))),(0,a.kt)("h3",{id:"limit"},(0,a.kt)("inlineCode",{parentName:"h3"},"limit")),(0,a.kt)("p",null,"The maximum number of results that can be returned by this query."),(0,a.kt)("p",null,"Limited by the ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/#batching-events"},"maximum message size"),"."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be zero")),(0,a.kt)("h3",{id:"flags"},(0,a.kt)("inlineCode",{parentName:"h3"},"flags")),(0,a.kt)("p",null,"A bitfield that specifies querying behavior."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h4",{id:"flagsreversed"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.reversed")),(0,a.kt)("p",null,"Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the\nflag is not set, the event that happened first (has the smallest timestamp) will come first. If the\nflag is set, the event that happened last (has the largest timestamp) will come first."),(0,a.kt)("h3",{id:"reserved"},(0,a.kt)("inlineCode",{parentName:"h3"},"reserved")),(0,a.kt)("p",null,"This space may be used for additional data in the future."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 6 bytes"),(0,a.kt)("li",{parentName:"ul"},"Must be zero")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f4744cb4.6027eb4c.js b/assets/js/f4744cb4.6027eb4c.js new file mode 100644 index 00000000..66ef4520 --- /dev/null +++ b/assets/js/f4744cb4.6027eb4c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7382],{3905:(e,t,n)=>{n.d(t,{Zo:()=>l,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),d=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},l=function(e){var t=d(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},_=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,l=c(e,["components","mdxType","originalType","parentName"]),p=d(n),_=r,m=p["".concat(s,".").concat(_)]||p[_]||u[_]||i;return n?a.createElement(m,o(o({ref:t},l),{},{components:n})):a.createElement(m,o({ref:t},l))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=_;var c={};for(var s in t)hasOwnProperty.call(t,s)&&(c[s]=t[s]);c.originalType=e,c[p]="string"==typeof e?e:r,o[1]=c;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>c,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const i={},o="create_accounts",c={unversionedId:"reference/requests/create_accounts",id:"reference/requests/create_accounts",title:"create_accounts",description:"Create one or more Accounts.",source:"@site/pages/reference/requests/create_accounts.md",sourceDirName:"reference/requests",slug:"/reference/requests/create_accounts",permalink:"/reference/requests/create_accounts",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/requests/create_accounts.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Requests",permalink:"/reference/requests/"},next:{title:"create_transfers",permalink:"/reference/requests/create_transfers"}},s={},d=[{value:"Event",id:"event",level:2},{value:"Result",id:"result",level:2},{value:"ok",id:"ok",level:3},{value:"linked_event_failed",id:"linked_event_failed",level:3},{value:"linked_event_chain_open",id:"linked_event_chain_open",level:3},{value:"imported_event_expected",id:"imported_event_expected",level:3},{value:"imported_event_not_expected",id:"imported_event_not_expected",level:3},{value:"timestamp_must_be_zero",id:"timestamp_must_be_zero",level:3},{value:"imported_event_timestamp_out_of_range",id:"imported_event_timestamp_out_of_range",level:3},{value:"imported_event_timestamp_must_not_advance",id:"imported_event_timestamp_must_not_advance",level:3},{value:"reserved_field",id:"reserved_field",level:3},{value:"reserved_flag",id:"reserved_flag",level:3},{value:"id_must_not_be_zero",id:"id_must_not_be_zero",level:3},{value:"id_must_not_be_int_max",id:"id_must_not_be_int_max",level:3},{value:"flags_are_mutually_exclusive",id:"flags_are_mutually_exclusive",level:3},{value:"debits_pending_must_be_zero",id:"debits_pending_must_be_zero",level:3},{value:"debits_posted_must_be_zero",id:"debits_posted_must_be_zero",level:3},{value:"credits_pending_must_be_zero",id:"credits_pending_must_be_zero",level:3},{value:"credits_posted_must_be_zero",id:"credits_posted_must_be_zero",level:3},{value:"ledger_must_not_be_zero",id:"ledger_must_not_be_zero",level:3},{value:"code_must_not_be_zero",id:"code_must_not_be_zero",level:3},{value:"exists_with_different_flags",id:"exists_with_different_flags",level:3},{value:"exists_with_different_user_data_128",id:"exists_with_different_user_data_128",level:3},{value:"exists_with_different_user_data_64",id:"exists_with_different_user_data_64",level:3},{value:"exists_with_different_user_data_32",id:"exists_with_different_user_data_32",level:3},{value:"exists_with_different_ledger",id:"exists_with_different_ledger",level:3},{value:"exists_with_different_code",id:"exists_with_different_code",level:3},{value:"exists",id:"exists",level:3},{value:"imported_event_timestamp_must_not_regress",id:"imported_event_timestamp_must_not_regress",level:3},{value:"Client libraries",id:"client-libraries",level:2},{value:"Internals",id:"internals",level:2}],l={toc:d},p="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"create_accounts"},(0,r.kt)("inlineCode",{parentName:"h1"},"create_accounts")),(0,r.kt)("p",null,"Create one or more ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Account")),"s."),(0,r.kt)("h2",{id:"event"},"Event"),(0,r.kt)("p",null,"The account to create. See ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Account"))," for constraints."),(0,r.kt)("h2",{id:"result"},"Result"),(0,r.kt)("p",null,"Results are listed in this section in order of descending precedence \u2014 that is, if more than one\nerror is applicable to the account being created, only the result listed first is returned."),(0,r.kt)("h3",{id:"ok"},(0,r.kt)("inlineCode",{parentName:"h3"},"ok")),(0,r.kt)("p",null,"The account was successfully created; it did not previously exist."),(0,r.kt)("p",null,"Note that ",(0,r.kt)("inlineCode",{parentName:"p"},"ok")," is generated by the client implementation; the network protocol does not include a\nresult when the account was successfully created."),(0,r.kt)("h3",{id:"linked_event_failed"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_failed")),(0,r.kt)("p",null,"The account was not created. One or more of the accounts in the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagslinked"},"linked chain")," is invalid, so the whole chain failed."),(0,r.kt)("h3",{id:"linked_event_chain_open"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_chain_open")),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.linked"))," flag was set on\nthe last event in the batch, which is not legal. (",(0,r.kt)("inlineCode",{parentName:"p"},"flags.linked")," indicates that the chain continues\nto the next operation)."),(0,r.kt)("h3",{id:"imported_event_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_expected")),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," was\nset on the first account of the batch, but not all accounts in the batch.\nBatches cannot mix imported accounts with non-imported accounts."),(0,r.kt)("h3",{id:"imported_event_not_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_not_expected")),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," was\nexpected to ",(0,r.kt)("em",{parentName:"p"},"not")," be set, as it's not allowed to mix accounts with different ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag\nin the same batch. The first account determines the entire operation."),(0,r.kt)("h3",{id:"timestamp_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"timestamp_must_be_zero")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is ",(0,r.kt)("em",{parentName:"p"},"not")," set."),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," is nonzero, but\nmust be zero. The cluster is responsible for setting this field."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," can only be assigned when creating accounts\nwith ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," set."),(0,r.kt)("h3",{id:"imported_event_timestamp_out_of_range"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_out_of_range")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is set."),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," is out of range,\nbut must be a user-defined timestamp greater than ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and less than ",(0,r.kt)("inlineCode",{parentName:"p"},"2^63"),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_advance"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_advance")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is set."),(0,r.kt)("p",null,"The account was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," is\ngreater than the current ",(0,r.kt)("a",{parentName:"p",href:"/coding/time"},"cluster time"),", but it must be a past timestamp."),(0,r.kt)("h3",{id:"reserved_field"},(0,r.kt)("inlineCode",{parentName:"h3"},"reserved_field")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#reserved"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.reserved"))," is nonzero, but must be\nzero."),(0,r.kt)("h3",{id:"reserved_flag"},(0,r.kt)("inlineCode",{parentName:"h3"},"reserved_flag")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"Account.flags.reserved")," is nonzero, but must be zero."),(0,r.kt)("h3",{id:"id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.id"))," is zero, which is a reserved value."),(0,r.kt)("h3",{id:"id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_int_max")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.id"))," is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", which is a reserved\nvalue."),(0,r.kt)("h3",{id:"flags_are_mutually_exclusive"},(0,r.kt)("inlineCode",{parentName:"h3"},"flags_are_mutually_exclusive")),(0,r.kt)("p",null,"The account was not created. An account cannot be created with the specified combination of\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags")),"."),(0,r.kt)("p",null,"The following flags are mutually exclusive:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/account#flagsdebits_must_not_exceed_credits"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.debits_must_not_exceed_credits"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/account#flagscredits_must_not_exceed_debits"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.credits_must_not_exceed_debits")))),(0,r.kt)("h3",{id:"debits_pending_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"debits_pending_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#debits_pending"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.debits_pending"))," is nonzero,\nbut must be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"debits_posted_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"debits_posted_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#debits_posted"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.debits_posted"))," is nonzero, but\nmust be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"credits_pending_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"credits_pending_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#credits_pending"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.credits_pending"))," is nonzero,\nbut must be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"credits_posted_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"credits_posted_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#credits_posted"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.credits_posted"))," is nonzero,\nbut must be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"ledger_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"ledger_must_not_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.ledger"))," is zero, but must be nonzero."),(0,r.kt)("h3",{id:"code_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"code_must_not_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#code"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.code"))," is zero, but must be nonzero."),(0,r.kt)("h3",{id:"exists_with_different_flags"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_flags")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"flags")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_128"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_128")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#user_data_128"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_128")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_64"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_64")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#user_data_64"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_64")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_32"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_32")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#user_data_32"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_32")),"."),(0,r.kt)("h3",{id:"exists_with_different_ledger"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_ledger")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger")),"."),(0,r.kt)("h3",{id:"exists_with_different_code"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_code")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#code"},(0,r.kt)("inlineCode",{parentName:"a"},"code")),"."),(0,r.kt)("h3",{id:"exists"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists."),(0,r.kt)("p",null,"With the possible exception of the following fields, the existing account is identical to the\naccount in the request:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"timestamp")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"debits_pending")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"debits_posted")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"credits_pending")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"credits_posted"))),(0,r.kt)("p",null,"To correctly ",(0,r.kt)("a",{parentName:"p",href:"/coding/reliable-transaction-submission"},"recover from application crashes"),",\nmany applications should handle ",(0,r.kt)("inlineCode",{parentName:"p"},"exists")," exactly as ",(0,r.kt)("a",{parentName:"p",href:"#ok"},(0,r.kt)("inlineCode",{parentName:"a"},"ok")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_regress"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_regress")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is set."),(0,r.kt)("p",null,"The account was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp")),"\nregressed, but it must be greater than the last timestamp assigned to any ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," in the cluster and cannot be equal to the timestamp of any existing ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer")),"."),(0,r.kt)("h2",{id:"client-libraries"},"Client libraries"),(0,r.kt)("p",null,"For language-specific docs see:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/dotnet#creating-accounts"},".NET library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/java#creating-accounts"},"Java library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/go#creating-accounts"},"Go library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/node#creating-accounts"},"Node.js library"))),(0,r.kt)("h2",{id:"internals"},"Internals"),(0,r.kt)("p",null,"If you're curious and want to learn more, you can find the source code for creating an account in\n",(0,r.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/state_machine.zig"},"src/state_machine.zig"),".\nSearch for ",(0,r.kt)("inlineCode",{parentName:"p"},"fn create_account(")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fn execute("),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f4744cb4.b3ee5ada.js b/assets/js/f4744cb4.b3ee5ada.js deleted file mode 100644 index 3e7807d5..00000000 --- a/assets/js/f4744cb4.b3ee5ada.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7382],{3905:(e,t,n)=>{n.d(t,{Zo:()=>l,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),d=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},l=function(e){var t=d(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},_=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,l=c(e,["components","mdxType","originalType","parentName"]),u=d(n),_=r,m=u["".concat(s,".").concat(_)]||u[_]||p[_]||i;return n?a.createElement(m,o(o({ref:t},l),{},{components:n})):a.createElement(m,o({ref:t},l))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=_;var c={};for(var s in t)hasOwnProperty.call(t,s)&&(c[s]=t[s]);c.originalType=e,c[u]="string"==typeof e?e:r,o[1]=c;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>p,frontMatter:()=>i,metadata:()=>c,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const i={},o="create_accounts",c={unversionedId:"reference/requests/create_accounts",id:"reference/requests/create_accounts",title:"create_accounts",description:"Create one or more Accounts.",source:"@site/pages/reference/requests/create_accounts.md",sourceDirName:"reference/requests",slug:"/reference/requests/create_accounts",permalink:"/reference/requests/create_accounts",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/requests/create_accounts.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Requests",permalink:"/reference/requests/"},next:{title:"create_transfers",permalink:"/reference/requests/create_transfers"}},s={},d=[{value:"Event",id:"event",level:2},{value:"Result",id:"result",level:2},{value:"ok",id:"ok",level:3},{value:"linked_event_failed",id:"linked_event_failed",level:3},{value:"linked_event_chain_open",id:"linked_event_chain_open",level:3},{value:"imported_event_expected",id:"imported_event_expected",level:3},{value:"imported_event_not_expected",id:"imported_event_not_expected",level:3},{value:"timestamp_must_be_zero",id:"timestamp_must_be_zero",level:3},{value:"imported_event_timestamp_out_of_range",id:"imported_event_timestamp_out_of_range",level:3},{value:"imported_event_timestamp_must_not_advance",id:"imported_event_timestamp_must_not_advance",level:3},{value:"reserved_field",id:"reserved_field",level:3},{value:"reserved_flag",id:"reserved_flag",level:3},{value:"id_must_not_be_zero",id:"id_must_not_be_zero",level:3},{value:"id_must_not_be_int_max",id:"id_must_not_be_int_max",level:3},{value:"flags_are_mutually_exclusive",id:"flags_are_mutually_exclusive",level:3},{value:"debits_pending_must_be_zero",id:"debits_pending_must_be_zero",level:3},{value:"debits_posted_must_be_zero",id:"debits_posted_must_be_zero",level:3},{value:"credits_pending_must_be_zero",id:"credits_pending_must_be_zero",level:3},{value:"credits_posted_must_be_zero",id:"credits_posted_must_be_zero",level:3},{value:"ledger_must_not_be_zero",id:"ledger_must_not_be_zero",level:3},{value:"code_must_not_be_zero",id:"code_must_not_be_zero",level:3},{value:"exists_with_different_flags",id:"exists_with_different_flags",level:3},{value:"exists_with_different_user_data_128",id:"exists_with_different_user_data_128",level:3},{value:"exists_with_different_user_data_64",id:"exists_with_different_user_data_64",level:3},{value:"exists_with_different_user_data_32",id:"exists_with_different_user_data_32",level:3},{value:"exists_with_different_ledger",id:"exists_with_different_ledger",level:3},{value:"exists_with_different_code",id:"exists_with_different_code",level:3},{value:"exists",id:"exists",level:3},{value:"imported_event_timestamp_must_not_regress",id:"imported_event_timestamp_must_not_regress",level:3},{value:"Client libraries",id:"client-libraries",level:2},{value:"Internals",id:"internals",level:2}],l={toc:d},u="wrapper";function p(e){let{components:t,...n}=e;return(0,r.kt)(u,(0,a.Z)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"create_accounts"},(0,r.kt)("inlineCode",{parentName:"h1"},"create_accounts")),(0,r.kt)("p",null,"Create one or more ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Account")),"s."),(0,r.kt)("h2",{id:"event"},"Event"),(0,r.kt)("p",null,"The account to create. See ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Account"))," for constraints."),(0,r.kt)("h2",{id:"result"},"Result"),(0,r.kt)("p",null,"Results are listed in this section in order of descending precedence \u2014 that is, if more than one\nerror is applicable to the account being created, only the result listed first is returned."),(0,r.kt)("h3",{id:"ok"},(0,r.kt)("inlineCode",{parentName:"h3"},"ok")),(0,r.kt)("p",null,"The account was successfully created; it did not previously exist."),(0,r.kt)("p",null,"Note that ",(0,r.kt)("inlineCode",{parentName:"p"},"ok")," is generated by the client implementation; the network protocol does not include a\nresult when the account was successfully created."),(0,r.kt)("h3",{id:"linked_event_failed"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_failed")),(0,r.kt)("p",null,"The account was not created. One or more of the accounts in the\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagslinked"},"linked chain")," is invalid, so the whole chain failed."),(0,r.kt)("h3",{id:"linked_event_chain_open"},(0,r.kt)("inlineCode",{parentName:"h3"},"linked_event_chain_open")),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.linked"))," flag was set on\nthe last event in the batch, which is not legal. (",(0,r.kt)("inlineCode",{parentName:"p"},"flags.linked")," indicates that the chain continues\nto the next operation)."),(0,r.kt)("h3",{id:"imported_event_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_expected")),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," was\nset on the first account of the batch, but not all accounts in the batch.\nBatches cannot mix imported accounts with non-imported accounts."),(0,r.kt)("h3",{id:"imported_event_not_expected"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_not_expected")),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," was\nexpected to ",(0,r.kt)("em",{parentName:"p"},"not")," be set, as it's not allowed to mix accounts with different ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag\nin the same batch. The first account determines the entire operation."),(0,r.kt)("h3",{id:"timestamp_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"timestamp_must_be_zero")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},"Account.flags.imported")," is ",(0,r.kt)("em",{parentName:"p"},"not")," set."),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," is nonzero, but\nmust be zero. The cluster is responsible for setting this field."),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," can only be assigned when creating accounts\nwith ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},"Account.flags.imported")," set."),(0,r.kt)("h3",{id:"imported_event_timestamp_out_of_range"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_out_of_range")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is set."),(0,r.kt)("p",null,"The account was not created. The ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," is out of range,\nbut must be a user-defined timestamp greater than ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and less than ",(0,r.kt)("inlineCode",{parentName:"p"},"2^63"),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_advance"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_advance")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.imported"))," is set."),(0,r.kt)("p",null,"The account was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp"))," is\ngreater than the current ",(0,r.kt)("a",{parentName:"p",href:"/coding/time"},"cluster time"),", but it must be a past timestamp."),(0,r.kt)("h3",{id:"reserved_field"},(0,r.kt)("inlineCode",{parentName:"h3"},"reserved_field")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#reserved"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.reserved"))," is nonzero, but must be\nzero."),(0,r.kt)("h3",{id:"reserved_flag"},(0,r.kt)("inlineCode",{parentName:"h3"},"reserved_flag")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("inlineCode",{parentName:"p"},"Account.flags.reserved")," is nonzero, but must be zero."),(0,r.kt)("h3",{id:"id_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.id"))," is zero, which is a reserved value."),(0,r.kt)("h3",{id:"id_must_not_be_int_max"},(0,r.kt)("inlineCode",{parentName:"h3"},"id_must_not_be_int_max")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#id"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.id"))," is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^128 - 1"),", which is a reserved\nvalue."),(0,r.kt)("h3",{id:"flags_are_mutually_exclusive"},(0,r.kt)("inlineCode",{parentName:"h3"},"flags_are_mutually_exclusive")),(0,r.kt)("p",null,"The account was not created. An account cannot be created with the specified combination of\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags")),"."),(0,r.kt)("p",null,"The following flags are mutually exclusive:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/account#flagsdebits_must_not_exceed_credits"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.debits_must_not_exceed_credits"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/reference/account#flagscredits_must_not_exceed_debits"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.flags.credits_must_not_exceed_debits")))),(0,r.kt)("h3",{id:"debits_pending_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"debits_pending_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#debits_pending"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.debits_pending"))," is nonzero,\nbut must be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"debits_posted_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"debits_posted_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#debits_posted"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.debits_posted"))," is nonzero, but\nmust be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"credits_pending_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"credits_pending_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#credits_pending"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.credits_pending"))," is nonzero,\nbut must be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"credits_posted_must_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"credits_posted_must_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#credits_posted"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.credits_posted"))," is nonzero,\nbut must be zero."),(0,r.kt)("p",null,"An account's debits and credits are only modified by transfers."),(0,r.kt)("h3",{id:"ledger_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"ledger_must_not_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.ledger"))," is zero, but must be nonzero."),(0,r.kt)("h3",{id:"code_must_not_be_zero"},(0,r.kt)("inlineCode",{parentName:"h3"},"code_must_not_be_zero")),(0,r.kt)("p",null,"The account was not created. ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#code"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.code"))," is zero, but must be nonzero."),(0,r.kt)("h3",{id:"exists_with_different_flags"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_flags")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flags"},(0,r.kt)("inlineCode",{parentName:"a"},"flags")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_128"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_128")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#user_data_128"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_128")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_64"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_64")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#user_data_64"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_64")),"."),(0,r.kt)("h3",{id:"exists_with_different_user_data_32"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_user_data_32")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different\n",(0,r.kt)("a",{parentName:"p",href:"/reference/account#user_data_32"},(0,r.kt)("inlineCode",{parentName:"a"},"user_data_32")),"."),(0,r.kt)("h3",{id:"exists_with_different_ledger"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_ledger")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger")),"."),(0,r.kt)("h3",{id:"exists_with_different_code"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists_with_different_code")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists, but with different ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#code"},(0,r.kt)("inlineCode",{parentName:"a"},"code")),"."),(0,r.kt)("h3",{id:"exists"},(0,r.kt)("inlineCode",{parentName:"h3"},"exists")),(0,r.kt)("p",null,"An account with the same ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," already exists."),(0,r.kt)("p",null,"With the possible exception of the following fields, the existing account is identical to the\naccount in the request:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"timestamp")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"debits_pending")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"debits_posted")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"credits_pending")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"credits_posted"))),(0,r.kt)("p",null,"To correctly ",(0,r.kt)("a",{parentName:"p",href:"/coding/reliable-transaction-submission"},"recover from application crashes"),",\nmany applications should handle ",(0,r.kt)("inlineCode",{parentName:"p"},"exists")," exactly as ",(0,r.kt)("a",{parentName:"p",href:"#ok"},(0,r.kt)("inlineCode",{parentName:"a"},"ok")),"."),(0,r.kt)("h3",{id:"imported_event_timestamp_must_not_regress"},(0,r.kt)("inlineCode",{parentName:"h3"},"imported_event_timestamp_must_not_regress")),(0,r.kt)("p",null,"This result only applies when ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#flagsimported"},"Account.flags.imported")," is set."),(0,r.kt)("p",null,"The account was not created. The user-defined ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#timestamp"},(0,r.kt)("inlineCode",{parentName:"a"},"Account.timestamp")),"\nregressed, but it must be greater than the last timestamp assigned to any ",(0,r.kt)("inlineCode",{parentName:"p"},"Account")," in the cluster and cannot be equal to the timestamp of any existing ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"Transfer")),"."),(0,r.kt)("h2",{id:"client-libraries"},"Client libraries"),(0,r.kt)("p",null,"For language-specific docs see:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/dotnet#creating-accounts"},".NET library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/java#creating-accounts"},"Java library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/go#creating-accounts"},"Go library")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/clients/node#creating-accounts"},"Node.js library"))),(0,r.kt)("h2",{id:"internals"},"Internals"),(0,r.kt)("p",null,"If you're curious and want to learn more, you can find the source code for creating an account in\n",(0,r.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/state_machine.zig"},"src/state_machine.zig"),".\nSearch for ",(0,r.kt)("inlineCode",{parentName:"p"},"fn create_account(")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fn execute("),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.430bce87.js b/assets/js/runtime~main.54525745.js similarity index 91% rename from assets/js/runtime~main.430bce87.js rename to assets/js/runtime~main.54525745.js index 724271c2..e7f09af3 100644 --- a/assets/js/runtime~main.430bce87.js +++ b/assets/js/runtime~main.54525745.js @@ -1 +1 @@ -(()=>{"use strict";var e,c,a,f,t,b={},r={};function d(e){var c=r[e];if(void 0!==c)return c.exports;var a=r[e]={exports:{}};return b[e].call(a.exports,a,a.exports,d),a.exports}d.m=b,e=[],d.O=(c,a,f,t)=>{if(!a){var b=1/0;for(i=0;i=t)&&Object.keys(d.O).every((e=>d.O[e](a[o])))?a.splice(o--,1):(r=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[a,f,t]},d.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return d.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var t=Object.create(null);d.r(t);var b={};c=c||[null,a({}),a([]),a(a)];for(var r=2&f&&e;"object"==typeof r&&!~c.indexOf(r);r=a(r))Object.getOwnPropertyNames(r).forEach((c=>b[c]=()=>e[c]));return b.default=()=>e,d.d(t,b),t},d.d=(e,c)=>{for(var a in c)d.o(c,a)&&!d.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((c,a)=>(d.f[a](e,c),c)),[])),d.u=e=>"assets/js/"+({53:"935f2afb",113:"f6069575",199:"583d611f",333:"8cb2464c",379:"f7ad50ac",400:"350546f3",499:"3004abec",771:"e22d4ec9",885:"9b0eb4fb",962:"51c41399",1372:"13a99faa",1488:"7398e46d",1564:"7c1a6224",1677:"feb55396",2024:"c227177d",2032:"79002dcb",2132:"b51c73cc",2500:"de64a4bc",2519:"a4a8880c",2539:"b373b0c3",2738:"2fce5429",3042:"cab96357",3489:"b0f69697",4038:"34d38065",4317:"59cf55a9",4396:"4051a670",4698:"280500df",5511:"ac624c80",5610:"d135e03c",5614:"970deb07",5616:"af51e3cb",5681:"2209f33f",5885:"53635fc1",6201:"2d5c62ec",6214:"85b859d5",6221:"675d658f",6441:"b2e1ac4e",6641:"8388e350",6834:"214199fe",6994:"4fb5df53",7042:"625a5f2a",7092:"1feeaa2e",7112:"c76e3e11",7382:"f4744cb4",7596:"2b4a1ee0",7736:"99843d3e",7784:"d1ef64c4",7918:"17896441",7920:"1a4e3797",7938:"3c8e1e2c",7996:"27273ae6",8071:"38d047e3",8227:"69a6ec57",8404:"eeac8955",8775:"b2d6321c",9007:"f482cb0c",9049:"8b046af8",9154:"13142b44",9259:"0be03cf2",9290:"48f60023",9491:"f310e26a",9514:"1be78505",9530:"d3a362a2",9743:"2cf7febb"}[e]||e)+"."+{53:"b6f4cb6e",113:"fe2ecf32",199:"d5fa9b78",333:"6002416e",379:"420d4d9b",400:"c1b6cd69",499:"358e5244",771:"70ef1af3",885:"bc411fef",962:"0fb6fb58",1372:"7e453912",1426:"5959ada0",1488:"612528e3",1564:"647f2fba",1677:"d1c1c491",2024:"71f7e4dc",2032:"fce3a44a",2132:"9851cc63",2500:"451fd145",2519:"cd03e428",2539:"fc1667dd",2738:"0b2bd283",3042:"71d29d6c",3489:"9e4b8def",4038:"038c237e",4317:"eeffad2b",4396:"c0366401",4698:"aae615aa",4972:"16297f58",5511:"6b6801d8",5610:"4e33dbe0",5614:"3a9b1cd3",5616:"d9a58c29",5681:"9d675f26",5885:"3c68c3e0",6201:"8f3fba57",6214:"b2a12386",6221:"d2918bb2",6316:"fdbc5257",6441:"9deb9562",6641:"a1ec4c84",6834:"907f6d80",6945:"e6ca558a",6994:"33c30321",7042:"0128e97a",7092:"17039da5",7112:"1ee2c9d2",7382:"b3ee5ada",7596:"032d72ca",7724:"492d96d5",7736:"25678062",7784:"e5aff3c4",7918:"51037d8a",7920:"aed7c3e9",7938:"55cd56a9",7996:"75904e74",8071:"79054ebd",8227:"9d873408",8404:"2ec07622",8775:"c88352dd",8894:"547a1c8d",9007:"67df2f0a",9049:"a982777d",9154:"2744f1bc",9259:"ba5f69c3",9290:"81cedd6f",9487:"8ebb94ec",9491:"d8e2bec8",9514:"f4f69e1a",9530:"f623f861",9743:"1308ecc4"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),f={},t="docs:",d.l=(e,c,a,b)=>{if(f[e])f[e].push(c);else{var r,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var t=f[e];if(delete f[e],r.parentNode&&r.parentNode.removeChild(r),t&&t.forEach((e=>e(a))),c)return c(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=l.bind(null,r.onerror),r.onload=l.bind(null,r.onload),o&&document.head.appendChild(r)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/",d.gca=function(e){return e={17896441:"7918","935f2afb":"53",f6069575:"113","583d611f":"199","8cb2464c":"333",f7ad50ac:"379","350546f3":"400","3004abec":"499",e22d4ec9:"771","9b0eb4fb":"885","51c41399":"962","13a99faa":"1372","7398e46d":"1488","7c1a6224":"1564",feb55396:"1677",c227177d:"2024","79002dcb":"2032",b51c73cc:"2132",de64a4bc:"2500",a4a8880c:"2519",b373b0c3:"2539","2fce5429":"2738",cab96357:"3042",b0f69697:"3489","34d38065":"4038","59cf55a9":"4317","4051a670":"4396","280500df":"4698",ac624c80:"5511",d135e03c:"5610","970deb07":"5614",af51e3cb:"5616","2209f33f":"5681","53635fc1":"5885","2d5c62ec":"6201","85b859d5":"6214","675d658f":"6221",b2e1ac4e:"6441","8388e350":"6641","214199fe":"6834","4fb5df53":"6994","625a5f2a":"7042","1feeaa2e":"7092",c76e3e11:"7112",f4744cb4:"7382","2b4a1ee0":"7596","99843d3e":"7736",d1ef64c4:"7784","1a4e3797":"7920","3c8e1e2c":"7938","27273ae6":"7996","38d047e3":"8071","69a6ec57":"8227",eeac8955:"8404",b2d6321c:"8775",f482cb0c:"9007","8b046af8":"9049","13142b44":"9154","0be03cf2":"9259","48f60023":"9290",f310e26a:"9491","1be78505":"9514",d3a362a2:"9530","2cf7febb":"9743"}[e]||e,d.p+d.u(e)},(()=>{var e={1303:0,532:0};d.f.j=(c,a)=>{var f=d.o(e,c)?e[c]:void 0;if(0!==f)if(f)a.push(f[2]);else if(/^(1303|532)$/.test(c))e[c]=0;else{var t=new Promise(((a,t)=>f=e[c]=[a,t]));a.push(f[2]=t);var b=d.p+d.u(c),r=new Error;d.l(b,(a=>{if(d.o(e,c)&&(0!==(f=e[c])&&(e[c]=void 0),f)){var t=a&&("load"===a.type?"missing":a.type),b=a&&a.target&&a.target.src;r.message="Loading chunk "+c+" failed.\n("+t+": "+b+")",r.name="ChunkLoadError",r.type=t,r.request=b,f[1](r)}}),"chunk-"+c,c)}},d.O.j=c=>0===e[c];var c=(c,a)=>{var f,t,b=a[0],r=a[1],o=a[2],n=0;if(b.some((c=>0!==e[c]))){for(f in r)d.o(r,f)&&(d.m[f]=r[f]);if(o)var i=o(d)}for(c&&c(a);n{"use strict";var e,c,a,f,t,b={},r={};function d(e){var c=r[e];if(void 0!==c)return c.exports;var a=r[e]={exports:{}};return b[e].call(a.exports,a,a.exports,d),a.exports}d.m=b,e=[],d.O=(c,a,f,t)=>{if(!a){var b=1/0;for(i=0;i=t)&&Object.keys(d.O).every((e=>d.O[e](a[o])))?a.splice(o--,1):(r=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[a,f,t]},d.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return d.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var t=Object.create(null);d.r(t);var b={};c=c||[null,a({}),a([]),a(a)];for(var r=2&f&&e;"object"==typeof r&&!~c.indexOf(r);r=a(r))Object.getOwnPropertyNames(r).forEach((c=>b[c]=()=>e[c]));return b.default=()=>e,d.d(t,b),t},d.d=(e,c)=>{for(var a in c)d.o(c,a)&&!d.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((c,a)=>(d.f[a](e,c),c)),[])),d.u=e=>"assets/js/"+({53:"935f2afb",113:"f6069575",199:"583d611f",333:"8cb2464c",379:"f7ad50ac",400:"350546f3",499:"3004abec",771:"e22d4ec9",885:"9b0eb4fb",962:"51c41399",1372:"13a99faa",1488:"7398e46d",1564:"7c1a6224",1677:"feb55396",2024:"c227177d",2032:"79002dcb",2132:"b51c73cc",2500:"de64a4bc",2519:"a4a8880c",2539:"b373b0c3",2738:"2fce5429",3042:"cab96357",3489:"b0f69697",4038:"34d38065",4317:"59cf55a9",4396:"4051a670",4698:"280500df",5511:"ac624c80",5610:"d135e03c",5614:"970deb07",5616:"af51e3cb",5681:"2209f33f",5885:"53635fc1",6201:"2d5c62ec",6214:"85b859d5",6221:"675d658f",6441:"b2e1ac4e",6641:"8388e350",6834:"214199fe",6994:"4fb5df53",7042:"625a5f2a",7092:"1feeaa2e",7112:"c76e3e11",7382:"f4744cb4",7596:"2b4a1ee0",7736:"99843d3e",7784:"d1ef64c4",7918:"17896441",7920:"1a4e3797",7938:"3c8e1e2c",7996:"27273ae6",8071:"38d047e3",8227:"69a6ec57",8404:"eeac8955",8775:"b2d6321c",9007:"f482cb0c",9049:"8b046af8",9154:"13142b44",9259:"0be03cf2",9290:"48f60023",9491:"f310e26a",9514:"1be78505",9530:"d3a362a2",9743:"2cf7febb"}[e]||e)+"."+{53:"b6f4cb6e",113:"fe2ecf32",199:"d5fa9b78",333:"60d68d0d",379:"420d4d9b",400:"c1b6cd69",499:"358e5244",771:"70ef1af3",885:"ae095492",962:"0fb6fb58",1372:"46b4d6cd",1426:"5959ada0",1488:"612528e3",1564:"647f2fba",1677:"d1c1c491",2024:"71f7e4dc",2032:"fce3a44a",2132:"9851cc63",2500:"451fd145",2519:"cd03e428",2539:"fc1667dd",2738:"0b2bd283",3042:"71d29d6c",3489:"9e4b8def",4038:"038c237e",4317:"eeffad2b",4396:"c0366401",4698:"aae615aa",4972:"16297f58",5511:"87bfbbd1",5610:"4e33dbe0",5614:"3a9b1cd3",5616:"d9a58c29",5681:"9d675f26",5885:"3c68c3e0",6201:"8f3fba57",6214:"b2a12386",6221:"d2918bb2",6316:"fdbc5257",6441:"9deb9562",6641:"63e0b00a",6834:"907f6d80",6945:"e6ca558a",6994:"33c30321",7042:"0128e97a",7092:"17039da5",7112:"1ee2c9d2",7382:"6027eb4c",7596:"032d72ca",7724:"492d96d5",7736:"25678062",7784:"e5aff3c4",7918:"51037d8a",7920:"aed7c3e9",7938:"55cd56a9",7996:"75904e74",8071:"79054ebd",8227:"9d873408",8404:"2ec07622",8775:"c1915e3d",8894:"547a1c8d",9007:"67df2f0a",9049:"a982777d",9154:"2744f1bc",9259:"ba5f69c3",9290:"81cedd6f",9487:"8ebb94ec",9491:"28734c2e",9514:"f4f69e1a",9530:"f623f861",9743:"dd3e76e4"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),f={},t="docs:",d.l=(e,c,a,b)=>{if(f[e])f[e].push(c);else{var r,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var t=f[e];if(delete f[e],r.parentNode&&r.parentNode.removeChild(r),t&&t.forEach((e=>e(a))),c)return c(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=l.bind(null,r.onerror),r.onload=l.bind(null,r.onload),o&&document.head.appendChild(r)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/",d.gca=function(e){return e={17896441:"7918","935f2afb":"53",f6069575:"113","583d611f":"199","8cb2464c":"333",f7ad50ac:"379","350546f3":"400","3004abec":"499",e22d4ec9:"771","9b0eb4fb":"885","51c41399":"962","13a99faa":"1372","7398e46d":"1488","7c1a6224":"1564",feb55396:"1677",c227177d:"2024","79002dcb":"2032",b51c73cc:"2132",de64a4bc:"2500",a4a8880c:"2519",b373b0c3:"2539","2fce5429":"2738",cab96357:"3042",b0f69697:"3489","34d38065":"4038","59cf55a9":"4317","4051a670":"4396","280500df":"4698",ac624c80:"5511",d135e03c:"5610","970deb07":"5614",af51e3cb:"5616","2209f33f":"5681","53635fc1":"5885","2d5c62ec":"6201","85b859d5":"6214","675d658f":"6221",b2e1ac4e:"6441","8388e350":"6641","214199fe":"6834","4fb5df53":"6994","625a5f2a":"7042","1feeaa2e":"7092",c76e3e11:"7112",f4744cb4:"7382","2b4a1ee0":"7596","99843d3e":"7736",d1ef64c4:"7784","1a4e3797":"7920","3c8e1e2c":"7938","27273ae6":"7996","38d047e3":"8071","69a6ec57":"8227",eeac8955:"8404",b2d6321c:"8775",f482cb0c:"9007","8b046af8":"9049","13142b44":"9154","0be03cf2":"9259","48f60023":"9290",f310e26a:"9491","1be78505":"9514",d3a362a2:"9530","2cf7febb":"9743"}[e]||e,d.p+d.u(e)},(()=>{var e={1303:0,532:0};d.f.j=(c,a)=>{var f=d.o(e,c)?e[c]:void 0;if(0!==f)if(f)a.push(f[2]);else if(/^(1303|532)$/.test(c))e[c]=0;else{var t=new Promise(((a,t)=>f=e[c]=[a,t]));a.push(f[2]=t);var b=d.p+d.u(c),r=new Error;d.l(b,(a=>{if(d.o(e,c)&&(0!==(f=e[c])&&(e[c]=void 0),f)){var t=a&&("load"===a.type?"missing":a.type),b=a&&a.target&&a.target.src;r.message="Loading chunk "+c+" failed.\n("+t+": "+b+")",r.name="ChunkLoadError",r.type=t,r.request=b,f[1](r)}}),"chunk-"+c,c)}},d.O.j=c=>0===e[c];var c=(c,a)=>{var f,t,b=a[0],r=a[1],o=a[2],n=0;if(b.some((c=>0!==e[c]))){for(f in r)d.o(r,f)&&(d.m[f]=r[f]);if(o)var i=o(d)}for(c&&c(a);n.NET | TigerBeetle Docs - + @@ -114,7 +114,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    var accountsBatch = new System.Collections.Generic.List<Account>();
    for (var index = 0; index < historicalAccounts.Length; index++)
    {
    var account = historicalAccounts[index];

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    account.Timestamp = historicalTimestamp;
    // Set the account as `imported`.
    account.Flags = AccountFlags.Imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalAccounts.Length - 1)
    {
    account.Flags |= AccountFlags.Linked;
    }

    accountsBatch.Add(account);
    }

    createAccountsError = client.CreateAccounts(accountsBatch.ToArray());
    // Error handling omitted.

    // Then, load and import all transfers with their timestamps from the historical source.
    var transfersBatch = new System.Collections.Generic.List<Transfer>();
    for (var index = 0; index < historicalTransfers.Length; index++)
    {
    var transfer = historicalTransfers[index];

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    transfer.Timestamp = historicalTimestamp;
    // Set the account as `imported`.
    transfer.Flags = TransferFlags.Imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalTransfers.Length - 1)
    {
    transfer.Flags |= TransferFlags.Linked;
    }

    transfersBatch.Add(transfer);
    }

    createTransfersError = client.CreateTransfers(transfersBatch.ToArray());
    // Error handling omitted.
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/clients/go/index.html b/clients/go/index.html index d95ae9d1..9ede42e6 100644 --- a/clients/go/index.html +++ b/clients/go/index.html @@ -6,7 +6,7 @@ Go | TigerBeetle Docs - + @@ -78,7 +78,7 @@ TigerBeetle will post the transfer. TigerBeetle will atomically roll back the changes to debits_pending and credits_pending of the appropriate accounts and apply them to the debits_posted and -credits_posted balances.

    transfer := Transfer{
    ID: ToUint128(2),
    // Post the entire pending amount.
    Amount: AmountMax,
    PendingID: ToUint128(1),
    Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(),
    Timestamp: 0,
    }
    transfersRes, err = client.CreateTransfers([]Transfer{transfer})
    // Error handling omitted.

    Void a Pending Transfer

    In contrast, with flags set to void_pending_transfer, +credits_posted balances.

    transfer := Transfer{
    ID: ToUint128(2),
    // Post the entire pending amount.
    Amount: AmountMax,
    PendingID: ToUint128(1),
    Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(),
    Timestamp: 0,
    }
    transfersRes, err = client.CreateTransfers([]Transfer{transfer})
    // Error handling omitted.

    Void a Pending Transfer

    In contrast, with flags set to void_pending_transfer, TigerBeetle will void the transfer. TigerBeetle will roll back the changes to debits_pending and credits_pending of the appropriate accounts and not apply them to the debits_posted and @@ -118,7 +118,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    accountsBatch := []Account{}
    for index, account := range historicalAccounts {
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1
    account.Timestamp = historicalTimestamp

    account.Flags = AccountFlags{
    // Set the account as `imported`.
    Imported: true,
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    Linked: index < len(historicalAccounts)-1,
    }.ToUint16()

    accountsBatch = append(accountsBatch, account)
    }
    accountsRes, err = client.CreateAccounts(accountsBatch)

    // Then, load and import all transfers with their timestamps from the historical source.
    transfersBatch := []Transfer{}
    for index, transfer := range historicalTransfers {
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1
    transfer.Timestamp = historicalTimestamp

    transfer.Flags = TransferFlags{
    // Set the transfer as `imported`.
    Imported: true,
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    Linked: index < len(historicalAccounts)-1,
    }.ToUint16()

    transfersBatch = append(transfersBatch, transfer)
    }
    transfersRes, err = client.CreateTransfers(transfersBatch)
    // Error handling omitted..
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/clients/java/index.html b/clients/java/index.html index 606b15db..eade4aeb 100644 --- a/clients/java/index.html +++ b/clients/java/index.html @@ -6,7 +6,7 @@ Java | TigerBeetle Docs - + @@ -112,7 +112,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    accounts = new AccountBatch(historicalAccounts.length);
    for(int index = 0; index < historicalAccounts.length; index += 1) {
    accounts.add();

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    accounts.setTimestamp(historicalTimestamp);
    // Set the account as `imported`.
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalAccounts.length - 1) {
    accounts.setFlags(AccountFlags.IMPORTED | AccountFlags.LINKED);
    } else {
    accounts.setFlags(AccountFlags.IMPORTED);
    }

    // Populate the rest of the account:
    // accounts.setId(historicalAccounts[index].Id);
    // accounts.setCode(historicalAccounts[index].Code);
    }
    accountErrors = client.createAccounts(accounts);
    // Error handling omitted.

    // Then, load and import all transfers with their timestamps from the historical source.
    transfers = new TransferBatch(historicalTransfers.length);
    for(int index = 0; index < historicalTransfers.length; index += 1) {
    transfers.add();

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    transfers.setTimestamp(historicalTimestamp);
    // Set the account as `imported`.
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalTransfers.length - 1) {
    transfers.setFlags(TransferFlags.IMPORTED | TransferFlags.LINKED);
    } else {
    transfers.setFlags(TransferFlags.IMPORTED);
    }

    // Populate the rest of the transfer:
    // transfers.setId(historicalTransfers[index].Id);
    // transfers.setCode(historicalTransfers[index].Code);
    }
    transferErrors = client.createTransfers(transfers);
    // Error handling omitted.
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/clients/node/index.html b/clients/node/index.html index e80271cd..7ad71151 100644 --- a/clients/node/index.html +++ b/clients/node/index.html @@ -6,7 +6,7 @@ Node.js | TigerBeetle Docs - + @@ -119,7 +119,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    const accountsBatch = [];
    for (let index = 0; i < historicalAccounts.length; i++) {
    let account = historicalAccounts[i];
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    account.timestamp = historicalTimestamp;
    // Set the account as `imported`.
    account.flags = AccountFlags.imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalAccounts.length - 1) {
    account.flags |= AccountFlags.linked;
    }

    accountsBatch.push(account);
    }
    accountErrors = await client.createAccounts(accountsBatch);

    // Error handling omitted.
    // Then, load and import all transfers with their timestamps from the historical source.
    const transfersBatch = [];
    for (let index = 0; i < historicalTransfers.length; i++) {
    let transfer = historicalTransfers[i];
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    transfer.timestamp = historicalTimestamp;
    // Set the account as `imported`.
    transfer.flags = TransferFlags.imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalTransfers.length - 1) {
    transfer.flags |= TransferFlags.linked;
    }

    transfersBatch.push(transfer);
    }
    transferErrors = await client.createAccounts(transfersBatch);
    // Error handling omitted.
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/coding/data-modeling/index.html b/coding/data-modeling/index.html index 60628fca..4e26e43d 100644 --- a/coding/data-modeling/index.html +++ b/coding/data-modeling/index.html @@ -6,7 +6,7 @@ Data Modeling | TigerBeetle Docs - + @@ -90,7 +90,7 @@ happening, such as a purchase, refund, currency exchange, etc.

    When you start building out your application on top of TigerBeetle, you may find it helpful to list out all of the known types of accounts and movements of funds and mapping each of these to code numbers or ranges.

    - + \ No newline at end of file diff --git a/coding/financial-accounting/index.html b/coding/financial-accounting/index.html index bea59120..3a86b188 100644 --- a/coding/financial-accounting/index.html +++ b/coding/financial-accounting/index.html @@ -6,7 +6,7 @@ Financial Accounting | TigerBeetle Docs - + @@ -82,7 +82,7 @@ of TigerBeetle in your architecture?

    Let us help you get it right. Contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com to set up a call.

    - + \ No newline at end of file diff --git a/coding/index.html b/coding/index.html index 48bed319..e6894563 100644 --- a/coding/index.html +++ b/coding/index.html @@ -6,7 +6,7 @@ Developing Applications on TigerBeetle | TigerBeetle Docs - + @@ -29,7 +29,7 @@ you might have!

    Dedicated Consultation

    Would you like the TigerBeetle team to help you design your chart of accounts and leverage the power of TigerBeetle in your architecture?

    Let us help you get it right. Contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com to set up a call.

    - + \ No newline at end of file diff --git a/coding/recipes/balance-bounds/index.html b/coding/recipes/balance-bounds/index.html index 1e049298..cc570266 100644 --- a/coding/recipes/balance-bounds/index.html +++ b/coding/recipes/balance-bounds/index.html @@ -6,7 +6,7 @@ Balance Bounds | TigerBeetle Docs - + @@ -32,7 +32,7 @@ Account's funds, even if it succeeds.

    If everything to this point succeeds, the fourth and fifth transfers simply undo the effects of the second and third transfers. The fourth transfer voids the pending transfer. And the fifth transfer resets the Control Account's net balance to zero.

    - + \ No newline at end of file diff --git a/coding/recipes/balance-conditional-transfers/index.html b/coding/recipes/balance-conditional-transfers/index.html index 3e25e046..7ed8aa4c 100644 --- a/coding/recipes/balance-conditional-transfers/index.html +++ b/coding/recipes/balance-conditional-transfers/index.html @@ -6,7 +6,7 @@ Balance-Conditional Transfers | TigerBeetle Docs - + @@ -31,7 +31,7 @@ account from the control account. Then, the third transfer would execute the desired transfer to the ultimate destination account.

    Note that in the tables above, we do the balance check on the source account. The balance check could also be applied to the destination account instead.

    - + \ No newline at end of file diff --git a/coding/recipes/close-account/index.html b/coding/recipes/close-account/index.html index db841ab9..534659b9 100644 --- a/coding/recipes/close-account/index.html +++ b/coding/recipes/close-account/index.html @@ -6,7 +6,7 @@ Close Account | TigerBeetle Docs - + @@ -20,7 +20,7 @@ application does not need to know (or query) the balance prior to closing the account.

    The stored transfer's amount will be set to the actual amount transferred.

  • T2 and T4 are closing transfers that will cause the respective account to be closed.

    The closing transfer must be also a pending transfer so the action can be reversible.

  • After committing these transfers, A and B are closed with net balance zero, and will reject any further transfers.

    AccountDebits PendingDebits PostedCredits PendingCredits PostedFlags
    A020020debits_must_not_exceed_credits, closed
    B030030credits_must_not_exceed_debits, closed
    C025010

    To re-open the closed account, the pending closing transfer can be voided, reverting the closing action (but not reverting the net balance):

    TransferDebit AccountCredit AccountAmountPending TransferFlags
    T5AC0T2void_pending_transfer
    T6CB0T4void_pending_transfer

    After committing these transfers, A and B are re-opened and can accept transfers again:

    AccountDebits PendingDebits PostedCredits PendingCredits PostedFlags
    A020020debits_must_not_exceed_credits
    B030030credits_must_not_exceed_debits
    C025010
    - + \ No newline at end of file diff --git a/coding/recipes/correcting-transfers/index.html b/coding/recipes/correcting-transfers/index.html index f6f10eef..a5f62483 100644 --- a/coding/recipes/correcting-transfers/index.html +++ b/coding/recipes/correcting-transfers/index.html @@ -6,7 +6,7 @@ Correcting Transfers | TigerBeetle Docs - + @@ -31,7 +31,7 @@ would submit two additional transfers going in the opposite direction:

    LedgerDebit AccountCredit AccountAmountcodeuser_data_128flags.linked
    USDXA100010000123456true
    USDYA510000123456false

    Note that the codes used here don't have any actual meaning, but you would want to enumerate your business events and map each to a numeric code value, including the initial reasons for transfers and the reasons they might be corrected.

    - + \ No newline at end of file diff --git a/coding/recipes/currency-exchange/index.html b/coding/recipes/currency-exchange/index.html index 5307e657..18b2aaca 100644 --- a/coding/recipes/currency-exchange/index.html +++ b/coding/recipes/currency-exchange/index.html @@ -6,7 +6,7 @@ Currency Exchange | TigerBeetle Docs - + @@ -29,7 +29,7 @@ it implicitly records the exchange rate and spread at the time of the exchange — information that cannot be derived if the two are combined.

    Example

    This depicts the same scenario as the prior example, except the liquidity provider charges a $0.10 fee for the transaction.

    LedgerDebit AccountCredit AccountAmountflags.linked
    USDA₁L₁10000true
    USDA₁L₁10true
    INRL₂A₂8242135false
    - + \ No newline at end of file diff --git a/coding/recipes/multi-debit-credit-transfers/index.html b/coding/recipes/multi-debit-credit-transfers/index.html index 61673865..6c8bd7fb 100644 --- a/coding/recipes/multi-debit-credit-transfers/index.html +++ b/coding/recipes/multi-debit-credit-transfers/index.html @@ -6,7 +6,7 @@ Multi-Debit, Multi-Credit Transfers | TigerBeetle Docs - + @@ -16,15 +16,19 @@ credits. For example, you might have a transfer where you want to extract fees and/or taxes.

    Read on to see how to implement one-to-many and many-to-many transfers!

    Note that all of these examples use the Linked Transfers flag (flags.linked) to ensure that all of the transfers succeed or fail together.

    One-to-Many Transfers

    Transactions that involve multiple debits and a single credit OR a single debit and multiple credits -are relatively straightforward.

    You can use multiple linked transfers as depicted below.

    Single Debit, Multiple Credits

    This example debits a single account and credits multiple accounts. It uses the following accounts:

    • A source account A, on the USD ledger.
    • Three destination accounts X, Y, and Z, on the USD ledger.
    LedgerDebit AccountCredit AccountAmountflags.linked
    USDAX10000true
    USDAY50true
    USDAZ10false

    Multiple Debits, Single Credit

    This example debits multiple accounts and credits a single account. It uses the following accounts:

    • Three source accounts A, B, and C on the USD ledger.
    • A destination account X on the USD ledger.
    LedgerDebit AccountCredit AccountAmountflags.linked
    USDAX10000true
    USDBX50true
    USDCX10false

    Many-to-Many Transfers

    Transactions with multiple debits and multiple credits are a bit more involved (but you got this!).

    This is where the accounting concept of a Control Account comes in handy. We can use this as an +are relatively straightforward.

    You can use multiple linked transfers as depicted below.

    Single Debit, Multiple Credits

    This example debits a single account and credits multiple accounts. It uses the following accounts:

    • A source account A, on the USD ledger.
    • Three destination accounts X, Y, and Z, on the USD ledger.
    LedgerDebit AccountCredit AccountAmountflags.linked
    USDAX10000true
    USDAY50true
    USDAZ10false

    Multiple Debits, Single Credit

    This example debits multiple accounts and credits a single account. It uses the following accounts:

    • Three source accounts A, B, and C on the USD ledger.
    • A destination account X on the USD ledger.
    LedgerDebit AccountCredit AccountAmountflags.linked
    USDAX10000true
    USDBX50true
    USDCX10false

    Multiple Debits, Single Credit, Balancing debits

    This example debits multiple accounts and credits a single account. +The total amount to transfer to the credit account is known (in this case, 100), but the balances +of the individual debit accounts are not known. That is, each debit account should contribute as +much as possible (in order of precedence) up to the target, cumulative transfer amount.

    It uses the following accounts:

    IdLedgerDebit AccountCredit AccountAmountFlags
    1USDSETUPLIMIT100linked
    2USDASETUP100linked, balancing_debit, balancing_credit
    3USDBSETUP100linked, balancing_debit, balancing_credit
    4USDCSETUP100linked, balancing_debit, balancing_credit
    5USDSETUPX100linked
    6USDLIMITSETUP100balancing_credit

    If the cumulative credit balance of A + B + C is less than +100, the chain will fail (transfer 6 will return exceeds_credits).

    Many-to-Many Transfers

    Transactions with multiple debits and multiple credits are a bit more involved (but you got this!).

    This is where the accounting concept of a Control Account comes in handy. We can use this as an intermediary account, as illustrated below.

    In this example, we'll use the following accounts:

    • Two source accounts A and B on the USD ledger.
    • Three destination accounts X, Y, and Z, on the USD ledger.
    • A compound entry control account Control on the USD ledger.
    LedgerDebit AccountCredit AccountAmountflags.linked
    USDAControl10000true
    USDBControl50true
    USDControlX9000true
    USDControlY1000true
    USDControlZ50false

    Here, we use two transfers to debit accounts A and B and credit the Control account, and another three transfers to credit accounts X, Y, and Z.

    If you looked closely at this example, you may have noticed that we could have debited B and credited Z directly because the amounts happened to line up. That is true!

    For a little more extreme performance, you might consider implementing logic to circumvent the control account where possible, to reduce the number of transfers to implement a compound journal entry.

    However, if you're just getting started, you can avoid premature optimizations (we've all been there!). You may find it easier to program these compound journal entries always using a control -account -- and you can then come back to squeeze this performance out later!

    - +account -- and you can then come back to squeeze this performance out later!

    + \ No newline at end of file diff --git a/coding/recipes/rate-limiting/index.html b/coding/recipes/rate-limiting/index.html index 5bd14081..45d3644f 100644 --- a/coding/recipes/rate-limiting/index.html +++ b/coding/recipes/rate-limiting/index.html @@ -6,7 +6,7 @@ Rate Limiting | TigerBeetle Docs - + @@ -31,7 +31,7 @@ money per time window. We can do that using 2 ledgers and linked transfers.

    LedgerAccountFlags
    Rate LimitingOperator0
    Rate LimitingUserdebits_must_not_exceed_credits
    USDOperator0
    USDUserdebits_must_not_exceed_credits

    Let's say we wanted to limit each account to sending no more than 1000 USD per day.

    To set up, we transfer 1000 from the Operator to the User on the Rate Limiting ledger:

    TransferLedgerDebit AccountCredit AccountAmount
    1Rate LimitingOperatorUser1000

    For each transfer the user wants to do, we will create 2 transfers that are linked:

    TransferLedgerDebit AccountCredit AccountAmountTimeoutFlags (Note \| sets multiple flags)
    2NRate LimitingUserOperatorTransfer Amount86400pending | linked
    2N + 1USDUserDestinationTransfer Amount00

    Note that we are using a timeout of 86400 seconds, because this is the number of seconds in a day.

    These are linked such that if the first transfer fails, because the user has already transferred too much money in the past day, the second transfer will also fail.

    - + \ No newline at end of file diff --git a/coding/reliable-transaction-submission/index.html b/coding/reliable-transaction-submission/index.html index 88b5f43d..b1b90563 100644 --- a/coding/reliable-transaction-submission/index.html +++ b/coding/reliable-transaction-submission/index.html @@ -6,7 +6,7 @@ Reliable Transaction Submission | TigerBeetle Docs - + @@ -30,7 +30,7 @@ TigerBeetle will respond with the ok if a transfer is newly created and exists if an object with the same id was already created.

    - + \ No newline at end of file diff --git a/coding/system-architecture/index.html b/coding/system-architecture/index.html index e31005ac..599685e8 100644 --- a/coding/system-architecture/index.html +++ b/coding/system-architecture/index.html @@ -6,7 +6,7 @@ TigerBeetle in Your System Architecture | TigerBeetle Docs - + @@ -26,7 +26,7 @@ purpose database. If it does, that database will become the bottleneck and will negate the performance gains from using TigerBeetle.

    Specifically, the types of information that fit into this category include:

    Hard-coded in app or cachedIn TigerBeetle
    Currency or asset code's string representation (for example, "USD")ledger and asset scale
    Account type's string representation (for example, "cash")code
    Transfer type's string representation (for example, "refund")code

    Authentication

    TigerBeetle does not support authentication. You should never allow untrusted users or services to interact with it directly.

    Also, untrusted processes must not be able to access or modify TigerBeetle's on-disk data file.

    - + \ No newline at end of file diff --git a/coding/time/index.html b/coding/time/index.html index ab3558ea..f3e4754f 100644 --- a/coding/time/index.html +++ b/coding/time/index.html @@ -6,7 +6,7 @@ Time | TigerBeetle Docs - + @@ -41,7 +41,7 @@ on YouTube for more details.

    You can also read the blog post Three Clocks are Better than One for more on how nodes determine their own time and clock skew.

    - + \ No newline at end of file diff --git a/coding/two-phase-transfers/index.html b/coding/two-phase-transfers/index.html index e48bc3fb..52538159 100644 --- a/coding/two-phase-transfers/index.html +++ b/coding/two-phase-transfers/index.html @@ -6,7 +6,7 @@ Two-Phase Transfers | TigerBeetle Docs - + @@ -57,7 +57,7 @@ id of the first transfer. The id of the second transfer will be unique, not the same id as the initial pending transfer.

    Examples

    The following examples show the state of two accounts in three steps:

    1. Initially, before any transfers
    2. After a pending transfer
    3. And after the pending transfer is posted or voided

    Post Full Pending Amount

    Account AAccount BTransfers
    debitscredits
    pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
    wxyz----
    123 + wx123 + yzAB123pending
    w123 + xy123 + zAB123post_pending_transfer

    Post Partial Pending Amount

    Account AAccount BTransfers
    debitscredits
    pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
    wxyz----
    123 + wx123 + yzAB123pending
    w100 + xy100 + zAB100post_pending_transfer

    Void Pending Transfer

    Account AAccount BTransfers
    debitscredits
    pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
    wxyz----
    123 + wx123 + yzAB123pending
    wxyzAB123void_pending_transfer

    Client Documentation

    Read more about how two-phase transfers work with each client.

    Client Samples

    Or take a look at how it works with real code.

    - + \ No newline at end of file diff --git a/index.html b/index.html index b2c27b93..063960dd 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ TigerBeetle Docs | TigerBeetle Docs - + @@ -17,7 +17,7 @@ architecture, approach to safety and performance in the About section.

    Contributing

    Community

    - + \ No newline at end of file diff --git a/operating/deploy/index.html b/operating/deploy/index.html index e77d16fa..a9c57ba8 100644 --- a/operating/deploy/index.html +++ b/operating/deploy/index.html @@ -6,7 +6,7 @@ Deploying for Production | TigerBeetle Docs - + @@ -30,7 +30,7 @@ replicated across sites before being committed.

    Hardware Fault Tolerance

    It is important to ensure independent fault domains for each replica's data file, that each replica's data file is stored on a separate disk (required), machine (required), rack (recommended), data center (recommended) etc.

    - + \ No newline at end of file diff --git a/operating/docker/index.html b/operating/docker/index.html index 18b788eb..311060df 100644 --- a/operating/docker/index.html +++ b/operating/docker/index.html @@ -6,7 +6,7 @@ Docker | TigerBeetle Docs - + @@ -26,7 +26,7 @@ you will need to do one of the following:

    1. Run docker run with --cap-add IPC_LOCK
    2. Run docker run with --ulimit memlock=-1:-1
    3. Or modify the defaults in $HOME/.docker/daemon.json and restart the Docker for Mac application:
    {
    ... other settings ...
    "default-ulimits": {
    "memlock": {
    "Hard": -1,
    "Name": "memlock",
    "Soft": -1
    }
    },
    ... other settings ...
    }

    If you are running TigerBeetle with Docker Compose, you will need to add the IPC_LOCK capability like this:

    ... rest of docker-compose.yml ...

    services:
    tigerbeetle_0:
    image: ghcr.io/tigerbeetle/tigerbeetle
    command: "start --addresses=0.0.0.0:3001,0.0.0.0:3002,0.0.0.0:3003 /data/0_0.tigerbeetle"
    network_mode: host
    cap_add: # HERE
    - IPC_LOCK # HERE
    volumes:
    - ./data:/data

    ... rest of docker-compose.yml ...

    See https://github.com/tigerbeetle/tigerbeetle/issues/92 for discussion.

    - + \ No newline at end of file diff --git a/operating/hardware/index.html b/operating/hardware/index.html index 9987a889..fc253876 100644 --- a/operating/hardware/index.html +++ b/operating/hardware/index.html @@ -6,7 +6,7 @@ Hardware | TigerBeetle Docs - + @@ -24,7 +24,7 @@ allocation and will use exactly how much memory is explicitly allocated to it for caching via command line argument.

    CPU

    TigerBeetle requires only a single core per replica machine. TigerBeetle at present does not utilize more cores, but may in future.

    Multitenancy

    There are no restrictions on sharing a server with other tenant processes.

    - + \ No newline at end of file diff --git a/operating/linux/index.html b/operating/linux/index.html index d1afe489..83e88f94 100644 --- a/operating/linux/index.html +++ b/operating/linux/index.html @@ -6,7 +6,7 @@ Deploying on Linux | TigerBeetle Docs - + @@ -17,7 +17,7 @@ single-node cluster, so you may need to adjust it for other cluster configurations.

    See the Quick Start for an example of how to run a single- vs multi-node cluster.

    - + \ No newline at end of file diff --git a/operating/managed-service/index.html b/operating/managed-service/index.html index 71d90fe8..4b5f17d7 100644 --- a/operating/managed-service/index.html +++ b/operating/managed-service/index.html @@ -6,7 +6,7 @@ Managed Service | TigerBeetle Docs - + @@ -14,7 +14,7 @@

    Managed Service

    Want to use TigerBeetle in production, along with automated disaster recovery, monitoring, and dedicated support from the TigerBeetle team?

    Let us help you get up and running faster! Contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com to set up a call.

    - + \ No newline at end of file diff --git a/operating/upgrading/index.html b/operating/upgrading/index.html index 84855526..05351dee 100644 --- a/operating/upgrading/index.html +++ b/operating/upgrading/index.html @@ -6,7 +6,7 @@ Upgrading | TigerBeetle Docs - + @@ -38,7 +38,7 @@ to upgrade, with two options:

    • Upgrade the replicas to the latest version. In this case, the clients will stop working for the duration of the upgrade and unavailability will be extended.
    • Upgrade the replicas to the latest release that supports the client version in use, then upgrade the clients to that version. Repeat this until you're on the latest release.
    - + \ No newline at end of file diff --git a/quick-start/index.html b/quick-start/index.html index 120da2eb..59d7b5a4 100644 --- a/quick-start/index.html +++ b/quick-start/index.html @@ -6,7 +6,7 @@ Quick Start | TigerBeetle Docs - + @@ -22,7 +22,7 @@ provided.

    You can connect to the REPL as described above try creating accounts and transfers in this cluster.

    You can also read more about deploying TigerBeetle in production.

    Next: Designing for TigerBeetle

    Now that you've created some accounts and transfers, you may want to read about how TigerBeetle fits into your system architecture and dig into the data model.

    - + \ No newline at end of file diff --git a/reference/account-balance/index.html b/reference/account-balance/index.html index f019e8e5..84990f70 100644 --- a/reference/account-balance/index.html +++ b/reference/account-balance/index.html @@ -6,7 +6,7 @@ AccountBalance | TigerBeetle Docs - + @@ -15,7 +15,7 @@ time.

    Only Accounts with the flag history set retain historical balances.

    Fields

    timestamp

    This is the time the account balance was updated, as nanoseconds since UNIX epoch.

    The timestamp refers to the same Transfer.timestamp which changed the Account.

    The amounts refer to the account balance recorded after the transfer execution.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    debits_pending

    Amount of pending debits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    debits_posted

    Amount of posted debits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    credits_pending

    Amount of pending credits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    credits_posted

    Amount of posted credits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 56 bytes
    • Must be zero
    - + \ No newline at end of file diff --git a/reference/account-filter/index.html b/reference/account-filter/index.html index 0a2e80fd..fe73dab7 100644 --- a/reference/account-filter/index.html +++ b/reference/account-filter/index.html @@ -6,7 +6,7 @@ AccountFilter | TigerBeetle Docs - + @@ -20,7 +20,7 @@ matches the parameter account_id.

    flags.reversed

    Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the flag is not set, the event that happened first (has the smallest timestamp) will come first. If the flag is set, the event that happened last (has the largest timestamp) will come first.

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 24 bytes
    • Must be zero
    - + \ No newline at end of file diff --git a/reference/account/index.html b/reference/account/index.html index 4b30c890..e4f79df3 100644 --- a/reference/account/index.html +++ b/reference/account/index.html @@ -6,7 +6,7 @@ Account | TigerBeetle Docs - + @@ -63,7 +63,7 @@ Search for const Account = extern struct {.

    You can find the source code for creating an account in src/state_machine.zig. Search for fn create_account(.

    - + \ No newline at end of file diff --git a/reference/query-filter/index.html b/reference/query-filter/index.html index df1b9ca2..3f1774ab 100644 --- a/reference/query-filter/index.html +++ b/reference/query-filter/index.html @@ -6,23 +6,23 @@ QueryFilter | TigerBeetle Docs - +

    QueryFilter

    A QueryFilter is a record containing the filter parameters for querying accounts -and querying transfers.

    Fields

    user_data_128

    Filter the results by the field Account.user_data_128 or -Transfer.user_data_128. -Optional; set to zero to disable the filter.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    user_data_64

    Filter the results by the field Account.user_data_64 or -Transfer.user_data_64. -Optional; set to zero to disable the filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    user_data_32

    Filter the results by the field Account.user_data_32 or -Transfer.user_data_32. -Optional; set to zero to disable the filter.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    ledger

    Filter the results by the field Account.ledger or -Transfer.ledger. -Optional; set to zero to disable the filter.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    code

    Filter the results by the field Account.code or -Transfer.code. +and querying transfers.

    Fields

    user_data_128

    Filter the results by the field Account.user_data_128 or +Transfer.user_data_128. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    user_data_64

    Filter the results by the field Account.user_data_64 or +Transfer.user_data_64. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    user_data_32

    Filter the results by the field Account.user_data_32 or +Transfer.user_data_32. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    ledger

    Filter the results by the field Account.ledger or +Transfer.ledger. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    code

    Filter the results by the field Account.code or +Transfer.code. Optional; set to zero to disable the filter.

    Constraints:

    • Type is 16-bit unsigned integer (2 bytes)

    timestamp_min

    The minimum Account.timestamp or Transfer.timestamp from which results will be returned, inclusive range. @@ -32,7 +32,7 @@ Optional; set to zero to disable the upper-bound filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)
    • Must not be 2^64 - 1

    limit

    The maximum number of results that can be returned by this query.

    Limited by the maximum message size.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)
    • Must not be zero

    flags

    A bitfield that specifies querying behavior.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    flags.reversed

    Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the flag is not set, the event that happened first (has the smallest timestamp) will come first. If the flag is set, the event that happened last (has the largest timestamp) will come first.

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 6 bytes
    • Must be zero
    - + \ No newline at end of file diff --git a/reference/requests/create_accounts/index.html b/reference/requests/create_accounts/index.html index e678d5a7..068ed5ee 100644 --- a/reference/requests/create_accounts/index.html +++ b/reference/requests/create_accounts/index.html @@ -6,7 +6,7 @@ create_accounts | TigerBeetle Docs - + @@ -20,9 +20,9 @@ set on the first account of the batch, but not all accounts in the batch. Batches cannot mix imported accounts with non-imported accounts.

    imported_event_not_expected

    The account was not created. The Account.flags.imported was expected to not be set, as it's not allowed to mix accounts with different imported flag -in the same batch. The first account determines the entire operation.

    timestamp_must_be_zero

    This result only applies when Account.flags.imported is not set.

    The account was not created. The Account.timestamp is nonzero, but +in the same batch. The first account determines the entire operation.

    timestamp_must_be_zero

    This result only applies when Account.flags.imported is not set.

    The account was not created. The Account.timestamp is nonzero, but must be zero. The cluster is responsible for setting this field.

    The Account.timestamp can only be assigned when creating accounts -with Account.flags.imported set.

    imported_event_timestamp_out_of_range

    This result only applies when Account.flags.imported is set.

    The account was not created. The Account.timestamp is out of range, +with Account.flags.imported set.

    imported_event_timestamp_out_of_range

    This result only applies when Account.flags.imported is set.

    The account was not created. The Account.timestamp is out of range, but must be a user-defined timestamp greater than 0 and less than 2^63.

    imported_event_timestamp_must_not_advance

    This result only applies when Account.flags.imported is set.

    The account was not created. The user-defined Account.timestamp is greater than the current cluster time, but it must be a past timestamp.

    reserved_field

    The account was not created. Account.reserved is nonzero, but must be zero.

    reserved_flag

    The account was not created. Account.flags.reserved is nonzero, but must be zero.

    id_must_not_be_zero

    The account was not created. Account.id is zero, which is a reserved value.

    id_must_not_be_int_max

    The account was not created. Account.id is 2^128 - 1, which is a reserved @@ -36,11 +36,11 @@ user_data_64.

    exists_with_different_user_data_32

    An account with the same id already exists, but with different user_data_32.

    exists_with_different_ledger

    An account with the same id already exists, but with different ledger.

    exists_with_different_code

    An account with the same id already exists, but with different code.

    exists

    An account with the same id already exists.

    With the possible exception of the following fields, the existing account is identical to the account in the request:

    • timestamp
    • debits_pending
    • debits_posted
    • credits_pending
    • credits_posted

    To correctly recover from application crashes, -many applications should handle exists exactly as ok.

    imported_event_timestamp_must_not_regress

    This result only applies when Account.flags.imported is set.

    The account was not created. The user-defined Account.timestamp +many applications should handle exists exactly as ok.

    imported_event_timestamp_must_not_regress

    This result only applies when Account.flags.imported is set.

    The account was not created. The user-defined Account.timestamp regressed, but it must be greater than the last timestamp assigned to any Account in the cluster and cannot be equal to the timestamp of any existing Transfer.

    Client libraries

    For language-specific docs see:

    Internals

    If you're curious and want to learn more, you can find the source code for creating an account in src/state_machine.zig. Search for fn create_account( and fn execute(.

    - + \ No newline at end of file diff --git a/reference/requests/create_transfers/index.html b/reference/requests/create_transfers/index.html index 47f96d69..26dd2d40 100644 --- a/reference/requests/create_transfers/index.html +++ b/reference/requests/create_transfers/index.html @@ -6,7 +6,7 @@ create_transfers | TigerBeetle Docs - + @@ -22,9 +22,9 @@ set on the first transfer of the batch, but not all transfers in the batch. Batches cannot mix imported transfers with non-imported transfers.

    imported_event_not_expected

    The transfer was not created. The Transfer.flags.imported was expected to not be set, as it's not allowed to mix transfers with different imported flag -in the same batch. The first transfer determines the entire operation.

    timestamp_must_be_zero

    This result only applies when Account.flags.imported is not set.

    The transfer was not created. The Transfer.timestamp is nonzero, but +in the same batch. The first transfer determines the entire operation.

    timestamp_must_be_zero

    This result only applies when Account.flags.imported is not set.

    The transfer was not created. The Transfer.timestamp is nonzero, but must be zero. The cluster is responsible for setting this field.

    The Transfer.timestamp can only be assigned when creating transfers -with Transfer.flags.imported set.

    imported_event_timestamp_out_of_range

    This result only applies when Transfer.flags.imported is set.

    The transfer was not created. The Transfer.timestamp is out of range, +with Transfer.flags.imported set.

    imported_event_timestamp_out_of_range

    This result only applies when Transfer.flags.imported is set.

    The transfer was not created. The Transfer.timestamp is out of range, but must be a user-defined timestamp greater than 0 and less than 2^63.

    imported_event_timestamp_must_not_advance

    This result only applies when Transfer.flags.imported is set.

    The transfer was not created. The user-defined Transfer.timestamp is greater than the current cluster time, but it must be a past timestamp.

    reserved_flag

    The transfer was not created. Transfer.flags.reserved is nonzero, but must be zero.

    id_must_not_be_zero

    The transfer was not created. Transfer.id is zero, which is a reserved value.

    id_must_not_be_int_max

    The transfer was not created. Transfer.id is 2^128 - 1, which is a reserved value.

    flags_are_mutually_exclusive

    The transfer was not created. An account cannot be created with the specified combination of @@ -140,7 +140,7 @@ credit_account.debits_posted.

    Client libraries

    For language-specific docs see:

    Internals

    If you're curious and want to learn more, you can find the source code for creating a transfer in src/state_machine.zig. Search for fn create_transfer( and fn execute(.

    - + \ No newline at end of file diff --git a/reference/requests/get_account_balances/index.html b/reference/requests/get_account_balances/index.html index 690bf94f..dd475f58 100644 --- a/reference/requests/get_account_balances/index.html +++ b/reference/requests/get_account_balances/index.html @@ -6,7 +6,7 @@ get_account_balances | TigerBeetle Docs - + @@ -19,7 +19,7 @@ See AccountFilter for constraints.

    Result

    • If the account has the flag history set and any matching balances exist, return an array of AccountBalances.
    • If the account does not have the flag history set, return nothing.
    • If no matching balances exist, return nothing.
    • If any constraint is violated, return nothing.

    Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/requests/get_account_transfers/index.html b/reference/requests/get_account_transfers/index.html index 998f8861..c4aee3aa 100644 --- a/reference/requests/get_account_transfers/index.html +++ b/reference/requests/get_account_transfers/index.html @@ -6,7 +6,7 @@ get_account_transfers | TigerBeetle Docs - + @@ -16,7 +16,7 @@ reversed to change this.
  • The result is always limited in size. If there are more results, you need to page through them using the AccountFilter's timestamp_min and/or timestamp_max.
  • Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/requests/index.html b/reference/requests/index.html index f249abf6..9967f609 100644 --- a/reference/requests/index.html +++ b/reference/requests/index.html @@ -6,7 +6,7 @@ Requests | TigerBeetle Docs - + @@ -49,7 +49,7 @@ timestamps are unique and strictly increasing. No two objects within the same cluster will have the same timestamp. Furthermore, the order of the timestamps indicates the order in which the objects were committed. - + \ No newline at end of file diff --git a/reference/requests/lookup_accounts/index.html b/reference/requests/lookup_accounts/index.html index 4de22437..e70130f8 100644 --- a/reference/requests/lookup_accounts/index.html +++ b/reference/requests/lookup_accounts/index.html @@ -6,7 +6,7 @@ lookup_accounts | TigerBeetle Docs - + @@ -20,7 +20,7 @@ balance-conditional transfers.

    Event

    An id belonging to a Account.

    Result

    • If the account exists, return the Account.
    • If the account does not exist, return nothing.

    Client libraries

    For language-specific docs see:

    Internals

    If you're curious and want to learn more, you can find the source code for looking up an account in src/state_machine.zig. Search for fn execute_lookup_accounts(.

    - + \ No newline at end of file diff --git a/reference/requests/lookup_transfers/index.html b/reference/requests/lookup_transfers/index.html index 3bdc22be..fba12c08 100644 --- a/reference/requests/lookup_transfers/index.html +++ b/reference/requests/lookup_transfers/index.html @@ -6,7 +6,7 @@ lookup_transfers | TigerBeetle Docs - + @@ -15,7 +15,7 @@ for looking up a transfer in src/state_machine.zig. Search for fn execute_lookup_transfers(.

    - + \ No newline at end of file diff --git a/reference/requests/query_accounts/index.html b/reference/requests/query_accounts/index.html index 5a93100b..ff3e3cfd 100644 --- a/reference/requests/query_accounts/index.html +++ b/reference/requests/query_accounts/index.html @@ -6,7 +6,7 @@ query_accounts | TigerBeetle Docs - + @@ -16,7 +16,7 @@ reversed to change this.
  • The result is always limited in size. If there are more results, you need to page through them using the QueryFilter's timestamp_min and/or timestamp_max.
  • Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/requests/query_transfers/index.html b/reference/requests/query_transfers/index.html index a333cfc4..7428e0f8 100644 --- a/reference/requests/query_transfers/index.html +++ b/reference/requests/query_transfers/index.html @@ -6,7 +6,7 @@ query_transfers | TigerBeetle Docs - + @@ -16,7 +16,7 @@ reversed to change this.
  • The result is always limited in size. If there are more results, you need to page through them using the QueryFilter's timestamp_min and/or timestamp_max.
  • Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/sessions/index.html b/reference/sessions/index.html index 49eca0b5..cacbea20 100644 --- a/reference/sessions/index.html +++ b/reference/sessions/index.html @@ -6,7 +6,7 @@ Client Sessions | TigerBeetle Docs - + @@ -44,7 +44,7 @@ updates for which the corresponding reply was not received prior to the restart. Those updates may occur at any point in the future, or never. Handling application crash recovery safely requires using ids to idempotently retry events. - + \ No newline at end of file diff --git a/reference/transfer/index.html b/reference/transfer/index.html index bdd9b237..eb89232d 100644 --- a/reference/transfer/index.html +++ b/reference/transfer/index.html @@ -6,7 +6,7 @@ Transfer | TigerBeetle Docs - + @@ -48,13 +48,22 @@ transfer. If this is not a post or void transfer, it must be zero.

    See the section on Two-Phase Transfers for more information on how the pending_id is used.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)
    • Must be zero if neither void nor pending transfer flag is set
    • Must match an existing transfer's id if non-zero

    user_data_128

    This is an optional 128-bit secondary identifier to link this transfer to an external entity or event.

    When set to zero, no secondary identifier will be associated with the account, therefore only -non-zero values can be used as query filter.

    As an example, you might generate a +non-zero values can be used as query filter.

    When set to zero, if +flags.post_pending_transfer or +flags.void_pending_transfer is set, then +it will be automatically set to the pending transfer's user_data_128.

    As an example, you might generate a TigerBeetle Time-Based Identifier that ties together a group of transfers.

    For more information, see Data Modeling.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    user_data_64

    This is an optional 64-bit secondary identifier to link this transfer to an external entity or event.

    When set to zero, no secondary identifier will be associated with the account, therefore only -non-zero values can be used as query filter.

    As an example, you might use this field store an external timestamp.

    For more information, see Data Modeling.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    user_data_32

    This is an optional 32-bit secondary identifier to link this transfer to an external entity or +non-zero values can be used as query filter.

    When set to zero, if +flags.post_pending_transfer or +flags.void_pending_transfer is set, then +it will be automatically set to the pending transfer's user_data_64.

    As an example, you might use this field store an external timestamp.

    For more information, see Data Modeling.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    user_data_32

    This is an optional 32-bit secondary identifier to link this transfer to an external entity or event.

    When set to zero, no secondary identifier will be associated with the account, therefore only -non-zero values can be used as query filter.

    As an example, you might use this field to store a timezone or locale.

    For more information, see Data Modeling.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    timeout

    This is the interval in seconds after a pending transfer's +non-zero values can be used as query filter.

    When set to zero, if +flags.post_pending_transfer or +flags.void_pending_transfer is set, then +it will be automatically set to the pending transfer's user_data_32.

    As an example, you might use this field to store a timezone or locale.

    For more information, see Data Modeling.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    timeout

    This is the interval in seconds after a pending transfer's arrival at the cluster that it may be posted or voided. Zero denotes absence of timeout.

    Non-pending transfers cannot have a timeout.

    Imported transfers cannot have a timeout.

    TigerBeetle makes a best-effort approach to remove pending balances of expired transfers automatically: