diff --git a/404.html b/404.html index 5b1ed50..998ca24 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 5ce2d4c..f6e1edd 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 8575f77..9ff105c 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 2c8f95b..b175af9 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 de8ba72..9c2dee5 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 b89af13..c73ce62 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 0a250da..eb71695 100644 --- a/about/internals/lsm/index.html +++ b/about/internals/lsm/index.html @@ -6,7 +6,7 @@ LSM | TigerBeetle Docs - + @@ -49,7 +49,7 @@ 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:

- + \ No newline at end of file diff --git a/about/internals/releases/index.html b/about/internals/releases/index.html index e20c190..2f28190 100644 --- a/about/internals/releases/index.html +++ b/about/internals/releases/index.html @@ -6,7 +6,7 @@ Releases | TigerBeetle Docs - + @@ -34,7 +34,7 @@ skipped if there are not enough people to make a release (at least two are required) or if there aren't any changes in a given week.

Changelog Guidelines

Purposes of the changelog:

As such:

- + \ No newline at end of file diff --git a/about/internals/sync/index.html b/about/internals/sync/index.html index b38b25b..b0c9b6b 100644 --- a/about/internals/sync/index.html +++ b/about/internals/sync/index.html @@ -6,7 +6,7 @@ State Sync | TigerBeetle Docs - + @@ -42,7 +42,7 @@ 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 mismatches) the replica which detects the mismatch will panic. This scenario should prompt operator 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 f6c3570..d2fe060 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 start (or change target)
      • >: 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?
    4 < ~ v 3V 23/_23/_46C 19:_50Jo 0/_0J! 19:_50Wo <__0:__0> v1:2 66Ga 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 b675588..4f9f770 100644 --- a/about/internals/upgrades/index.html +++ b/about/internals/upgrades/index.html @@ -6,7 +6,7 @@ Upgrades | TigerBeetle Docs - + @@ -34,13 +34,13 @@ differ (besides atime) it'll re-read the binary into memory, verify checksums and metadata, and start advertising new versions without requiring a restart.

    This optimization allows skipping a potentially expensive WAL replay when upgrading: the previous version is what will checkpoint to the new version, at which point the exec happens.

    Executing

    The final step is executing into the new version of TigerBeetle. On Linux, this is handled by -execveat which allows executing from a memfd. If executing the latest release, exec_latest +execveat which allows executing from a memfd. If executing the latest release, exec_current re-execs the memfd as-is. If executing an older release, exec_release copies it out of the pack, verifies its checksum, and then executes it.

    One key point is that the newest version is always what starts up and determines what version to 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 49d3ae3..14fa3a5 100644 --- a/about/internals/vsr/index.html +++ b/about/internals/vsr/index.html @@ -6,7 +6,7 @@ VSR | TigerBeetle Docs - + @@ -24,7 +24,7 @@ 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

    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 de92aab..a1fa841 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 b5764b7..f44b63d 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 c1e7e7b..36f4d06 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 684a996..815a9c3 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 1e217d0..40644a4 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 f7818fc..d0d6b4c 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/2b4a1ee0.3ac1dbb2.js b/assets/js/2b4a1ee0.032d72ca.js similarity index 87% rename from assets/js/2b4a1ee0.3ac1dbb2.js rename to assets/js/2b4a1ee0.032d72ca.js index 81946c6..64d7b7d 100644 --- a/assets/js/2b4a1ee0.3ac1dbb2.js +++ b/assets/js/2b4a1ee0.032d72ca.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7596],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var r=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 a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),d=p(n),m=i,h=d["".concat(s,".").concat(m)]||d[m]||c[m]||a;return n?r.createElement(h,o(o({ref:t},u),{},{components:n})):r.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[d]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var r=n(7462),i=(n(7294),n(3905));const a={sidebar_position:8},o="Upgrades",l={unversionedId:"about/internals/upgrades",id:"about/internals/upgrades",title:"Upgrades",description:"Upgrades in TigerBeetle are handled by bundling multiple underlying TigerBeetle binaries of",source:"@site/pages/about/internals/upgrades.md",sourceDirName:"about/internals",slug:"/about/internals/upgrades",permalink:"/about/internals/upgrades",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/upgrades.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{sidebar_position:8},sidebar:"tutorialSidebar",previous:{title:"Cloud",permalink:"/about/internals/cloud"}},s={},p=[{value:"Building",id:"building",level:2},{value:"Bootstrapping",id:"bootstrapping",level:3},{value:"Monitoring",id:"monitoring",level:2},{value:"Executing",id:"executing",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(d,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"upgrades"},"Upgrades"),(0,i.kt)("p",null,'Upgrades in TigerBeetle are handled by bundling multiple underlying TigerBeetle binaries of\ndifferent versions, into a single binary, known as "Multiversion Binaries".'),(0,i.kt)("p",null,"The idea behind multiversion binaries is to give operators a great experience when upgrading\nTigerBeetle clusters:"),(0,i.kt)("p",null,"Upgrades should be simple, involve minimal downtime and be robust, while not requiring external\ncoordination."),(0,i.kt)("p",null,"Multiple versions in a single binary are required for two reasons:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"It allows a replica to crash after the binary has been upgraded, and still come back online.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"It also allows for deployments, like Docker, where the binary is immutable and the process\nhas to be terminated to learn about new versions from itself."))),(0,i.kt)("li",{parentName:"ul"},"It allows for migrations over a range to happen easily without having to manually jump from\nversion to version.")),(0,i.kt)("p",null,"The upgrade instructions look something like:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"# SSH to each replica, in no particular order:\ncd /tmp\nwget https://github.com/tigerbeetle/tigerbeetle/releases/download/0.15.4/tigerbeetle-x86_64-linux.zip\nunzip tigerbeetle-x86_64-linux.zip\n\n# Put the binary on the same file system as the target, so mv is atomic.\nmv tigerbeetle /usr/bin/tigerbeetle-new\n\nmv /usr/bin/tigerbeetle /usr/bin/tigerbeetle-old\nmv /usr/bin/tigerbeetle-new /usr/bin/tigerbeetle\n")),(0,i.kt)("p",null,"When the primary determines that enough",(0,i.kt)("sup",{parentName:"p",id:"fnref-1"},(0,i.kt)("a",{parentName:"sup",href:"#fn-1",className:"footnote-ref"},"1"))," replicas have the new binary, it'll ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/pull/1670"},"coordinate the\nupgrade"),"."),(0,i.kt)("p",null,"There are three main parts to multiversion binaries: building, monitoring and executing, with\nplatform specific parts in each."),(0,i.kt)("h2",{id:"building"},"Building"),(0,i.kt)("p",null,"Physically, multiversion binaries are regular TigerBeetle ELF / PE / MachO",(0,i.kt)("sup",{parentName:"p",id:"fnref-2"},(0,i.kt)("a",{parentName:"sup",href:"#fn-2",className:"footnote-ref"},"2"))," files that have two\nextra sections",(0,i.kt)("sup",{parentName:"p",id:"fnref-3"},(0,i.kt)("a",{parentName:"sup",href:"#fn-3",className:"footnote-ref"},"3"))," embedded into them - marked as ",(0,i.kt)("inlineCode",{parentName:"p"},"noload")," so that they're not memory mapped:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},".tb_mvh")," or TigerBeetleMultiVersionHeader - a header struct containing information on past\nversions embedded as well as offsets, sizes, checksums and the like."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},".tb_mvb")," or TigerBeetleMultiVersionBody - a concatenated pack of binaries. The offsets in\n",(0,i.kt)("inlineCode",{parentName:"li"},".tb_mvh")," refer into here.")),(0,i.kt)("p",null,"These are added by an explicit objcopy step in the release process, ",(0,i.kt)("em",{parentName:"p"},"after")," the regular build is\ndone. After the epoch, the build process only needs to pull the last TigerBeetle release from\nGitHub, to access its embedded pack to build its own."),(0,i.kt)("h3",{id:"bootstrapping"},"Bootstrapping"),(0,i.kt)("p",null,"0.15.3 is considered the epoch release, but it doesn't know about any future versions of\nTigerBeetle or how to read the metadata yet. This means that if the build process pulled in that\nexact release, when running on a 0.15.3 data file, 0.15.3 would be executed and nothing further\nwould happen. There is a ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/pull/1935"},"special backport\nrelease"),", that embeds the fact that 0.15.4 is\navailable to solve this problem. The release code for 0.15.4 builds this version for 0.15.3,\ninstead of downloading it from GitHub."),(0,i.kt)("p",null,"Additionally, since 0.15.3 can't read its own binary (see Monitoring below), restarting the replica\nmanually after copying it in is needed."),(0,i.kt)("p",null,"Once 0.15.4 is running, no more special cases are needed."),(0,i.kt)("h2",{id:"monitoring"},"Monitoring"),(0,i.kt)("p",null,"On a 1 second timer, TigerBeetle ",(0,i.kt)("inlineCode",{parentName:"p"},"stat"),"s its binary file, looking for changes. Should anything\ndiffer (besides ",(0,i.kt)("inlineCode",{parentName:"p"},"atime"),") it'll re-read the binary into memory, verify checksums and metadata, and\nstart advertising new versions without requiring a restart."),(0,i.kt)("p",null,"This optimization allows skipping a potentially expensive WAL replay when upgrading: the previous\nversion is what will checkpoint to the new version, at which point the exec happens."),(0,i.kt)("h2",{id:"executing"},"Executing"),(0,i.kt)("p",null,"The final step is executing into the new version of TigerBeetle. On Linux, this is handled by\n",(0,i.kt)("inlineCode",{parentName:"p"},"execveat")," which allows executing from a ",(0,i.kt)("inlineCode",{parentName:"p"},"memfd"),". If executing the latest release, ",(0,i.kt)("inlineCode",{parentName:"p"},"exec_latest"),"\nre-execs the ",(0,i.kt)("inlineCode",{parentName:"p"},"memfd")," as-is. If executing an older release, ",(0,i.kt)("inlineCode",{parentName:"p"},"exec_release")," copies it out of the\npack, verifies its checksum, and then executes it."),(0,i.kt)("p",null,"One key point is that the newest version is always what starts up and determines what version to\nrun."),(0,i.kt)("div",{className:"footnotes"},(0,i.kt)("hr",{parentName:"div"}),(0,i.kt)("ol",{parentName:"div"},(0,i.kt)("li",{parentName:"ol",id:"fn-1"},"Currently the total number of replicas, less one.",(0,i.kt)("a",{parentName:"li",href:"#fnref-1",className:"footnote-backref"},"\u21a9")),(0,i.kt)("li",{parentName:"ol",id:"fn-2"},"MachO binaries are constructed as fat binaries, using unused, esoteric CPU identifiers to\nsignal the header and body, for both x86_64 and arm64.",(0,i.kt)("a",{parentName:"li",href:"#fnref-2",className:"footnote-backref"},"\u21a9")),(0,i.kt)("li",{parentName:"ol",id:"fn-3"},"The short names are for compatibility with Windows: PE supports up to 8 characters for\nsection names without getting more complicated.",(0,i.kt)("a",{parentName:"li",href:"#fnref-3",className:"footnote-backref"},"\u21a9")))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7596],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var r=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 a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),d=p(n),m=i,h=d["".concat(s,".").concat(m)]||d[m]||c[m]||a;return n?r.createElement(h,o(o({ref:t},u),{},{components:n})):r.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[d]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var r=n(7462),i=(n(7294),n(3905));const a={sidebar_position:8},o="Upgrades",l={unversionedId:"about/internals/upgrades",id:"about/internals/upgrades",title:"Upgrades",description:"Upgrades in TigerBeetle are handled by bundling multiple underlying TigerBeetle binaries of",source:"@site/pages/about/internals/upgrades.md",sourceDirName:"about/internals",slug:"/about/internals/upgrades",permalink:"/about/internals/upgrades",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/about/internals/upgrades.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{sidebar_position:8},sidebar:"tutorialSidebar",previous:{title:"Cloud",permalink:"/about/internals/cloud"}},s={},p=[{value:"Building",id:"building",level:2},{value:"Bootstrapping",id:"bootstrapping",level:3},{value:"Monitoring",id:"monitoring",level:2},{value:"Executing",id:"executing",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(d,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"upgrades"},"Upgrades"),(0,i.kt)("p",null,'Upgrades in TigerBeetle are handled by bundling multiple underlying TigerBeetle binaries of\ndifferent versions, into a single binary, known as "Multiversion Binaries".'),(0,i.kt)("p",null,"The idea behind multiversion binaries is to give operators a great experience when upgrading\nTigerBeetle clusters:"),(0,i.kt)("p",null,"Upgrades should be simple, involve minimal downtime and be robust, while not requiring external\ncoordination."),(0,i.kt)("p",null,"Multiple versions in a single binary are required for two reasons:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"It allows a replica to crash after the binary has been upgraded, and still come back online.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"It also allows for deployments, like Docker, where the binary is immutable and the process\nhas to be terminated to learn about new versions from itself."))),(0,i.kt)("li",{parentName:"ul"},"It allows for migrations over a range to happen easily without having to manually jump from\nversion to version.")),(0,i.kt)("p",null,"The upgrade instructions look something like:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"# SSH to each replica, in no particular order:\ncd /tmp\nwget https://github.com/tigerbeetle/tigerbeetle/releases/download/0.15.4/tigerbeetle-x86_64-linux.zip\nunzip tigerbeetle-x86_64-linux.zip\n\n# Put the binary on the same file system as the target, so mv is atomic.\nmv tigerbeetle /usr/bin/tigerbeetle-new\n\nmv /usr/bin/tigerbeetle /usr/bin/tigerbeetle-old\nmv /usr/bin/tigerbeetle-new /usr/bin/tigerbeetle\n")),(0,i.kt)("p",null,"When the primary determines that enough",(0,i.kt)("sup",{parentName:"p",id:"fnref-1"},(0,i.kt)("a",{parentName:"sup",href:"#fn-1",className:"footnote-ref"},"1"))," replicas have the new binary, it'll ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/pull/1670"},"coordinate the\nupgrade"),"."),(0,i.kt)("p",null,"There are three main parts to multiversion binaries: building, monitoring and executing, with\nplatform specific parts in each."),(0,i.kt)("h2",{id:"building"},"Building"),(0,i.kt)("p",null,"Physically, multiversion binaries are regular TigerBeetle ELF / PE / MachO",(0,i.kt)("sup",{parentName:"p",id:"fnref-2"},(0,i.kt)("a",{parentName:"sup",href:"#fn-2",className:"footnote-ref"},"2"))," files that have two\nextra sections",(0,i.kt)("sup",{parentName:"p",id:"fnref-3"},(0,i.kt)("a",{parentName:"sup",href:"#fn-3",className:"footnote-ref"},"3"))," embedded into them - marked as ",(0,i.kt)("inlineCode",{parentName:"p"},"noload")," so that they're not memory mapped:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},".tb_mvh")," or TigerBeetleMultiVersionHeader - a header struct containing information on past\nversions embedded as well as offsets, sizes, checksums and the like."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},".tb_mvb")," or TigerBeetleMultiVersionBody - a concatenated pack of binaries. The offsets in\n",(0,i.kt)("inlineCode",{parentName:"li"},".tb_mvh")," refer into here.")),(0,i.kt)("p",null,"These are added by an explicit objcopy step in the release process, ",(0,i.kt)("em",{parentName:"p"},"after")," the regular build is\ndone. After the epoch, the build process only needs to pull the last TigerBeetle release from\nGitHub, to access its embedded pack to build its own."),(0,i.kt)("h3",{id:"bootstrapping"},"Bootstrapping"),(0,i.kt)("p",null,"0.15.3 is considered the epoch release, but it doesn't know about any future versions of\nTigerBeetle or how to read the metadata yet. This means that if the build process pulled in that\nexact release, when running on a 0.15.3 data file, 0.15.3 would be executed and nothing further\nwould happen. There is a ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/pull/1935"},"special backport\nrelease"),", that embeds the fact that 0.15.4 is\navailable to solve this problem. The release code for 0.15.4 builds this version for 0.15.3,\ninstead of downloading it from GitHub."),(0,i.kt)("p",null,"Additionally, since 0.15.3 can't read its own binary (see Monitoring below), restarting the replica\nmanually after copying it in is needed."),(0,i.kt)("p",null,"Once 0.15.4 is running, no more special cases are needed."),(0,i.kt)("h2",{id:"monitoring"},"Monitoring"),(0,i.kt)("p",null,"On a 1 second timer, TigerBeetle ",(0,i.kt)("inlineCode",{parentName:"p"},"stat"),"s its binary file, looking for changes. Should anything\ndiffer (besides ",(0,i.kt)("inlineCode",{parentName:"p"},"atime"),") it'll re-read the binary into memory, verify checksums and metadata, and\nstart advertising new versions without requiring a restart."),(0,i.kt)("p",null,"This optimization allows skipping a potentially expensive WAL replay when upgrading: the previous\nversion is what will checkpoint to the new version, at which point the exec happens."),(0,i.kt)("h2",{id:"executing"},"Executing"),(0,i.kt)("p",null,"The final step is executing into the new version of TigerBeetle. On Linux, this is handled by\n",(0,i.kt)("inlineCode",{parentName:"p"},"execveat")," which allows executing from a ",(0,i.kt)("inlineCode",{parentName:"p"},"memfd"),". If executing the latest release, ",(0,i.kt)("inlineCode",{parentName:"p"},"exec_current"),"\nre-execs the ",(0,i.kt)("inlineCode",{parentName:"p"},"memfd")," as-is. If executing an older release, ",(0,i.kt)("inlineCode",{parentName:"p"},"exec_release")," copies it out of the\npack, verifies its checksum, and then executes it."),(0,i.kt)("p",null,"One key point is that the newest version is always what starts up and determines what version to\nrun."),(0,i.kt)("div",{className:"footnotes"},(0,i.kt)("hr",{parentName:"div"}),(0,i.kt)("ol",{parentName:"div"},(0,i.kt)("li",{parentName:"ol",id:"fn-1"},"Currently the total number of replicas, less one.",(0,i.kt)("a",{parentName:"li",href:"#fnref-1",className:"footnote-backref"},"\u21a9")),(0,i.kt)("li",{parentName:"ol",id:"fn-2"},"MachO binaries are constructed as fat binaries, using unused, esoteric CPU identifiers to\nsignal the header and body, for both x86_64 and arm64.",(0,i.kt)("a",{parentName:"li",href:"#fnref-2",className:"footnote-backref"},"\u21a9")),(0,i.kt)("li",{parentName:"ol",id:"fn-3"},"The short names are for compatibility with Windows: PE supports up to 8 characters for\nsection names without getting more complicated.",(0,i.kt)("a",{parentName:"li",href:"#fnref-3",className:"footnote-backref"},"\u21a9")))))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2fce5429.0b2bd283.js b/assets/js/2fce5429.0b2bd283.js new file mode 100644 index 0000000..8c74a04 --- /dev/null +++ b/assets/js/2fce5429.0b2bd283.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2738],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,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 l(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 l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=c(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)}},g=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,d=i(e,["components","mdxType","originalType","parentName"]),u=c(n),g=r,m=u["".concat(s,".").concat(g)]||u[g]||p[g]||l;return n?a.createElement(m,o(o({ref:t},d),{},{components:n})):a.createElement(m,o({ref:t},d))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,o=new Array(l);o[0]=g;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[u]="string"==typeof e?e:r,o[1]=i;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>p,frontMatter:()=>l,metadata:()=>i,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const l={sidebar_label:"Quick Start",sidebar_position:2},o="Quick Start",i={unversionedId:"quick-start",id:"quick-start",title:"Quick Start",description:"This page will guide you through downloading TigerBeetle, setting up a single- or multi-node",source:"@site/pages/quick-start.md",sourceDirName:".",slug:"/quick-start",permalink:"/quick-start",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/quick-start.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_label:"Quick Start",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"TigerBeetle Docs",permalink:"/"},next:{title:"Developing Applications on TigerBeetle",permalink:"/coding/"}},s={},c=[{value:"1. Download TigerBeetle",id:"1-download-tigerbeetle",level:2},{value:"Latest Release",id:"latest-release",level:3},{value:"Build from Source",id:"build-from-source",level:4},{value:"Direct Download",id:"direct-download",level:4},{value:"Docker",id:"docker",level:4},{value:"2. Create the Data File",id:"2-create-the-data-file",level:2},{value:"3. Start Your Cluster",id:"3-start-your-cluster",level:2},{value:"4. Connect to the REPL",id:"4-connect-to-the-repl",level:2},{value:"5. Create Accounts",id:"5-create-accounts",level:2},{value:"6. Create a Transfer",id:"6-create-a-transfer",level:2},{value:"7. Look Up Accounts",id:"7-look-up-accounts",level:2},{value:"Optional: Run a Multi-Node Cluster",id:"optional-run-a-multi-node-cluster",level:2},{value:"Next: Designing for TigerBeetle",id:"next-designing-for-tigerbeetle",level:2}],d={toc:c},u="wrapper";function p(e){let{components:t,...n}=e;return(0,r.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"quick-start"},"Quick Start"),(0,r.kt)("p",null,"This page will guide you through downloading TigerBeetle, setting up a single- or multi-node\ncluster, and creating some accounts and transfers using the REPL."),(0,r.kt)("h2",{id:"1-download-tigerbeetle"},"1. Download TigerBeetle"),(0,r.kt)("p",null,"TigerBeetle is a single, small, statically-linked binary."),(0,r.kt)("h3",{id:"latest-release"},"Latest Release"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"# macOS\ncurl -Lo tigerbeetle.zip https://mac.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"# Linux\ncurl -Lo tigerbeetle.zip https://linux.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},'# Windows\npowershell -command "curl.exe -Lo tigerbeetle.zip https://windows.tigerbeetle.com; Expand-Archive tigerbeetle.zip .; .\\tigerbeetle version"\n')),(0,r.kt)("h4",{id:"build-from-source"},"Build from Source"),(0,r.kt)("p",null,"To build TigerBeetle from source, clone the repo, install Zig and run ",(0,r.kt)("inlineCode",{parentName:"p"},"zig build"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"git clone https://github.com/tigerbeetle/tigerbeetle && cd tigerbeetle\n./zig/download.sh # .bat if you're on Windows.\n./zig/zig build\n./tigerbeetle version\n")),(0,r.kt)("p",null,"Notes:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Building from source is not recommended for production deployments."),(0,r.kt)("li",{parentName:"ul"},"If you build TigerBeetle from source, it is only compatible with clients that were also built from source.")),(0,r.kt)("h4",{id:"direct-download"},"Direct Download"),(0,r.kt)("p",null,"You can download prebuilt binaries here:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"left"}),(0,r.kt)("th",{parentName:"tr",align:"left"},"Linux"),(0,r.kt)("th",{parentName:"tr",align:"left"},"Windows"),(0,r.kt)("th",{parentName:"tr",align:"left"},"MacOS"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"left"},"x86_64"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-x86_64-linux.zip"},"tigerbeetle-x86_64-linux.zip")),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-x86_64-windows.zip"},"tigerbeetle-x86_64-windows.zip")),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-universal-macos.zip"},"tigerbeetle-universal-macos.zip"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"left"},"aarch64"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-aarch64-linux.zip"},"tigerbeetle-aarch64-linux.zip")),(0,r.kt)("td",{parentName:"tr",align:"left"},"N/A"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-universal-macos.zip"},"tigerbeetle-universal-macos.zip"))))),(0,r.kt)("h4",{id:"docker"},"Docker"),(0,r.kt)("p",null,"You can find instructions on using TigerBeetle with Docker ",(0,r.kt)("a",{parentName:"p",href:"/operating/docker"},"here"),"."),(0,r.kt)("h2",{id:"2-create-the-data-file"},"2. Create the Data File"),(0,r.kt)("p",null,"Each TigerBeetle node uses a single data file to store its state. Create the data file using the\n",(0,r.kt)("inlineCode",{parentName:"p"},"format")," command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 --development 0_0.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},'info(io): creating "0_0.tigerbeetle"...\ninfo(io): allocating 660.140625MiB...\n')),(0,r.kt)("h2",{id:"3-start-your-cluster"},"3. Start Your Cluster"),(0,r.kt)("p",null,"Now we'll run the TigerBeetle server."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=3000 --development 0_0.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},'info(io): opening "0_0.tigerbeetle"...\ninfo(main): 0: cluster=0: listening on 127.0.0.1:3000\n')),(0,r.kt)("h2",{id:"4-connect-to-the-repl"},"4. Connect to the REPL"),(0,r.kt)("p",null,"Now that we have TigerBeetle running, we can connect to it via the REPL to create some accounts and\ntransfers!"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle repl --cluster=0 --addresses=3000\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"TigerBeetle Client\n Hit enter after a semicolon to run a command.\n\nExamples:\n create_accounts id=1 code=10 ledger=700 flags=linked | history,\n id=2 code=10 ledger=700;\n create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;\n lookup_accounts id=1;\n lookup_accounts id=1, id=2;\n")),(0,r.kt)("h2",{id:"5-create-accounts"},"5. Create Accounts"),(0,r.kt)("p",null,"In the REPL, create two accounts on the same ledger using the command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"create_accounts id=1 code=10 ledger=700,\n id=2 code=10 ledger=700;\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"info(message_bus): connected to replica 0\n")),(0,r.kt)("h2",{id:"6-create-a-transfer"},"6. Create a Transfer"),(0,r.kt)("p",null,"Now create a transfer of ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," (of some amount/currency) between the two accounts."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;\n")),(0,r.kt)("p",null,"Now, the amount of ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," has been credited to account ",(0,r.kt)("inlineCode",{parentName:"p"},"2")," and debited from account ",(0,r.kt)("inlineCode",{parentName:"p"},"1"),"."),(0,r.kt)("h2",{id:"7-look-up-accounts"},"7. Look Up Accounts"),(0,r.kt)("p",null,"Let's query TigerBeetle for these two accounts to verify the transfer we made!"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"lookup_accounts id=1, id=2;\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "1",\n "user_data": "0",\n "ledger": "700",\n "code": "10",\n "flags": [],\n "debits_pending": "0",\n "debits_posted": "10",\n "credits_pending": "0",\n "credits_posted": "0"\n}\n{\n "id": "2",\n "user_data": "0",\n "ledger": "700",\n "code": "10",\n "flags": "",\n "debits_pending": "0",\n "debits_posted": "0",\n "credits_pending": "0",\n "credits_posted": "10"\n}\n')),(0,r.kt)("p",null,"And indeed you can see that account ",(0,r.kt)("inlineCode",{parentName:"p"},"1")," has ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," as ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," and account ",(0,r.kt)("inlineCode",{parentName:"p"},"2")," has\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," as ",(0,r.kt)("inlineCode",{parentName:"p"},"10"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," amount is fully accounted for!"),(0,r.kt)("p",null,"You can take a look at the ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Accounts")," reference")," to understand all of the\nfields on the accounts."),(0,r.kt)("p",null,"You can also take a look at the ",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/"},"Request Types")," to see what else you\ncan do with the REPL."),(0,r.kt)("h2",{id:"optional-run-a-multi-node-cluster"},"Optional: Run a Multi-Node Cluster"),(0,r.kt)("p",null,"Up to this point, we have only shown you how to run a single-node TigerBeetle cluster. In\nproduction, TigerBeetle is intended to be run with ",(0,r.kt)("a",{parentName:"p",href:"/operating/deploy"},"6 nodes"),"."),(0,r.kt)("p",null,"Here, we will show you how to run a 3-node cluster (the idea is the same for 6 nodes):"),(0,r.kt)("p",null,"First, create the data files for each node:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle format --cluster=0 --replica=0 --replica-count=3 --development 0_0.tigerbeetle\n./tigerbeetle format --cluster=0 --replica=1 --replica-count=3 --development 0_1.tigerbeetle\n./tigerbeetle format --cluster=0 --replica=2 --replica-count=3 --development 0_2.tigerbeetle\n")),(0,r.kt)("p",null,"Note that the data file stores which replica in the cluster the file belongs to."),(0,r.kt)("p",null,"Start each server in a new terminal window:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002 --development 0_0.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002 --development 0_1.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002 --development 0_2.tigerbeetle\n")),(0,r.kt)("p",null,"TigerBeetle uses the ",(0,r.kt)("inlineCode",{parentName:"p"},"--replica")," that's stored in the data file as an index into the ",(0,r.kt)("inlineCode",{parentName:"p"},"--addresses"),"\nprovided."),(0,r.kt)("p",null,"You can connect to the REPL as described above try creating accounts and transfers in this cluster."),(0,r.kt)("p",null,"You can also read more about ",(0,r.kt)("a",{parentName:"p",href:"/operating/deploy"},"deploying TigerBeetle in production"),"."),(0,r.kt)("h2",{id:"next-designing-for-tigerbeetle"},"Next: Designing for TigerBeetle"),(0,r.kt)("p",null,"Now that you've created some accounts and transfers, you may want to read about\n",(0,r.kt)("a",{parentName:"p",href:"/coding/system-architecture"},"how TigerBeetle fits into your system architecture")," and dig into\nthe ",(0,r.kt)("a",{parentName:"p",href:"/coding/data-modeling"},"data model"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2fce5429.24d660e0.js b/assets/js/2fce5429.24d660e0.js deleted file mode 100644 index e95fc5f..0000000 --- a/assets/js/2fce5429.24d660e0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2738],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,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 l(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 l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=c(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)}},g=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,d=i(e,["components","mdxType","originalType","parentName"]),u=c(n),g=r,m=u["".concat(s,".").concat(g)]||u[g]||p[g]||l;return n?a.createElement(m,o(o({ref:t},d),{},{components:n})):a.createElement(m,o({ref:t},d))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,o=new Array(l);o[0]=g;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[u]="string"==typeof e?e:r,o[1]=i;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>p,frontMatter:()=>l,metadata:()=>i,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const l={sidebar_label:"Quick Start",sidebar_position:2},o="Quick Start",i={unversionedId:"quick-start",id:"quick-start",title:"Quick Start",description:"This page will guide you through downloading TigerBeetle, setting up a single- or multi-node",source:"@site/pages/quick-start.md",sourceDirName:".",slug:"/quick-start",permalink:"/quick-start",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/quick-start.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_label:"Quick Start",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"TigerBeetle Docs",permalink:"/"},next:{title:"Developing Applications on TigerBeetle",permalink:"/coding/"}},s={},c=[{value:"1. Download TigerBeetle",id:"1-download-tigerbeetle",level:2},{value:"Latest Release",id:"latest-release",level:3},{value:"Build from Source",id:"build-from-source",level:4},{value:"Direct Download",id:"direct-download",level:4},{value:"Docker",id:"docker",level:4},{value:"2. Create the Data File",id:"2-create-the-data-file",level:2},{value:"3. Start Your Cluster",id:"3-start-your-cluster",level:2},{value:"4. Connect to the REPL",id:"4-connect-to-the-repl",level:2},{value:"5. Create Accounts",id:"5-create-accounts",level:2},{value:"6. Create a Transfer",id:"6-create-a-transfer",level:2},{value:"7. Look Up Accounts",id:"7-look-up-accounts",level:2},{value:"Optional: Run a Multi-Node Cluster",id:"optional-run-a-multi-node-cluster",level:2},{value:"Next: Designing for TigerBeetle",id:"next-designing-for-tigerbeetle",level:2}],d={toc:c},u="wrapper";function p(e){let{components:t,...n}=e;return(0,r.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"quick-start"},"Quick Start"),(0,r.kt)("p",null,"This page will guide you through downloading TigerBeetle, setting up a single- or multi-node\ncluster, and creating some accounts and transfers using the REPL."),(0,r.kt)("h2",{id:"1-download-tigerbeetle"},"1. Download TigerBeetle"),(0,r.kt)("p",null,"TigerBeetle is a single, small, statically-linked binary."),(0,r.kt)("h3",{id:"latest-release"},"Latest Release"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"# macOS\ncurl -Lo tigerbeetle.zip https://mac.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"# Linux\ncurl -Lo tigerbeetle.zip https://linux.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},'# Windows\npowershell -command "curl.exe -Lo tigerbeetle.zip https://windows.tigerbeetle.com; Expand-Archive tigerbeetle.zip .; .\\tigerbeetle version"\n')),(0,r.kt)("h4",{id:"build-from-source"},"Build from Source"),(0,r.kt)("p",null,"To build TigerBeetle from source, clone the repo, install Zig and run ",(0,r.kt)("inlineCode",{parentName:"p"},"zig build"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"git clone https://github.com/tigerbeetle/tigerbeetle && cd tigerbeetle\n./scripts/install_zig.sh # or .bat if you're on Windows.\nzig/zig build\n./tigerbeetle version\n")),(0,r.kt)("p",null,"Notes:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Building from source is not recommended for production deployments."),(0,r.kt)("li",{parentName:"ul"},"If you build TigerBeetle from source, it is only compatible with clients that were also built from source.")),(0,r.kt)("h4",{id:"direct-download"},"Direct Download"),(0,r.kt)("p",null,"You can download prebuilt binaries here:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:"left"}),(0,r.kt)("th",{parentName:"tr",align:"left"},"Linux"),(0,r.kt)("th",{parentName:"tr",align:"left"},"Windows"),(0,r.kt)("th",{parentName:"tr",align:"left"},"MacOS"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"left"},"x86_64"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-x86_64-linux.zip"},"tigerbeetle-x86_64-linux.zip")),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-x86_64-windows.zip"},"tigerbeetle-x86_64-windows.zip")),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-universal-macos.zip"},"tigerbeetle-universal-macos.zip"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"left"},"aarch64"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-aarch64-linux.zip"},"tigerbeetle-aarch64-linux.zip")),(0,r.kt)("td",{parentName:"tr",align:"left"},"N/A"),(0,r.kt)("td",{parentName:"tr",align:"left"},(0,r.kt)("a",{parentName:"td",href:"https://github.com/tigerbeetle/tigerbeetle/releases/latest/download/tigerbeetle-universal-macos.zip"},"tigerbeetle-universal-macos.zip"))))),(0,r.kt)("h4",{id:"docker"},"Docker"),(0,r.kt)("p",null,"You can find instructions on using TigerBeetle with Docker ",(0,r.kt)("a",{parentName:"p",href:"/operating/docker"},"here"),"."),(0,r.kt)("h2",{id:"2-create-the-data-file"},"2. Create the Data File"),(0,r.kt)("p",null,"Each TigerBeetle node uses a single data file to store its state. Create the data file using the\n",(0,r.kt)("inlineCode",{parentName:"p"},"format")," command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 --development 0_0.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},'info(io): creating "0_0.tigerbeetle"...\ninfo(io): allocating 660.140625MiB...\n')),(0,r.kt)("h2",{id:"3-start-your-cluster"},"3. Start Your Cluster"),(0,r.kt)("p",null,"Now we'll run the TigerBeetle server."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=3000 --development 0_0.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},'info(io): opening "0_0.tigerbeetle"...\ninfo(main): 0: cluster=0: listening on 127.0.0.1:3000\n')),(0,r.kt)("h2",{id:"4-connect-to-the-repl"},"4. Connect to the REPL"),(0,r.kt)("p",null,"Now that we have TigerBeetle running, we can connect to it via the REPL to create some accounts and\ntransfers!"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle repl --cluster=0 --addresses=3000\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"TigerBeetle Client\n Hit enter after a semicolon to run a command.\n\nExamples:\n create_accounts id=1 code=10 ledger=700 flags=linked | history,\n id=2 code=10 ledger=700;\n create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;\n lookup_accounts id=1;\n lookup_accounts id=1, id=2;\n")),(0,r.kt)("h2",{id:"5-create-accounts"},"5. Create Accounts"),(0,r.kt)("p",null,"In the REPL, create two accounts on the same ledger using the command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"create_accounts id=1 code=10 ledger=700,\n id=2 code=10 ledger=700;\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"info(message_bus): connected to replica 0\n")),(0,r.kt)("h2",{id:"6-create-a-transfer"},"6. Create a Transfer"),(0,r.kt)("p",null,"Now create a transfer of ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," (of some amount/currency) between the two accounts."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;\n")),(0,r.kt)("p",null,"Now, the amount of ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," has been credited to account ",(0,r.kt)("inlineCode",{parentName:"p"},"2")," and debited from account ",(0,r.kt)("inlineCode",{parentName:"p"},"1"),"."),(0,r.kt)("h2",{id:"7-look-up-accounts"},"7. Look Up Accounts"),(0,r.kt)("p",null,"Let's query TigerBeetle for these two accounts to verify the transfer we made!"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"lookup_accounts id=1, id=2;\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "1",\n "user_data": "0",\n "ledger": "700",\n "code": "10",\n "flags": [],\n "debits_pending": "0",\n "debits_posted": "10",\n "credits_pending": "0",\n "credits_posted": "0"\n}\n{\n "id": "2",\n "user_data": "0",\n "ledger": "700",\n "code": "10",\n "flags": "",\n "debits_pending": "0",\n "debits_posted": "0",\n "credits_pending": "0",\n "credits_posted": "10"\n}\n')),(0,r.kt)("p",null,"And indeed you can see that account ",(0,r.kt)("inlineCode",{parentName:"p"},"1")," has ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," as ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," and account ",(0,r.kt)("inlineCode",{parentName:"p"},"2")," has\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," as ",(0,r.kt)("inlineCode",{parentName:"p"},"10"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"10")," amount is fully accounted for!"),(0,r.kt)("p",null,"You can take a look at the ",(0,r.kt)("a",{parentName:"p",href:"/reference/account"},(0,r.kt)("inlineCode",{parentName:"a"},"Accounts")," reference")," to understand all of the\nfields on the accounts."),(0,r.kt)("p",null,"You can also take a look at the ",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/"},"Request Types")," to see what else you\ncan do with the REPL."),(0,r.kt)("h2",{id:"optional-run-a-multi-node-cluster"},"Optional: Run a Multi-Node Cluster"),(0,r.kt)("p",null,"Up to this point, we have only shown you how to run a single-node TigerBeetle cluster. In\nproduction, TigerBeetle is intended to be run with ",(0,r.kt)("a",{parentName:"p",href:"/operating/deploy"},"6 nodes"),"."),(0,r.kt)("p",null,"Here, we will show you how to run a 3-node cluster (the idea is the same for 6 nodes):"),(0,r.kt)("p",null,"First, create the data files for each node:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle format --cluster=0 --replica=0 --replica-count=3 --development 0_0.tigerbeetle\n./tigerbeetle format --cluster=0 --replica=1 --replica-count=3 --development 0_1.tigerbeetle\n./tigerbeetle format --cluster=0 --replica=2 --replica-count=3 --development 0_2.tigerbeetle\n")),(0,r.kt)("p",null,"Note that the data file stores which replica in the cluster the file belongs to."),(0,r.kt)("p",null,"Start each server in a new terminal window:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002 --development 0_0.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002 --development 0_1.tigerbeetle\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"./tigerbeetle start --addresses=127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002 --development 0_2.tigerbeetle\n")),(0,r.kt)("p",null,"TigerBeetle uses the ",(0,r.kt)("inlineCode",{parentName:"p"},"--replica")," that's stored in the data file as an index into the ",(0,r.kt)("inlineCode",{parentName:"p"},"--addresses"),"\nprovided."),(0,r.kt)("p",null,"You can connect to the REPL as described above try creating accounts and transfers in this cluster."),(0,r.kt)("p",null,"You can also read more about ",(0,r.kt)("a",{parentName:"p",href:"/operating/deploy"},"deploying TigerBeetle in production"),"."),(0,r.kt)("h2",{id:"next-designing-for-tigerbeetle"},"Next: Designing for TigerBeetle"),(0,r.kt)("p",null,"Now that you've created some accounts and transfers, you may want to read about\n",(0,r.kt)("a",{parentName:"p",href:"/coding/system-architecture"},"how TigerBeetle fits into your system architecture")," and dig into\nthe ",(0,r.kt)("a",{parentName:"p",href:"/coding/data-modeling"},"data model"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c227177d.34ac2fe4.js b/assets/js/c227177d.34ac2fe4.js new file mode 100644 index 0000000..dc40c4e --- /dev/null +++ b/assets/js/c227177d.34ac2fe4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2024],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var r=n(7294);function a(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 r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},c=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),u=p(n),c=a,h=u["".concat(s,".").concat(c)]||u[c]||g[c]||i;return n?r.createElement(h,l(l({ref:t},d),{},{components:n})):r.createElement(h,l({ref:t},d))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,l=new Array(i);l[0]=c;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[u]="string"==typeof e?e:a,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var r=n(7462),a=(n(7294),n(3905));const i={sidebar_position:5},l="Upgrading",o={unversionedId:"operating/upgrading",id:"operating/upgrading",title:"Upgrading",description:"TigerBeetle guarantees storage stability and provides forward upgradeability. In other words, data",source:"@site/pages/operating/upgrading.md",sourceDirName:"operating",slug:"/operating/upgrading",permalink:"/operating/upgrading",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/operating/upgrading.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Managed Service",permalink:"/operating/managed-service"},next:{title:".NET",permalink:"/clients/dotnet"}},s={},p=[{value:"Planning for upgrades",id:"planning-for-upgrades",level:2},{value:"Upgrading binary-based installations",id:"upgrading-binary-based-installations",level:2},{value:"Upgrading Docker-based installations",id:"upgrading-docker-based-installations",level:2},{value:"Upgrading clients",id:"upgrading-clients",level:2},{value:".NET",id:"net",level:3},{value:"Go",id:"go",level:3},{value:"Java",id:"java",level:3},{value:"Node.js",id:"nodejs",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Upgrading to a newer version with incompatible clients",id:"upgrading-to-a-newer-version-with-incompatible-clients",level:3}],d={toc:p},u="wrapper";function g(e){let{components:t,...n}=e;return(0,a.kt)(u,(0,r.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"upgrading"},"Upgrading"),(0,a.kt)("p",null,"TigerBeetle guarantees storage stability and provides forward upgradeability. In other words, data\nfiles created by a particular past version of TigerBeetle can be migrated to any future version of\nTigerBeetle."),(0,a.kt)("p",null,"Migration is automatic and the upgrade process is usually as simple as:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Upgrade the replicas, by replacing the ",(0,a.kt)("inlineCode",{parentName:"li"},"./tigerbeetle")," binary with a newer version on each\nreplica (they will restart automatically when needed)."),(0,a.kt)("li",{parentName:"ul"},"Upgrade the clients, by updating the corresponding client libraries, recompiling and redeploying\nas usual.")),(0,a.kt)("p",null,"There's no need to stop the cluster for upgrades, and the client upgrades can be rolled out\ngradually as any change to the client code might."),(0,a.kt)("p",null,"NOTE: if you are upgrading from 0.15.3 (the first stable version), the upgrade procedure is more\ninvolved, see the ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/releases/tag/0.15.4"},"release notes for 0.15.4"),"."),(0,a.kt)("h2",{id:"planning-for-upgrades"},"Planning for upgrades"),(0,a.kt)("p",null,"When upgrading TigerBeetle, each release specifies two important versions:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"the oldest release that can be upgraded from and,"),(0,a.kt)("li",{parentName:"ul"},"the oldest supported client version.")),(0,a.kt)("p",null,"It's critical to make sure that the release you intend to upgrade from is supported by the release\nyou're upgrading to. This is a hard requirement, but also a hard guarantee: if you wish to upgrade\nto ",(0,a.kt)("inlineCode",{parentName:"p"},"0.15.20")," which says it supports down to ",(0,a.kt)("inlineCode",{parentName:"p"},"0.15.5"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"0.15.5")," ",(0,a.kt)("em",{parentName:"p"},"will")," work and ",(0,a.kt)("inlineCode",{parentName:"p"},"0.15.4")," ",(0,a.kt)("em",{parentName:"p"},"will not"),".\nYou will have to perform multiple upgrades in this case."),(0,a.kt)("p",null,"The upgrade process involves first upgrading the replicas, followed by upgrading the clients. The\nclient version ",(0,a.kt)("em",{parentName:"p"},"cannot")," be newer than the replica version, and will fail with an error message if\nso. Provided the supported version ranges overlap, coordinating the upgrade between clients and\nreplicas is not required."),(0,a.kt)("p",null,"Upgrading causes a short period of unavailability as the replicas restart. This is on the order of\n5 seconds, and will show up as a latency spike on requests. The TigerBeetle clients will internally\nretry any requests during the period."),(0,a.kt)("p",null,"Any special instructions, like that when upgrading from 0.15.3 to 0.15.4, will be explicitly\nmentioned in the changelog and release notes."),(0,a.kt)("p",null,"Even though this period is short, scheduling a maintenance windows for upgrades is still\nrecommended, for an extra layer of safety."),(0,a.kt)("h2",{id:"upgrading-binary-based-installations"},"Upgrading binary-based installations"),(0,a.kt)("p",null,"If TigerBeetle is installed under ",(0,a.kt)("inlineCode",{parentName:"p"},"/usr/bin/tigerbeetle"),", and you wish to upgrade to ",(0,a.kt)("inlineCode",{parentName:"p"},"0.15.4"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"# SSH to each replica, in no particular order:\ncd /tmp\nwget https://github.com/tigerbeetle/tigerbeetle/releases/download/0.15.4/tigerbeetle-x86_64-linux.zip\nunzip tigerbeetle-x86_64-linux.zip\n\n# Put the binary on the same file system as the target, so mv is atomic.\nmv tigerbeetle /usr/bin/tigerbeetle-new\n\nmv /usr/bin/tigerbeetle /usr/bin/tigerbeetle-old\nmv /usr/bin/tigerbeetle-new /usr/bin/tigerbeetle\n\n# Restart TigerBeetle. Only required when upgrading from 0.15.3.\n# Otherwise, it will detect new versions are available and coordinate the upgrade itself.\nsystemctl restart tigerbeetle # or, however you are managing TigerBeetle.\n")),(0,a.kt)("h2",{id:"upgrading-docker-based-installations"},"Upgrading Docker-based installations"),(0,a.kt)("p",null,"If you're running TigerBeetle inside Kubernetes or Docker, update the tag that is pointed to to the\nrelease you wish to upgrade to. Before beginning, it's strongly recommend to have a rolling deploy\nstrategy set up."),(0,a.kt)("p",null,"For example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"image: ghcr.io/tigerbeetle/tigerbeetle:0.15.3\n")),(0,a.kt)("p",null,"becomes"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"image: ghcr.io/tigerbeetle/tigerbeetle:0.15.4\n")),(0,a.kt)("p",null,"Due to the way upgrades work internally, this will restart with the new binary available, but still\nrunning the older version. TigerBeetle will then coordinate the actual upgrade when all replicas\nare ready and have the latest version available."),(0,a.kt)("h2",{id:"upgrading-clients"},"Upgrading clients"),(0,a.kt)("p",null,"Update your language's specific package management, to reference the same version of the\nTigerBeetle client:"),(0,a.kt)("h3",{id:"net"},".NET"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"dotnet add package tigerbeetle --version 0.15.4\n")),(0,a.kt)("h3",{id:"go"},"Go"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"go mod edit -require github.com/tigerbeetle/tigerbeetle-go@v0.15.4\n")),(0,a.kt)("h3",{id:"java"},"Java"),(0,a.kt)("p",null,"Edit your ",(0,a.kt)("inlineCode",{parentName:"p"},"pom.xml"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"}," \n com.tigerbeetle\n tigerbeetle-java\n 0.15.4\n \n")),(0,a.kt)("h3",{id:"nodejs"},"Node.js"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"npm install tigerbeetle-node@0.15.4\n")),(0,a.kt)("h2",{id:"troubleshooting"},"Troubleshooting"),(0,a.kt)("h3",{id:"upgrading-to-a-newer-version-with-incompatible-clients"},"Upgrading to a newer version with incompatible clients"),(0,a.kt)("p",null,"If a release of TigerBeetle no longer supports the client version you're using, it's still possible\nto upgrade, with two options:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Upgrade the replicas to the latest version. In this case, the clients will stop working for the\nduration of the upgrade and unavailability will be extended."),(0,a.kt)("li",{parentName:"ul"},"Upgrade the replicas to the latest release that supports the client version in use, then upgrade\nthe clients to that version. Repeat this until you're on the latest release.")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c227177d.fe246cb3.js b/assets/js/c227177d.fe246cb3.js deleted file mode 100644 index 2d0e4ee..0000000 --- a/assets/js/c227177d.fe246cb3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2024],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var r=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 a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},g="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},c=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),g=p(n),c=i,h=g["".concat(s,".").concat(c)]||g[c]||u[c]||a;return n?r.createElement(h,l(l({ref:t},d),{},{components:n})):r.createElement(h,l({ref:t},d))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,l=new Array(a);l[0]=c;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[g]="string"==typeof e?e:i,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>a,metadata:()=>o,toc:()=>p});var r=n(7462),i=(n(7294),n(3905));const a={sidebar_position:5},l="Upgrading",o={unversionedId:"operating/upgrading",id:"operating/upgrading",title:"Upgrading",description:"TigerBeetle guarantees storage stability and provides forward upgradeability. In other words, data",source:"@site/pages/operating/upgrading.md",sourceDirName:"operating",slug:"/operating/upgrading",permalink:"/operating/upgrading",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/operating/upgrading.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Managed Service",permalink:"/operating/managed-service"},next:{title:".NET",permalink:"/clients/dotnet"}},s={},p=[{value:"Planning for upgrades",id:"planning-for-upgrades",level:2},{value:"Upgrading binary-based installations",id:"upgrading-binary-based-installations",level:2},{value:"Upgrading Docker-based installations",id:"upgrading-docker-based-installations",level:2},{value:"Upgrading clients",id:"upgrading-clients",level:2},{value:".NET",id:"net",level:3},{value:"Go",id:"go",level:3},{value:"Java",id:"java",level:3},{value:"Node.js",id:"nodejs",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Upgrading to a newer version with incompatible clients",id:"upgrading-to-a-newer-version-with-incompatible-clients",level:3},{value:"Upgrading Windows / macOS",id:"upgrading-windows--macos",level:2}],d={toc:p},g="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(g,(0,r.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"upgrading"},"Upgrading"),(0,i.kt)("p",null,"TigerBeetle guarantees storage stability and provides forward upgradeability. In other words, data\nfiles created by a particular past version of TigerBeetle can be migrated to any future version of\nTigerBeetle."),(0,i.kt)("p",null,"Migration is automatic and the upgrade process is usually as simple as:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Upgrade the replicas, by replacing the ",(0,i.kt)("inlineCode",{parentName:"li"},"./tigerbeetle")," binary with a newer version on each\nreplica (they will restart automatically when needed)."),(0,i.kt)("li",{parentName:"ul"},"Upgrade the clients, by updating the corresponding client libraries, recompiling and redeploying\nas usual.")),(0,i.kt)("p",null,"There's no need to stop the cluster for upgrades, and the client upgrades can be rolled out\ngradually as any change to the client code might."),(0,i.kt)("p",null,"NOTE: if you are upgrading from 0.15.3 (the first stable version), the upgrade procedure is more\ninvolved, see the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/releases/tag/0.15.4"},"release notes for 0.15.4"),"."),(0,i.kt)("h2",{id:"planning-for-upgrades"},"Planning for upgrades"),(0,i.kt)("p",null,"When upgrading TigerBeetle, each release specifies two important versions:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"the oldest release that can be upgraded from and,"),(0,i.kt)("li",{parentName:"ul"},"the oldest supported client version.")),(0,i.kt)("p",null,"It's critical to make sure that the release you intend to upgrade from is supported by the release\nyou're upgrading to. This is a hard requirement, but also a hard guarantee: if you wish to upgrade\nto ",(0,i.kt)("inlineCode",{parentName:"p"},"0.15.20")," which says it supports down to ",(0,i.kt)("inlineCode",{parentName:"p"},"0.15.5"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"0.15.5")," ",(0,i.kt)("em",{parentName:"p"},"will")," work and ",(0,i.kt)("inlineCode",{parentName:"p"},"0.15.4")," ",(0,i.kt)("em",{parentName:"p"},"will not"),".\nYou will have to perform multiple upgrades in this case."),(0,i.kt)("p",null,"The upgrade process involves first upgrading the replicas, followed by upgrading the clients. The\nclient version ",(0,i.kt)("em",{parentName:"p"},"cannot")," be newer than the replica version, and will fail with an error message if\nso. Provided the supported version ranges overlap, coordinating the upgrade between clients and\nreplicas is not required."),(0,i.kt)("p",null,"Upgrading causes a short period of unavailability as the replicas restart. This is on the order of\n5 seconds, and will show up as a latency spike on requests. The TigerBeetle clients will internally\nretry any requests during the period."),(0,i.kt)("p",null,"Any special instructions, like that when upgrading from 0.15.3 to 0.15.4, will be explicitly\nmentioned in the changelog and release notes."),(0,i.kt)("p",null,"Even though this period is short, scheduling a maintenance windows for upgrades is still\nrecommended, for an extra layer of safety."),(0,i.kt)("h2",{id:"upgrading-binary-based-installations"},"Upgrading binary-based installations"),(0,i.kt)("p",null,"If TigerBeetle is installed under ",(0,i.kt)("inlineCode",{parentName:"p"},"/usr/bin/tigerbeetle"),", and you wish to upgrade to ",(0,i.kt)("inlineCode",{parentName:"p"},"0.15.4"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"# SSH to each replica, in no particular order:\ncd /tmp\nwget https://github.com/tigerbeetle/tigerbeetle/releases/download/0.15.4/tigerbeetle-x86_64-linux.zip\nunzip tigerbeetle-x86_64-linux.zip\n\n# Put the binary on the same file system as the target, so mv is atomic.\nmv tigerbeetle /usr/bin/tigerbeetle-new\n\nmv /usr/bin/tigerbeetle /usr/bin/tigerbeetle-old\nmv /usr/bin/tigerbeetle-new /usr/bin/tigerbeetle\n\n# Restart TigerBeetle. Only required when upgrading from 0.15.3.\n# Otherwise, it will detect new versions are available and coordinate the upgrade itself.\nsystemctl restart tigerbeetle # or, however you are managing TigerBeetle.\n")),(0,i.kt)("h2",{id:"upgrading-docker-based-installations"},"Upgrading Docker-based installations"),(0,i.kt)("p",null,"If you're running TigerBeetle inside Kubernetes or Docker, update the tag that is pointed to to the\nrelease you wish to upgrade to. Before beginning, it's strongly recommend to have a rolling deploy\nstrategy set up."),(0,i.kt)("p",null,"For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"image: ghcr.io/tigerbeetle/tigerbeetle:0.15.3\n")),(0,i.kt)("p",null,"becomes"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"image: ghcr.io/tigerbeetle/tigerbeetle:0.15.4\n")),(0,i.kt)("p",null,"Due to the way upgrades work internally, this will restart with the new binary available, but still\nrunning the older version. TigerBeetle will then coordinate the actual upgrade when all replicas\nare ready and have the latest version available."),(0,i.kt)("h2",{id:"upgrading-clients"},"Upgrading clients"),(0,i.kt)("p",null,"Update your language's specific package management, to reference the same version of the\nTigerBeetle client:"),(0,i.kt)("h3",{id:"net"},".NET"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"dotnet add package tigerbeetle --version 0.15.4\n")),(0,i.kt)("h3",{id:"go"},"Go"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"go mod edit -require github.com/tigerbeetle/tigerbeetle-go@v0.15.4\n")),(0,i.kt)("h3",{id:"java"},"Java"),(0,i.kt)("p",null,"Edit your ",(0,i.kt)("inlineCode",{parentName:"p"},"pom.xml"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"}," \n com.tigerbeetle\n tigerbeetle-java\n 0.15.4\n \n")),(0,i.kt)("h3",{id:"nodejs"},"Node.js"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"npm install tigerbeetle-node@0.15.4\n")),(0,i.kt)("h2",{id:"troubleshooting"},"Troubleshooting"),(0,i.kt)("h3",{id:"upgrading-to-a-newer-version-with-incompatible-clients"},"Upgrading to a newer version with incompatible clients"),(0,i.kt)("p",null,"If a release of TigerBeetle no longer supports the client version you're using, it's still possible\nto upgrade, with two options:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Upgrade the replicas to the latest version. In this case, the clients will stop working for the\nduration of the upgrade and unavailability will be extended."),(0,i.kt)("li",{parentName:"ul"},"Upgrade the replicas to the latest release that supports the client version in use, then upgrade\nthe clients to that version. Repeat this until you're on the latest release.")),(0,i.kt)("h2",{id:"upgrading-windows--macos"},"Upgrading Windows / macOS"),(0,i.kt)("p",null,"Upgrading development data files on Windows / macOS is not yet supported. This will be added in the\nnext release."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cab96357.45ab5064.js b/assets/js/cab96357.748eb20a.js similarity index 50% rename from assets/js/cab96357.45ab5064.js rename to assets/js/cab96357.748eb20a.js index d831324..83cd237 100644 --- a/assets/js/cab96357.45ab5064.js +++ b/assets/js/cab96357.748eb20a.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3042],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>f});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 l(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 i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=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):i(i({},t),e)),n},c=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)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),p=d(n),m=r,f=p["".concat(s,".").concat(m)]||p[m]||u[m]||l;return n?a.createElement(f,i(i({ref:t},c),{},{components:n})):a.createElement(f,i({ref:t},c))}));function f(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,i=new Array(l);i[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[p]="string"==typeof e?e:r,i[1]=o;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const l={sidebar_position:5},i="Balance Bounds",o={unversionedId:"coding/recipes/balance-bounds",id:"coding/recipes/balance-bounds",title:"Balance Bounds",description:"It is easy to limit an account's balance using either",source:"@site/pages/coding/recipes/balance-bounds.md",sourceDirName:"coding/recipes",slug:"/coding/recipes/balance-bounds",permalink:"/coding/recipes/balance-bounds",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/coding/recipes/balance-bounds.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Balance-Conditional Transfers",permalink:"/coding/recipes/balance-conditional-transfers"},next:{title:"Correcting Transfers",permalink:"/coding/recipes/correcting-transfers"}},s={},d=[{value:"Preconditions",id:"preconditions",level:2},{value:"Executing a Transfer with a Balance Bounds Check",id:"executing-a-transfer-with-a-balance-bounds-check",level:2},{value:"If the Target Account Has a Credit Balance",id:"if-the-target-account-has-a-credit-balance",level:3},{value:"If the Target Account Has a Debit Balance",id:"if-the-target-account-has-a-debit-balance",level:3},{value:"Understanding the Mechanism",id:"understanding-the-mechanism",level:3}],c={toc:d},p="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"balance-bounds"},"Balance Bounds"),(0,r.kt)("p",null,"It is easy to limit an account's balance using either\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")),"\nor\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")),"."),(0,r.kt)("p",null,"What if you want an account's balance to stay between an upper and a lower bound?"),(0,r.kt)("p",null,"This is possible to check atomically using a set of linked transfers. (Note: with the\n",(0,r.kt)("inlineCode",{parentName:"p"},"must_not_exceed")," flag invariants, an account is guaranteed to never violate those invariants. This\nmaximum balance approach must be enforced per-transfer -- it is possible to exceed the limit simply\nby not enforcing it for a particular transfer.)"),(0,r.kt)("h2",{id:"preconditions"},"Preconditions"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Target Account Should Have a Limited Balance")),(0,r.kt)("p",null,"The account whose balance you want to bound should have one of these flags set:"),(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"},"flags.debits_must_not_exceed_credits")),"\nfor accounts with ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#credit-balances"},"credit balances")),(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"},"flags.credits_must_not_exceed_debits")),"\nfor accounts with ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#debit-balances"},"debit balances"))),(0,r.kt)("ol",{start:2},(0,r.kt)("li",{parentName:"ol"},"Create a Control Account with the Opposite Limit")),(0,r.kt)("p",null,"There must also be a designated control account."),(0,r.kt)("p",null,"As you can see below, this account will never actually take control of the target account's funds,\nbut we will set up simultaneous transfers in and out of the control account to apply the limit."),(0,r.kt)("p",null,"This account must have the opposite limit applied as the target account:"),(0,r.kt)("ul",null,(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"},"flags.credits_must_not_exceed_debits")),"\nif the target account has a ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#credit-balances"},"credit balance")),(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"},"flags.debits_must_not_exceed_credits")),"\nif the target account has a ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#debit-balances"},"debit balance"))),(0,r.kt)("ol",{start:3},(0,r.kt)("li",{parentName:"ol"},"Create an Operator Account")),(0,r.kt)("p",null,"The operator account will be used to fund the Control Account."),(0,r.kt)("h2",{id:"executing-a-transfer-with-a-balance-bounds-check"},"Executing a Transfer with a Balance Bounds Check"),(0,r.kt)("p",null,"This consists of 5 ",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/#linked-events"},"linked transfers"),"."),(0,r.kt)("p",null,"We will refer to two amounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"The ",(0,r.kt)("strong",{parentName:"li"},"limit amount")," is upper bound we want to maintain on the target account's balance."),(0,r.kt)("li",{parentName:"ul"},"The ",(0,r.kt)("strong",{parentName:"li"},"transfer amount")," is the amount we want to transfer if and only if the target account's\nbalance after a successful transfer would be within the bounds.")),(0,r.kt)("h3",{id:"if-the-target-account-has-a-credit-balance"},"If the Target Account Has a Credit Balance"),(0,r.kt)("p",null,"In this case, we are keeping the Destination Account's balance between the bounds."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("th",{parentName:"tr",align:null},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Amount"),(0,r.kt)("th",{parentName:"tr",align:null},"Pending ID"),(0,r.kt)("th",{parentName:"tr",align:null},"Flags (Note: ",(0,r.kt)("inlineCode",{parentName:"th"},"\\|")," sets multiple flags)"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"1"),(0,r.kt)("td",{parentName:"tr",align:null},"Source"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"2"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"3"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"4"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"3"),"*"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"5"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0"))))),(0,r.kt)("p",null,"*","This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3)."),(0,r.kt)("h3",{id:"if-the-target-account-has-a-debit-balance"},"If the Target Account Has a Debit Balance"),(0,r.kt)("p",null,"In this case, we are keeping the Destination Account's balance between the bounds."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("th",{parentName:"tr",align:null},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Amount"),(0,r.kt)("th",{parentName:"tr",align:null},"Pending ID"),(0,r.kt)("th",{parentName:"tr",align:null},"Flags (Note ",(0,r.kt)("inlineCode",{parentName:"th"},"\\|")," sets multiple flags)"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"1"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},"Source"),(0,r.kt)("td",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"2"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"3"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"4"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"3"),"*"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"5"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0"))))),(0,r.kt)("p",null,"*","This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3)."),(0,r.kt)("h3",{id:"understanding-the-mechanism"},"Understanding the Mechanism"),(0,r.kt)("p",null,"Each of the 5 transfers is ",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/#linked-events"},"linked")," so that all of\nthem will succeed or all of them will fail."),(0,r.kt)("p",null,"The first transfer is the one we actually want to send."),(0,r.kt)("p",null,"The second transfer sets the Control Account's balance to the upper bound we want to impose."),(0,r.kt)("p",null,"The third transfer uses a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_credit"))," to transfer the Destination\nAccount's net credit balance or net debit balance, respectively, to the Control Account. This\ntransfer will fail if the first transfer would put the Destination Account's balance above the upper\nbound."),(0,r.kt)("p",null,"The third transfer is also a pending transfer, so it won't actually transfer the Destination\nAccount's funds, even if it succeeds."),(0,r.kt)("p",null,"If everything to this point succeeds, the fourth and fifth transfers simply undo the effects of the\nsecond and third transfers. The fourth transfer voids the pending transfer. And the fifth transfer\nresets the Control Account's net balance to zero."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[3042],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>f});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 l(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 i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=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):i(i({},t),e)),n},c=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)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),p=d(n),m=r,f=p["".concat(s,".").concat(m)]||p[m]||u[m]||l;return n?a.createElement(f,i(i({ref:t},c),{},{components:n})):a.createElement(f,i({ref:t},c))}));function f(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,i=new Array(l);i[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[p]="string"==typeof e?e:r,i[1]=o;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const l={sidebar_position:5},i="Balance Bounds",o={unversionedId:"coding/recipes/balance-bounds",id:"coding/recipes/balance-bounds",title:"Balance Bounds",description:"It is easy to limit an account's balance using either",source:"@site/pages/coding/recipes/balance-bounds.md",sourceDirName:"coding/recipes",slug:"/coding/recipes/balance-bounds",permalink:"/coding/recipes/balance-bounds",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/coding/recipes/balance-bounds.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Balance-Conditional Transfers",permalink:"/coding/recipes/balance-conditional-transfers"},next:{title:"Correcting Transfers",permalink:"/coding/recipes/correcting-transfers"}},s={},d=[{value:"Preconditions",id:"preconditions",level:2},{value:"Executing a Transfer with a Balance Bounds Check",id:"executing-a-transfer-with-a-balance-bounds-check",level:2},{value:"If the Target Account Has a Credit Balance",id:"if-the-target-account-has-a-credit-balance",level:3},{value:"If the Target Account Has a Debit Balance",id:"if-the-target-account-has-a-debit-balance",level:3},{value:"Understanding the Mechanism",id:"understanding-the-mechanism",level:3}],c={toc:d},p="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"balance-bounds"},"Balance Bounds"),(0,r.kt)("p",null,"It is easy to limit an account's balance using either\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")),"\nor\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")),"."),(0,r.kt)("p",null,"What if you want an account's balance to stay between an upper and a lower bound?"),(0,r.kt)("p",null,"This is possible to check atomically using a set of linked transfers. (Note: with the\n",(0,r.kt)("inlineCode",{parentName:"p"},"must_not_exceed")," flag invariants, an account is guaranteed to never violate those invariants. This\nmaximum balance approach must be enforced per-transfer -- it is possible to exceed the limit simply\nby not enforcing it for a particular transfer.)"),(0,r.kt)("h2",{id:"preconditions"},"Preconditions"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Target Account Should Have a Limited Balance")),(0,r.kt)("p",null,"The account whose balance you want to bound should have one of these flags set:"),(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"},"flags.debits_must_not_exceed_credits")),"\nfor accounts with ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#credit-balances"},"credit balances")),(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"},"flags.credits_must_not_exceed_debits")),"\nfor accounts with ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#debit-balances"},"debit balances"))),(0,r.kt)("ol",{start:2},(0,r.kt)("li",{parentName:"ol"},"Create a Control Account with the Opposite Limit")),(0,r.kt)("p",null,"There must also be a designated control account."),(0,r.kt)("p",null,"As you can see below, this account will never actually take control of the target account's funds,\nbut we will set up simultaneous transfers in and out of the control account to apply the limit."),(0,r.kt)("p",null,"This account must have the opposite limit applied as the target account:"),(0,r.kt)("ul",null,(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"},"flags.credits_must_not_exceed_debits")),"\nif the target account has a ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#credit-balances"},"credit balance")),(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"},"flags.debits_must_not_exceed_credits")),"\nif the target account has a ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#debit-balances"},"debit balance"))),(0,r.kt)("ol",{start:3},(0,r.kt)("li",{parentName:"ol"},"Create an Operator Account")),(0,r.kt)("p",null,"The operator account will be used to fund the Control Account."),(0,r.kt)("h2",{id:"executing-a-transfer-with-a-balance-bounds-check"},"Executing a Transfer with a Balance Bounds Check"),(0,r.kt)("p",null,"This consists of 5 ",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/#linked-events"},"linked transfers"),"."),(0,r.kt)("p",null,"We will refer to two amounts:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"The ",(0,r.kt)("strong",{parentName:"li"},"limit amount")," is upper bound we want to maintain on the target account's balance."),(0,r.kt)("li",{parentName:"ul"},"The ",(0,r.kt)("strong",{parentName:"li"},"transfer amount")," is the amount we want to transfer if and only if the target account's\nbalance after a successful transfer would be within the bounds.")),(0,r.kt)("h3",{id:"if-the-target-account-has-a-credit-balance"},"If the Target Account Has a Credit Balance"),(0,r.kt)("p",null,"In this case, we are keeping the Destination Account's balance between the bounds."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("th",{parentName:"tr",align:null},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Amount"),(0,r.kt)("th",{parentName:"tr",align:null},"Pending ID"),(0,r.kt)("th",{parentName:"tr",align:null},"Flags (Note: `"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"1"),(0,r.kt)("td",{parentName:"tr",align:null},"Source"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"2"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"3"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_debit"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"4"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"3"),"*"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"5"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0"))))),(0,r.kt)("p",null,"*","This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3)."),(0,r.kt)("h3",{id:"if-the-target-account-has-a-debit-balance"},"If the Target Account Has a Debit Balance"),(0,r.kt)("p",null,"In this case, we are keeping the Destination Account's balance between the bounds."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("th",{parentName:"tr",align:null},"Debit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Credit Account"),(0,r.kt)("th",{parentName:"tr",align:null},"Amount"),(0,r.kt)("th",{parentName:"tr",align:null},"Pending ID"),(0,r.kt)("th",{parentName:"tr",align:null},"Flags (Note `"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"1"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},"Source"),(0,r.kt)("td",{parentName:"tr",align:null},"Transfer"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"2"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"3"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Destination"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.balancing_credit"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagspending"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.pending"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"4"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"3"),"*"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagsvoid_pending_transfer"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.void_pending_transfer"))," ","|"," ",(0,r.kt)("a",{parentName:"td",href:"/reference/transfer#flagslinked"},(0,r.kt)("inlineCode",{parentName:"a"},"flags.linked")))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"5"),(0,r.kt)("td",{parentName:"tr",align:null},"Control"),(0,r.kt)("td",{parentName:"tr",align:null},"Operator"),(0,r.kt)("td",{parentName:"tr",align:null},"Limit"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"0"))))),(0,r.kt)("p",null,"*","This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3)."),(0,r.kt)("h3",{id:"understanding-the-mechanism"},"Understanding the Mechanism"),(0,r.kt)("p",null,"Each of the 5 transfers is ",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/#linked-events"},"linked")," so that all of\nthem will succeed or all of them will fail."),(0,r.kt)("p",null,"The first transfer is the one we actually want to send."),(0,r.kt)("p",null,"The second transfer sets the Control Account's balance to the upper bound we want to impose."),(0,r.kt)("p",null,"The third transfer uses a ",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_debit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_debit"))," or\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagsbalancing_credit"},(0,r.kt)("inlineCode",{parentName:"a"},"balancing_credit"))," to transfer the Destination\nAccount's net credit balance or net debit balance, respectively, to the Control Account. This\ntransfer will fail if the first transfer would put the Destination Account's balance above the upper\nbound."),(0,r.kt)("p",null,"The third transfer is also a pending transfer, so it won't actually transfer the Destination\nAccount's funds, even if it succeeds."),(0,r.kt)("p",null,"If everything to this point succeeds, the fourth and fifth transfers simply undo the effects of the\nsecond and third transfers. The fourth transfer voids the pending transfer. And the fifth transfer\nresets the Control Account's net balance to zero."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d1ef64c4.0e94e786.js b/assets/js/d1ef64c4.e5aff3c4.js similarity index 96% rename from assets/js/d1ef64c4.0e94e786.js rename to assets/js/d1ef64c4.e5aff3c4.js index ede8702..e223dbe 100644 --- a/assets/js/d1ef64c4.0e94e786.js +++ b/assets/js/d1ef64c4.e5aff3c4.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7784],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>g});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 p=a.createContext({}),c=function(e){var t=a.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return a.createElement(p.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},s=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,p=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),m=c(n),s=r,g=m["".concat(p,".").concat(s)]||m[s]||u[s]||i;return n?a.createElement(g,l(l({ref:t},d),{},{components:n})):a.createElement(g,l({ref:t},d))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=s;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[m]="string"==typeof e?e:r,l[1]=o;for(var c=2;c{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const i={sidebar_position:1},l="Currency Exchange",o={unversionedId:"coding/recipes/currency-exchange",id:"coding/recipes/currency-exchange",title:"Currency Exchange",description:"Some applications require multiple currencies. For example, a bank may hold balances in many",source:"@site/pages/coding/recipes/currency-exchange.md",sourceDirName:"coding/recipes",slug:"/coding/recipes/currency-exchange",permalink:"/coding/recipes/currency-exchange",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/coding/recipes/currency-exchange.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Time",permalink:"/coding/time"},next:{title:"Multi-Debit, Multi-Credit Transfers",permalink:"/coding/recipes/multi-debit-credit-transfers"}},p={},c=[{value:"Data Modeling",id:"data-modeling",level:2},{value:"Example",id:"example",level:3},{value:"Spread",id:"spread",level:2},{value:"Example",id:"example-1",level:3}],d={toc:c},m="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"currency-exchange"},"Currency Exchange"),(0,r.kt)("p",null,"Some applications require multiple currencies. For example, a bank may hold balances in many\ndifferent currencies. If a single logical entity holds multiple currencies, each currency must be\nheld in a separate TigerBeetle ",(0,r.kt)("inlineCode",{parentName:"p"},"Account"),". (Normalizing to a single currency at the application level\nshould be avoided because exchange rates fluctuate)."),(0,r.kt)("p",null,"Currency exchange is a trade of one type of currency (denoted by the ",(0,r.kt)("inlineCode",{parentName:"p"},"ledger"),") for another,\nfacilitated by an entity called the ",(0,r.kt)("em",{parentName:"p"},"liquidity provider"),"."),(0,r.kt)("h2",{id:"data-modeling"},"Data Modeling"),(0,r.kt)("p",null,"Distinct ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger"))," values denote different currencies (or\nother asset types). Transfers between pairs of accounts with different ",(0,r.kt)("inlineCode",{parentName:"p"},"ledger"),"s are\n",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/create_transfers#accounts_must_have_the_same_ledger"},"not permitted"),"."),(0,r.kt)("p",null,"Instead, currency exchange is implemented by creating two\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},"atomically linked")," different-ledger transfers between\ntwo pairs of same-ledger accounts."),(0,r.kt)("p",null,"A simple currency exchange involves four 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\u2081"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"1"),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A\u2082"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"2"),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"source liquidity account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"L\u2081"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"1"),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination liquidity account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"L\u2082"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"2"),".")),(0,r.kt)("p",null,"and two linked transfers:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A transfer ",(0,r.kt)("inlineCode",{parentName:"li"},"T\u2081")," from the ",(0,r.kt)("em",{parentName:"li"},"source account")," to the ",(0,r.kt)("em",{parentName:"li"},"source liquidity account"),"."),(0,r.kt)("li",{parentName:"ul"},"A transfer ",(0,r.kt)("inlineCode",{parentName:"li"},"T\u2082")," from the ",(0,r.kt)("em",{parentName:"li"},"destination liquidity account")," to the ",(0,r.kt)("em",{parentName:"li"},"destination account"),".")),(0,r.kt)("p",null,"The transfer amounts vary according to the exchange rate."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Both liquidity accounts belong to the liquidity provider (e.g. a bank or exchange)."),(0,r.kt)("li",{parentName:"ul"},"The source and destination accounts may belong to the same entity as one another, or different\nentities, depending on the use case.")),(0,r.kt)("h3",{id:"example"},"Example"),(0,r.kt)("p",null,"Consider sending ",(0,r.kt)("inlineCode",{parentName:"p"},"$100.00")," from account ",(0,r.kt)("inlineCode",{parentName:"p"},"A\u2081")," (denominated in USD) to account ",(0,r.kt)("inlineCode",{parentName:"p"},"A\u2082")," (denominated in\nINR). Assuming an exchange rate of ",(0,r.kt)("inlineCode",{parentName:"p"},"$1.00 = \u20b982.42135"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"$100.00 = \u20b98242.135"),":"),(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\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2081")),(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"},"INR"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},"8242135"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Amounts are ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#fractional-amounts-and-asset-scale"},"represented as integers"),"."),(0,r.kt)("li",{parentName:"ul"},"Because both liquidity accounts belong to the same entity, the entity does not lose money on the\ntransaction.",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"If the exchange rate is precise, the entity breaks even."),(0,r.kt)("li",{parentName:"ul"},"If the exchange rate is not precise, the application should round in favor of the liquidity\naccount to deter arbitrage."))),(0,r.kt)("li",{parentName:"ul"},"Because the two transfers are linked together, they will either both succeed or both fail.")),(0,r.kt)("h2",{id:"spread"},"Spread"),(0,r.kt)("p",null,"In the prior example, the liquidity provider breaks even. A fee (i.e. spread) can be included in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," chain as a separate transfer from the source account to the source liquidity account (",(0,r.kt)("inlineCode",{parentName:"p"},"A\u2081"),"\nto ",(0,r.kt)("inlineCode",{parentName:"p"},"L\u2081"),")."),(0,r.kt)("p",null,"This is preferable to simply modifying the exchange rate in the liquidity provider's favor because\nit implicitly records the exchange rate and spread at the time of the exchange \u2014 information that\ncannot be derived if the two are combined."),(0,r.kt)("h3",{id:"example-1"},"Example"),(0,r.kt)("p",null,"This depicts the same scenario as the prior example, except the liquidity provider charges a ",(0,r.kt)("inlineCode",{parentName:"p"},"$0.10"),"\nfee for the transaction."),(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"},"L\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A\u2081")),(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"},"L\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"INR"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},"8242135"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7784],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>g});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 p=a.createContext({}),c=function(e){var t=a.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return a.createElement(p.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},s=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,p=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),m=c(n),s=r,g=m["".concat(p,".").concat(s)]||m[s]||u[s]||i;return n?a.createElement(g,l(l({ref:t},d),{},{components:n})):a.createElement(g,l({ref:t},d))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=s;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[m]="string"==typeof e?e:r,l[1]=o;for(var c=2;c{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const i={sidebar_position:1},l="Currency Exchange",o={unversionedId:"coding/recipes/currency-exchange",id:"coding/recipes/currency-exchange",title:"Currency Exchange",description:"Some applications require multiple currencies. For example, a bank may hold balances in many",source:"@site/pages/coding/recipes/currency-exchange.md",sourceDirName:"coding/recipes",slug:"/coding/recipes/currency-exchange",permalink:"/coding/recipes/currency-exchange",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/coding/recipes/currency-exchange.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Time",permalink:"/coding/time"},next:{title:"Multi-Debit, Multi-Credit Transfers",permalink:"/coding/recipes/multi-debit-credit-transfers"}},p={},c=[{value:"Data Modeling",id:"data-modeling",level:2},{value:"Example",id:"example",level:3},{value:"Spread",id:"spread",level:2},{value:"Example",id:"example-1",level:3}],d={toc:c},m="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"currency-exchange"},"Currency Exchange"),(0,r.kt)("p",null,"Some applications require multiple currencies. For example, a bank may hold balances in many\ndifferent currencies. If a single logical entity holds multiple currencies, each currency must be\nheld in a separate TigerBeetle ",(0,r.kt)("inlineCode",{parentName:"p"},"Account"),". (Normalizing to a single currency at the application level\nshould be avoided because exchange rates fluctuate)."),(0,r.kt)("p",null,"Currency exchange is a trade of one type of currency (denoted by the ",(0,r.kt)("inlineCode",{parentName:"p"},"ledger"),") for another,\nfacilitated by an entity called the ",(0,r.kt)("em",{parentName:"p"},"liquidity provider"),"."),(0,r.kt)("h2",{id:"data-modeling"},"Data Modeling"),(0,r.kt)("p",null,"Distinct ",(0,r.kt)("a",{parentName:"p",href:"/reference/account#ledger"},(0,r.kt)("inlineCode",{parentName:"a"},"ledger"))," values denote different currencies (or\nother asset types). Transfers between pairs of accounts with different ",(0,r.kt)("inlineCode",{parentName:"p"},"ledger"),"s are\n",(0,r.kt)("a",{parentName:"p",href:"/reference/requests/create_transfers#accounts_must_have_the_same_ledger"},"not permitted"),"."),(0,r.kt)("p",null,"Instead, currency exchange is implemented by creating two\n",(0,r.kt)("a",{parentName:"p",href:"/reference/transfer#flagslinked"},"atomically linked")," different-ledger transfers between\ntwo pairs of same-ledger accounts."),(0,r.kt)("p",null,"A simple currency exchange involves four 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\u2081"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"1"),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"A\u2082"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"2"),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"source liquidity account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"L\u2081"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"1"),"."),(0,r.kt)("li",{parentName:"ul"},"A ",(0,r.kt)("em",{parentName:"li"},"destination liquidity account")," ",(0,r.kt)("inlineCode",{parentName:"li"},"L\u2082"),", on ledger ",(0,r.kt)("inlineCode",{parentName:"li"},"2"),".")),(0,r.kt)("p",null,"and two linked transfers:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A transfer ",(0,r.kt)("inlineCode",{parentName:"li"},"T\u2081")," from the ",(0,r.kt)("em",{parentName:"li"},"source account")," to the ",(0,r.kt)("em",{parentName:"li"},"source liquidity account"),"."),(0,r.kt)("li",{parentName:"ul"},"A transfer ",(0,r.kt)("inlineCode",{parentName:"li"},"T\u2082")," from the ",(0,r.kt)("em",{parentName:"li"},"destination liquidity account")," to the ",(0,r.kt)("em",{parentName:"li"},"destination account"),".")),(0,r.kt)("p",null,"The transfer amounts vary according to the exchange rate."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Both liquidity accounts belong to the liquidity provider (e.g. a bank or exchange)."),(0,r.kt)("li",{parentName:"ul"},"The source and destination accounts may belong to the same entity as one another, or different\nentities, depending on the use case.")),(0,r.kt)("h3",{id:"example"},"Example"),(0,r.kt)("p",null,"Consider sending ",(0,r.kt)("inlineCode",{parentName:"p"},"$100.00")," from account ",(0,r.kt)("inlineCode",{parentName:"p"},"A\u2081")," (denominated in USD) to account ",(0,r.kt)("inlineCode",{parentName:"p"},"A\u2082")," (denominated in\nINR). Assuming an exchange rate of ",(0,r.kt)("inlineCode",{parentName:"p"},"$1.00 = \u20b982.42135"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"$100.00 = \u20b98242.135"),":"),(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\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2081")),(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"},"INR"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},"8242135"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Amounts are ",(0,r.kt)("a",{parentName:"li",href:"/coding/data-modeling#fractional-amounts-and-asset-scale"},"represented as integers"),"."),(0,r.kt)("li",{parentName:"ul"},"Because both liquidity accounts belong to the same entity, the entity does not lose money on the\ntransaction.",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"If the exchange rate is precise, the entity breaks even."),(0,r.kt)("li",{parentName:"ul"},"If the exchange rate is not precise, the application should round in favor of the liquidity\naccount to deter arbitrage."))),(0,r.kt)("li",{parentName:"ul"},"Because the two transfers are linked together, they will either both succeed or both fail.")),(0,r.kt)("h2",{id:"spread"},"Spread"),(0,r.kt)("p",null,"In the prior example, the liquidity provider breaks even. A fee (i.e. spread) can be included in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," chain as a separate transfer from the source account to the source liquidity account (",(0,r.kt)("inlineCode",{parentName:"p"},"A\u2081"),"\nto ",(0,r.kt)("inlineCode",{parentName:"p"},"L\u2081"),")."),(0,r.kt)("p",null,"This is preferable to simply modifying the exchange rate in the liquidity provider's favor because\nit implicitly records the exchange rate and spread at the time of the exchange \u2014 information that\ncannot be derived if the two are combined."),(0,r.kt)("h3",{id:"example-1"},"Example"),(0,r.kt)("p",null,"This depicts the same scenario as the prior example, except the liquidity provider charges a ",(0,r.kt)("inlineCode",{parentName:"p"},"$0.10"),"\nfee for the transaction."),(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\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2081")),(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\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2081")),(0,r.kt)("td",{parentName:"tr",align:"right"},"10"),(0,r.kt)("td",{parentName:"tr",align:"right"},"true")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:"right"},"INR"),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"L\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},(0,r.kt)("inlineCode",{parentName:"td"},"A\u2082")),(0,r.kt)("td",{parentName:"tr",align:"right"},"8242135"),(0,r.kt)("td",{parentName:"tr",align:"right"},"false")))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.2d179625.js b/assets/js/runtime~main.f8a26b7d.js similarity index 95% rename from assets/js/runtime~main.2d179625.js rename to assets/js/runtime~main.f8a26b7d.js index e30b113..4e52039 100644 --- a/assets/js/runtime~main.2d179625.js +++ b/assets/js/runtime~main.f8a26b7d.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:"efc0f62b",113:"fe2ecf32",199:"d5fa9b78",333:"6002416e",379:"420d4d9b",400:"c1b6cd69",499:"358e5244",771:"70ef1af3",885:"bc411fef",962:"e7f364fd",1372:"0078a997",1426:"5959ada0",1488:"612528e3",1564:"647f2fba",1677:"d1c1c491",2024:"fe246cb3",2032:"fce3a44a",2132:"573f6997",2500:"451fd145",2519:"cd03e428",2539:"fc1667dd",2738:"24d660e0",3042:"45ab5064",3489:"9e4b8def",4038:"038c237e",4317:"eeffad2b",4396:"c0366401",4698:"aae615aa",4972:"16297f58",5511:"6b6801d8",5610:"4e33dbe0",5614:"8fe28eef",5616:"d9a58c29",5681:"9d675f26",5885:"3c68c3e0",6201:"d0387cd1",6214:"b2a12386",6221:"9b17ad40",6316:"fdbc5257",6441:"9deb9562",6641:"2e8c1c9c",6834:"907f6d80",6945:"e6ca558a",6994:"31fc9568",7042:"0128e97a",7092:"7a253ed1",7112:"1ee2c9d2",7382:"11f033a1",7596:"3ac1dbb2",7724:"492d96d5",7736:"e4edd3bf",7784:"0e94e786",7918:"51037d8a",7920:"aed7c3e9",7938:"55cd56a9",7996:"75904e74",8071:"79054ebd",8227:"9d873408",8404:"2ec07622",8775:"0c4ac77d",8894:"547a1c8d",9007:"67df2f0a",9049:"8fe8344c",9154:"2744f1bc",9259:"ba5f69c3",9290:"81cedd6f",9487:"8ebb94ec",9491:"d8e2bec8",9514:"f4f69e1a",9530:"f623f861",9743:"54abf306"}[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:"efc0f62b",113:"fe2ecf32",199:"d5fa9b78",333:"6002416e",379:"420d4d9b",400:"c1b6cd69",499:"358e5244",771:"70ef1af3",885:"bc411fef",962:"e7f364fd",1372:"0078a997",1426:"5959ada0",1488:"612528e3",1564:"647f2fba",1677:"d1c1c491",2024:"34ac2fe4",2032:"fce3a44a",2132:"573f6997",2500:"451fd145",2519:"cd03e428",2539:"fc1667dd",2738:"0b2bd283",3042:"748eb20a",3489:"9e4b8def",4038:"038c237e",4317:"eeffad2b",4396:"c0366401",4698:"aae615aa",4972:"16297f58",5511:"6b6801d8",5610:"4e33dbe0",5614:"8fe28eef",5616:"d9a58c29",5681:"9d675f26",5885:"3c68c3e0",6201:"d0387cd1",6214:"b2a12386",6221:"9b17ad40",6316:"fdbc5257",6441:"9deb9562",6641:"2e8c1c9c",6834:"907f6d80",6945:"e6ca558a",6994:"31fc9568",7042:"0128e97a",7092:"7a253ed1",7112:"1ee2c9d2",7382:"11f033a1",7596:"032d72ca",7724:"492d96d5",7736:"e4edd3bf",7784:"e5aff3c4",7918:"51037d8a",7920:"aed7c3e9",7938:"55cd56a9",7996:"75904e74",8071:"79054ebd",8227:"9d873408",8404:"2ec07622",8775:"0c4ac77d",8894:"547a1c8d",9007:"67df2f0a",9049:"8fe8344c",9154:"2744f1bc",9259:"ba5f69c3",9290:"81cedd6f",9487:"8ebb94ec",9491:"d8e2bec8",9514:"f4f69e1a",9530:"f623f861",9743:"54abf306"}[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 - + @@ -109,7 +109,7 @@ to subsequent events after the chain. The event that was the first to break the chain will have a unique error result. Other events in the chain will have their error result set to linked_event_failed.

    var batch = new System.Collections.Generic.List<Transfer>();

    // An individual transfer (successful):
    batch.Add(new Transfer { Id = 1, /* ... rest of transfer ... */ });

    // A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):
    batch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Commit/rollback.
    batch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Commit/rollback.
    batch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Fail with exists
    batch.Add(new Transfer { Id = 4, /* ... rest of transfer ... */ }); // Fail without committing

    // An individual transfer (successful):
    // This should not see any effect from the failed chain above.
    batch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ });

    // A chain of 2 transfers (the first transfer fails the chain):
    batch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked });
    batch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ });

    // A chain of 2 transfers (successful):
    batch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ Flags = TransferFlags.Linked });
    batch.Add(new Transfer { Id = 4, /* ... rest of transfer ... */ });

    createTransfersError = client.CreateTransfers(batch.ToArray());
    // error handling omitted
    - + \ No newline at end of file diff --git a/clients/go/index.html b/clients/go/index.html index 0255401..31b1553 100644 --- a/clients/go/index.html +++ b/clients/go/index.html @@ -6,7 +6,7 @@ Go | TigerBeetle Docs - + @@ -113,7 +113,7 @@ to subsequent events after the chain. The event that was the first to break the chain will have a unique error result. Other events in the chain will have their error result set to linked_event_failed.

    batch := []Transfer{}
    linkedFlag := TransferFlags{Linked: true}.ToUint16()

    // An individual transfer (successful):
    batch = append(batch, Transfer{ID: ToUint128(1) /* ... rest of transfer ... */})

    // A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):
    batch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Commit/rollback.
    batch = append(batch, Transfer{ID: ToUint128(3) /* ... , */, Flags: linkedFlag}) // Commit/rollback.
    batch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Fail with exists
    batch = append(batch, Transfer{ID: ToUint128(4) /* ... , */}) // Fail without committing

    // An individual transfer (successful):
    // This should not see any effect from the failed chain above.
    batch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */})

    // A chain of 2 transfers (the first transfer fails the chain):
    batch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */, Flags: linkedFlag})
    batch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */})

    // A chain of 2 transfers (successful):
    batch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */, Flags: linkedFlag})
    batch = append(batch, Transfer{ID: ToUint128(4) /* ... rest of transfer ... */})

    transfersRes, err = client.CreateTransfers(batch)
    - + \ No newline at end of file diff --git a/clients/java/index.html b/clients/java/index.html index 3720106..f4d87a7 100644 --- a/clients/java/index.html +++ b/clients/java/index.html @@ -6,7 +6,7 @@ Java | TigerBeetle Docs - + @@ -107,7 +107,7 @@ to subsequent events after the chain. The event that was the first to break the chain will have a unique error result. Other events in the chain will have their error result set to linked_event_failed.

    transfers = new TransferBatch(10);

    // An individual transfer (successful):
    transfers.add();
    transfers.setId(1);

    // A chain of 4 transfers (the last transfer in the chain closes the chain with
    // linked=false):
    transfers.add();
    transfers.setId(2); // Commit/rollback.
    transfers.setFlags(TransferFlags.LINKED);

    transfers.add();
    transfers.setId(3); // Commit/rollback.
    transfers.setFlags(TransferFlags.LINKED);

    transfers.add();
    transfers.setId(2); // Fail with exists
    transfers.setFlags(TransferFlags.LINKED);

    transfers.add();
    transfers.setId(4); // Fail without committing

    // An individual transfer (successful):
    // This should not see any effect from the failed chain above.
    transfers.add();
    transfers.setId(2);

    // A chain of 2 transfers (the first transfer fails the chain):
    transfers.add();
    transfers.setId(2);
    transfers.setFlags(TransferFlags.LINKED);

    transfers.add();
    transfers.setId(3);

    // A chain of 2 transfers (successful):
    transfers.add();
    transfers.setId(3);
    transfers.setFlags(TransferFlags.LINKED);

    transfers.add();
    transfers.setId(4);

    transferErrors = client.createTransfers(transfers);
    - + \ No newline at end of file diff --git a/clients/node/index.html b/clients/node/index.html index 50aa1b9..fa97307 100644 --- a/clients/node/index.html +++ b/clients/node/index.html @@ -6,7 +6,7 @@ Node.js | TigerBeetle Docs - + @@ -114,7 +114,7 @@ to subsequent events after the chain. The event that was the first to break the chain will have a unique error result. Other events in the chain will have their error result set to linked_event_failed.

    const batch = [];
    let linkedFlag = 0;
    linkedFlag |= TransferFlags.linked;

    // An individual transfer (successful):
    batch.push({ id: 1n /* , ... */ });

    // A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):
    batch.push({ id: 2n, /* ..., */ flags: linkedFlag }); // Commit/rollback.
    batch.push({ id: 3n, /* ..., */ flags: linkedFlag }); // Commit/rollback.
    batch.push({ id: 2n, /* ..., */ flags: linkedFlag }); // Fail with exists
    batch.push({ id: 4n, /* ..., */ flags: 0 }); // Fail without committing.

    // An individual transfer (successful):
    // This should not see any effect from the failed chain above.
    batch.push({ id: 2n, /* ..., */ flags: 0 });

    // A chain of 2 transfers (the first transfer fails the chain):
    batch.push({ id: 2n, /* ..., */ flags: linkedFlag });
    batch.push({ id: 3n, /* ..., */ flags: 0 });

    // A chain of 2 transfers (successful):
    batch.push({ id: 3n, /* ..., */ flags: linkedFlag });
    batch.push({ id: 4n, /* ..., */ flags: 0 });

    const errors = await client.createTransfers(batch);

    /**
    * console.log(errors);
    * [
    * { index: 1, error: 1 }, // linked_event_failed
    * { index: 2, error: 1 }, // linked_event_failed
    * { index: 3, error: 25 }, // exists
    * { index: 4, error: 1 }, // linked_event_failed
    *
    * { index: 6, error: 17 }, // exists_with_different_flags
    * { index: 7, error: 1 }, // linked_event_failed
    * ]
    */
    - + \ No newline at end of file diff --git a/coding/data-modeling/index.html b/coding/data-modeling/index.html index defbc0d..f65ef25 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 091a50a..609555c 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 be3fda9..f49da94 100644 --- a/coding/index.html +++ b/coding/index.html @@ -6,7 +6,7 @@ Developing Applications on TigerBeetle | TigerBeetle Docs - + @@ -28,7 +28,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 ebb7108..276cb4b 100644 --- a/coding/recipes/balance-bounds/index.html +++ b/coding/recipes/balance-bounds/index.html @@ -6,7 +6,7 @@ Balance Bounds | TigerBeetle Docs - + @@ -23,7 +23,7 @@ but we will set up simultaneous transfers in and out of the control account to apply the limit.

    This account must have the opposite limit applied as the target account:

    1. Create an Operator Account

    The operator account will be used to fund the Control Account.

    Executing a Transfer with a Balance Bounds Check

    This consists of 5 linked transfers.

    We will refer to two amounts:

    • The limit amount is upper bound we want to maintain on the target account's balance.
    • The transfer amount is the amount we want to transfer if and only if the target account's -balance after a successful transfer would be within the bounds.

    If the Target Account Has a Credit Balance

    In this case, we are keeping the Destination Account's balance between the bounds.

    TransferDebit AccountCredit AccountAmountPending IDFlags (Note: \| sets multiple flags)
    1SourceDestinationTransfer0flags.linked
    2ControlOperatorLimit0flags.linked
    3DestinationControl00flags.linked | flags.balancing_debit | flags.pending
    40003*flags.linked | flags.void_pending_transfer
    5OperatorControlLimit00

    *This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3).

    If the Target Account Has a Debit Balance

    In this case, we are keeping the Destination Account's balance between the bounds.

    TransferDebit AccountCredit AccountAmountPending IDFlags (Note \| sets multiple flags)
    1DestinationSourceTransfer0flags.linked
    2OperatorControlLimit0flags.linked
    3ControlDestination00flags.balancing_credit | flags.pending | flags.linked
    40003*flags.void_pending_transfer | flags.linked
    5ControlOperatorLimit00

    *This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3).

    Understanding the Mechanism

    Each of the 5 transfers is linked so that all of +balance after a successful transfer would be within the bounds.

    If the Target Account Has a Credit Balance

    In this case, we are keeping the Destination Account's balance between the bounds.

    TransferDebit AccountCredit AccountAmountPending IDFlags (Note: `
    1SourceDestinationTransfer0flags.linked
    2ControlOperatorLimit0flags.linked
    3DestinationControl00flags.linked | flags.balancing_debit | flags.pending
    40003*flags.linked | flags.void_pending_transfer
    5OperatorControlLimit00

    *This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3).

    If the Target Account Has a Debit Balance

    In this case, we are keeping the Destination Account's balance between the bounds.

    TransferDebit AccountCredit AccountAmountPending IDFlags (Note `
    1DestinationSourceTransfer0flags.linked
    2OperatorControlLimit0flags.linked
    3ControlDestination00flags.balancing_credit | flags.pending | flags.linked
    40003*flags.void_pending_transfer | flags.linked
    5ControlOperatorLimit00

    *This must be set to the transfer ID of the pending transfer (in this example, it is transfer 3).

    Understanding the Mechanism

    Each of the 5 transfers is linked so that all of them will succeed or all of them will fail.

    The first transfer is the one we actually want to send.

    The second transfer sets the Control Account's balance to the upper bound we want to impose.

    The third transfer uses a balancing_debit or balancing_credit to transfer the Destination Account's net credit balance or net debit balance, respectively, to the Control Account. This @@ -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 8b3b8f7..e68ddb7 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 5f60e8a..891f7f3 100644 --- a/coding/recipes/close-account/index.html +++ b/coding/recipes/close-account/index.html @@ -6,7 +6,7 @@ Close Account | TigerBeetle Docs - + @@ -15,7 +15,7 @@ credits or debits this balance respectively, to zero the account's balance and move the balance to another account.

    Example

    Given a set of accounts:

    AccountDebits PendingDebits PostedCredits PendingCredits PostedFlags
    A010020debits_must_not_exceed_credits
    B03005credits_must_not_exceed_debits
    C0000

    The "closing entries" for accounts A and B are:

    Debit AccountCredit AccountAmountAmount (recorded)FlagsNotes
    AC010balancing_debit(close account A)
    CB025balancing_credit(close account B)

    (Pass 0 as the Transfer.amount so that the 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 (non-zero) amount transferred.)

    After committing these transfers, A and B's balances are zero:

    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 14e18dc..05d800c 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 0a52a5a..3d9056b 100644 --- a/coding/recipes/currency-exchange/index.html +++ b/coding/recipes/currency-exchange/index.html @@ -6,7 +6,7 @@ Currency Exchange | TigerBeetle Docs - + @@ -28,8 +28,8 @@ to L₁).

    This is preferable to simply modifying the exchange rate in the liquidity provider's favor because 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
    USDL₁A₁10000true
    USDL₁A₁10true
    INRA₂L₂8242135false
    - +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 64d0398..55aa731 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 - + @@ -24,7 +24,7 @@ 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!

    - + \ No newline at end of file diff --git a/coding/recipes/rate-limiting/index.html b/coding/recipes/rate-limiting/index.html index 6b128b4..516b544 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 2edbb3e..23b7c93 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 25d3364..7e55103 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 4169279..666b981 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 3ca722b..8bd38e6 100644 --- a/coding/two-phase-transfers/index.html +++ b/coding/two-phase-transfers/index.html @@ -6,7 +6,7 @@ Two-Phase Transfers | TigerBeetle Docs - + @@ -52,7 +52,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 d3b2cfc..8f6de57 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 32d8345..d798ec1 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 612fb4c..856dac4 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 6081747..878d0d8 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 eb1227f..f18c3a1 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 70cd74e..f404d30 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 36fee94..8012690 100644 --- a/operating/upgrading/index.html +++ b/operating/upgrading/index.html @@ -6,7 +6,7 @@ Upgrading | TigerBeetle Docs - + @@ -35,9 +35,8 @@ TigerBeetle client:

    .NET

    dotnet add package tigerbeetle --version 0.15.4

    Go

    go mod edit -require github.com/tigerbeetle/tigerbeetle-go@v0.15.4

    Java

    Edit your pom.xml:

        <dependency>
    <groupId>com.tigerbeetle</groupId>
    <artifactId>tigerbeetle-java</artifactId>
    <version>0.15.4</version>
    </dependency>

    Node.js

    npm install tigerbeetle-node@0.15.4

    Troubleshooting

    Upgrading to a newer version with incompatible clients

    If a release of TigerBeetle no longer supports the client version you're using, it's still possible 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.

    Upgrading Windows / macOS

    Upgrading development data files on Windows / macOS is not yet supported. This will be added in the -next release.

    - +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 100907b..685ba0f 100644 --- a/quick-start/index.html +++ b/quick-start/index.html @@ -6,13 +6,13 @@ Quick Start | TigerBeetle Docs - +

    Quick Start

    This page will guide you through downloading TigerBeetle, setting up a single- or multi-node -cluster, and creating some accounts and transfers using the REPL.

    1. Download TigerBeetle

    TigerBeetle is a single, small, statically-linked binary.

    Latest Release

    # macOS
    curl -Lo tigerbeetle.zip https://mac.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version
    # Linux
    curl -Lo tigerbeetle.zip https://linux.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version
    # Windows
    powershell -command "curl.exe -Lo tigerbeetle.zip https://windows.tigerbeetle.com; Expand-Archive tigerbeetle.zip .; .\tigerbeetle version"

    Build from Source

    To build TigerBeetle from source, clone the repo, install Zig and run zig build:

    git clone https://github.com/tigerbeetle/tigerbeetle && cd tigerbeetle
    ./scripts/install_zig.sh # or .bat if you're on Windows.
    zig/zig build
    ./tigerbeetle version

    Notes:

    • Building from source is not recommended for production deployments.
    • If you build TigerBeetle from source, it is only compatible with clients that were also built from source.

    Direct Download

    You can download prebuilt binaries here:

    LinuxWindowsMacOS
    x86_64tigerbeetle-x86_64-linux.ziptigerbeetle-x86_64-windows.ziptigerbeetle-universal-macos.zip
    aarch64tigerbeetle-aarch64-linux.zipN/Atigerbeetle-universal-macos.zip

    Docker

    You can find instructions on using TigerBeetle with Docker here.

    2. Create the Data File

    Each TigerBeetle node uses a single data file to store its state. Create the data file using the +cluster, and creating some accounts and transfers using the REPL.

    1. Download TigerBeetle

    TigerBeetle is a single, small, statically-linked binary.

    Latest Release

    # macOS
    curl -Lo tigerbeetle.zip https://mac.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version
    # Linux
    curl -Lo tigerbeetle.zip https://linux.tigerbeetle.com && unzip tigerbeetle.zip && ./tigerbeetle version
    # Windows
    powershell -command "curl.exe -Lo tigerbeetle.zip https://windows.tigerbeetle.com; Expand-Archive tigerbeetle.zip .; .\tigerbeetle version"

    Build from Source

    To build TigerBeetle from source, clone the repo, install Zig and run zig build:

    git clone https://github.com/tigerbeetle/tigerbeetle && cd tigerbeetle
    ./zig/download.sh # .bat if you're on Windows.
    ./zig/zig build
    ./tigerbeetle version

    Notes:

    • Building from source is not recommended for production deployments.
    • If you build TigerBeetle from source, it is only compatible with clients that were also built from source.

    Direct Download

    You can download prebuilt binaries here:

    LinuxWindowsMacOS
    x86_64tigerbeetle-x86_64-linux.ziptigerbeetle-x86_64-windows.ziptigerbeetle-universal-macos.zip
    aarch64tigerbeetle-aarch64-linux.zipN/Atigerbeetle-universal-macos.zip

    Docker

    You can find instructions on using TigerBeetle with Docker here.

    2. Create the Data File

    Each TigerBeetle node uses a single data file to store its state. Create the data file using the format command:

    ./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 --development 0_0.tigerbeetle
    info(io): creating "0_0.tigerbeetle"...
    info(io): allocating 660.140625MiB...

    3. Start Your Cluster

    Now we'll run the TigerBeetle server.

    ./tigerbeetle start --addresses=3000 --development 0_0.tigerbeetle
    info(io): opening "0_0.tigerbeetle"...
    info(main): 0: cluster=0: listening on 127.0.0.1:3000

    4. Connect to the REPL

    Now that we have TigerBeetle running, we can connect to it via the REPL to create some accounts and transfers!

    ./tigerbeetle repl --cluster=0 --addresses=3000
    TigerBeetle Client
    Hit enter after a semicolon to run a command.

    Examples:
    create_accounts id=1 code=10 ledger=700 flags=linked | history,
    id=2 code=10 ledger=700;
    create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;
    lookup_accounts id=1;
    lookup_accounts id=1, id=2;

    5. Create Accounts

    In the REPL, create two accounts on the same ledger using the command:

    create_accounts id=1 code=10 ledger=700,
    id=2 code=10 ledger=700;
    info(message_bus): connected to replica 0

    6. Create a Transfer

    Now create a transfer of 10 (of some amount/currency) between the two accounts.

    create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;

    Now, the amount of 10 has been credited to account 2 and debited from account 1.

    7. Look Up Accounts

    Let's query TigerBeetle for these two accounts to verify the transfer we made!

    lookup_accounts id=1, id=2;
    {
    "id": "1",
    "user_data": "0",
    "ledger": "700",
    "code": "10",
    "flags": [],
    "debits_pending": "0",
    "debits_posted": "10",
    "credits_pending": "0",
    "credits_posted": "0"
    }
    {
    "id": "2",
    "user_data": "0",
    "ledger": "700",
    "code": "10",
    "flags": "",
    "debits_pending": "0",
    "debits_posted": "0",
    "credits_pending": "0",
    "credits_posted": "10"
    }

    And indeed you can see that account 1 has debits_posted as 10 and account 2 has credits_posted as 10. The 10 amount is fully accounted for!

    You can take a look at the Accounts reference to understand all of the @@ -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 2ecf89c..6f70c6f 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 54d7f57..5adb9e1 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 8513105..924e04e 100644 --- a/reference/account/index.html +++ b/reference/account/index.html @@ -6,7 +6,7 @@ Account | TigerBeetle Docs - + @@ -44,7 +44,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 bf6c860..20e48dd 100644 --- a/reference/query-filter/index.html +++ b/reference/query-filter/index.html @@ -6,7 +6,7 @@ QueryFilter | TigerBeetle Docs - + @@ -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 fe051e5..922b484 100644 --- a/reference/requests/create_accounts/index.html +++ b/reference/requests/create_accounts/index.html @@ -6,7 +6,7 @@ create_accounts | TigerBeetle Docs - + @@ -32,7 +32,7 @@ many applications should handle exists exactly as ok.

    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 c8e2895..be6efa9 100644 --- a/reference/requests/create_transfers/index.html +++ b/reference/requests/create_transfers/index.html @@ -6,7 +6,7 @@ create_transfers | TigerBeetle Docs - + @@ -107,7 +107,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 8cdaac5..b229cf9 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 6a9f61d..7c77995 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 f9edad2..04160cc 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 4cb8d7f..3153d69 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 56d93a7..9d6f6a8 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 f5540d7..8eb3e55 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 da15470..f782fcc 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 75a1546..c3fa785 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 8cffbd4..b9f9c5d 100644 --- a/reference/transfer/index.html +++ b/reference/transfer/index.html @@ -6,7 +6,7 @@ Transfer | TigerBeetle Docs - + @@ -94,7 +94,7 @@ Search for const Transfer = extern struct {.

    You can find the source code for creating a transfer in src/state_machine.zig. Search for fn create_transfer(.

    - + \ No newline at end of file diff --git a/search/index.html b/search/index.html index 6900006..45579bb 100644 --- a/search/index.html +++ b/search/index.html @@ -6,13 +6,13 @@ Search the documentation | TigerBeetle Docs - +

    Search the documentation

    - + \ No newline at end of file