To ensure correctness at scale, FRAGMENT's consistency mode lets you build guardrails without sacrificing performance.
You can configure consistency within your Schema to make granular tradeoffs between throughput and consistency.
You can configure the consistency of the Ledger Entries list query in your Ledger. To do this, set consistencyConfig at the top level of your Schema:
entries: eventual for Ledgers that require high throughput but can tolerate a stale entry listentries: strong for Ledgers that have lower throughput but require strong consistency, such as those powering reconcilation dashboards{
"consistencyConfig": {
"entries": "strong"
},
"chartOfAccounts": [...]
}By default, all Ledgers use eventual consistency.
You can configure the consistency of balances, as well as the Ledger Lines list query, in your Ledger Account.
To configure an account's balance consistency, set consistencyConfig.totalBalanceUpdates within a Ledger Account's definition. totalBalanceUpdates controls the consistency with which both the selfTotal, childTotal and total balance components of the account are updated. When totalBalanceUpdates is set to strong, any entry posted to the account or any of its children will update the account's balance immediately.
Options:
totalBalanceUpdates: eventual for Ledger Accounts that require high throughput but can tolerate stale balances, such as those used for reportingtotalBalanceUpdates: strong for Ledger Accounts that have lower throughput but require strong consistency, such as those used to authorize transactionsSimilarly, to configure the consistency of an account's lines, set consistencyConfig.lines:
lines: eventual for Ledger Accounts that require high throughput but can tolerate a stale line listlines: strong for Ledger Accounts that have lower throughput but require strong consistency, such as those powering transaction histories displayed to end users{
"accounts": [
{
"key": "user-balance",
"template": true,
"type": "asset",
"consistencyConfig": {
"totalBalanceUpdates": "strong",
"lines": "eventual"
}
}
]
}By default, all Ledger Accounts use eventual for both properties.
For low-throughput applications, setting all Ledger Accounts as strong may make implementation easier. To do this, set defaultConsistencyConfig on chartOfAccounts:
{
"chartOfAccounts": {
"defaultConsistencyConfig": {
"totalBalanceUpdates": "strong",
"lines": "strong"
},
"accounts": [...]
}
}Strongly consistent Ledger Accounts generally won't have children, but in all cases child Ledger Accounts inherit the parent's consistencyConfig setting.
To query a strongly consistent balance, set consistencyMode to strong when querying the Ledger Account:
query GetBalances(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
balance(consistencyMode: strong)
balances(consistencyMode: strong) {
nodes {
amount
currency {
code
}
}
}
}
}By default, balance queries on all Ledger Accounts are eventually consistent.
Restrictions:
at with consistencyMode to query strongly consistent historical balancesEntry conditions are rules defined in your Schema to manage concurrency and enforce correctness within your Ledger.
Conditions are evaluated when a Ledger Entry is posted. If a condition is not met, the Ledger Entry is not posted and the mutation returns a BadRequestError with code conditional_request_failed.
Use precondition when your application reads a balance and needs to guarantee that it hasn't changed before posting the Ledger Entry.
{
"type": "pay-employee",
"lines": [...],
"conditions": [
{
"account": {
"path": "bank-account"
},
"precondition": {
"totalBalance": {
"eq": "{{current_balance}}"
}
}
}
]
}Use postcondition to guarantee that a write never puts a Ledger Account's balance in an undesirable state.
{
"type": "pay-employee",
"lines": [...],
"conditions": [
{
"account": {
"path": "bank-account"
},
"postcondition": {
"totalBalance": {
"gte": "0"
}
}
}
]
}Restrictions:
totalBalance apply to the account's total balance, which includes both the account's own lines and its children's lines.consistencyConfig.totalBalanceUpdates set to strongWhen using repeated Lines, conditions can be repeated using the same repeated key. This lets you enforce a condition on a variable set of accounts:
{
"type": "batch_transfer",
"lines": [
{
"key": "from",
"account": { "path": "liabilities/users:{{from_user}}/available" },
"amount": "-{{amount}}",
"repeated": { "key": "transfers" }
},
{
"key": "to",
"account": { "path": "liabilities/users:{{to_user}}/available" },
"amount": "{{amount}}",
"repeated": { "key": "transfers" }
}
],
"conditions": [
{
"account": { "path": "liabilities/users:{{from_user}}/available" },
"postcondition": { "totalBalance": { "gte": "0" } },
"currency": { "code": "USD" },
"repeated": { "key": "transfers" }
}
]
}Each element in the transfers array generates both the transfer lines and its own balance condition.
You can configure an account to have a strongly-consistent ownBalance for specific group keys.
To do this, use the consistencyConfig.groups configuration in the Schema.
{
"accounts": [
{
"key": "user-balance",
"template": true,
"type": "asset",
"consistencyConfig": {
"groups": [
{
"key": "invoice_id",
"ownBalanceUpdates": "strong"
}
]
}
}
]
}See Group Ledger Entries for more information about how to use Ledger Entry Groups.
To query a strongly consistent Ledger Entry Group balance, set consistencyMode to strong or use_account when querying the group's balances.
query ListLedgerEntryGroupBalances(
$ledger: LedgerMatchInput!
$groupKey: SafeString!
$groupValue: SafeString!
$consistencyMode: ReadBalanceConsistencyMode
) {
ledgerEntryGroup(ledgerEntryGroup: {
ledger: $ledger,
key: $groupKey,
value: $groupValue,
}) {
balances {
nodes {
account {
path
}
ownBalance(consistencyMode: $consistencyMode)
}
}
}
}By default, Ledger Entry Group balances are eventually consistent. Like other consistency options, enabling strong consistency will reduce the maximum throughput of an account.
Restrictions:
ownBalanceUpdates is the legacy way of setting account-level consistency. It only controls the consistency of the account's own balance (excluding children) and defaults all other balance types to eventual. A Ledger Account cannot have both totalBalanceUpdates and ownBalanceUpdates set.
Schemas using ownBalanceUpdates can only use ownBalance conditions. These conditions only apply to the account's own balance and require consistencyConfig.ownBalanceUpdates set to strong.
Any examples or documentation on this page that use totalBalanceUpdates or totalBalance conditions can be adapted for a legacy ownBalanceUpdates Schema by replacing:
totalBalanceUpdates with ownBalanceUpdates in your consistency configtotalBalance with ownBalance in your entry conditions