Cleanup ai slope.

This commit is contained in:
Ahmed Yasin Koculu
2026-10-25 21:58:19 +01:00
parent 7dc1e1b20c
commit f6de16af29
25 changed files with 48 additions and 48 deletions
@@ -23,7 +23,7 @@ layout:
aria-labelledby="benchmark-methodology-title"
>
<header class="zt-methodology-heading">
<p class="zt-methodology-eyebrow">Understand the numbers</p>
<p class="zt-methodology-eyebrow">Benchmark context</p>
<h2 id="benchmark-methodology-title">Methodology and reproduction</h2>
<p>
One indexed profile-store workload, measured across four database engines.
@@ -91,7 +91,7 @@ With the default capacity of 1,000,000 records, the bit arrays occupy:
These figures exclude object and array overhead. Power-of-two rounding creates steps: a small setting change can double the allocation. At the maximum-size cap, a very large configured segment can receive fewer effective bits per item than requested.
## Understand false positives
## False positives
A false positive sends a missing key into a B+Tree lookup that eventually fails. It costs work; it does not create a record or change key order.
+3 -3
View File
@@ -9,7 +9,7 @@ layout:
---
<div class="zt-docs-overview">
<SectionHeader class="zt-guide-intro" eyebrow="CONCEPTS / BUILD THE MENTAL MODEL" title="Understand the contracts behind the APIs" titleTag="h1" titleId="concepts-title" subtitle="Connect the LSM storage model with key order, serialization, filtering, deletion, write sequencing, and value ownership."/>
<SectionHeader class="zt-guide-intro" eyebrow="CONCEPTS / BUILD THE MENTAL MODEL" title="Storage model and API contracts" titleTag="h1" titleId="concepts-title" subtitle="Connect the LSM storage model with key order, serialization, filtering, deletion, write sequencing, and value ownership."/>
<Grid columns="1" columnsMd="3" class="gap-3 zt-guide-outcomes">
<Panel class="p-0" bodyClass="p-4"><strong>Storage model</strong><p>Layers describe record generations; parts divide a logical disk segment into ranges.</p></Panel>
@@ -22,9 +22,9 @@ layout:
<DocCard number="02 / STORAGE UNITS" title="Segments and parts" summary="Distinguish memory and disk layers, logical segments, reusable parts, and the counters that describe them." href="/docs/concepts/segments/" icon="tabler:database" label="Name the storage units"/>
<DocCard number="03 / QUERY SHAPE" title="Key ordering" summary="Put related records into contiguous comparer order and implement a complete composite-key example." href="/docs/concepts/key-ordering/" icon="tabler:arrows-sort" label="Design the keyspace"/>
<DocCard number="04 / DATA CONTRACTS" title="Serializers and comparers" summary="Separate equality, hashing, and persisted bytes. Configure custom components and preserve compatibility." href="/docs/concepts/serializers-and-comparers/" icon="tabler:code" label="Define the contracts"/>
<DocCard number="05 / POINT READS" title="Bloom filters" summary="Skip provably absent memory-segment lookups and understand filter allocation, hashing, and false positives." href="/docs/concepts/bloom-filters/" icon="tabler:settings" label="Understand the filter"/>
<DocCard number="05 / POINT READS" title="Bloom filters" summary="Skip provably absent memory-segment lookups and understand filter allocation, hashing, and false positives." href="/docs/concepts/bloom-filters/" icon="tabler:settings" label="How the filter works"/>
<DocCard number="06 / VISIBILITY" title="Deletion markers and TTL" summary="Choose tombstones, model expiration, and reclaim deleted storage without exposing older values." href="/docs/concepts/deletion-markers-and-ttl/" icon="tabler:shield-check" label="Define what is live"/>
<DocCard number="07 / WRITE IDENTITY" title="Operation indexes" summary="Use a producer's write sequence without confusing it with durable delivery, global ordering, or transaction commits." href="/docs/concepts/op-index/" icon="tabler:git-branch" label="Understand write sequencing"/>
<DocCard number="07 / WRITE IDENTITY" title="Operation indexes" summary="Use a producer's write sequence without confusing it with durable delivery, global ordering, or transaction commits." href="/docs/concepts/op-index/" icon="tabler:git-branch" label="Write sequencing and replay"/>
<DocCard number="08 / OWNERSHIP" title="Value mutability" summary="Keep stored objects and buffers read-only, create replacements, and make cancellation leave no shared mutation." href="/docs/concepts/value-mutability/" icon="tabler:book-2" label="Own stored values safely"/>
</Grid>
@@ -94,7 +94,7 @@ For example, a comparer that ignores case makes `"User:42"` and `"USER:42"` the
Do not let a key's order depend on mutable fields, the current time, or configuration that can change while the tree is open. Avoid mutating key buffers after insertion. A key that changes position without an engine write breaks the assumptions used by lookup, iteration, sparse indexes, and merging.
## Understand strings and byte sequences
## Default ordering for common key types
| Representation | Default order | Consequence |
| --- | --- | --- |
@@ -105,7 +105,7 @@ Do not let a key's order depend on mutable fields, the current time, or configur
For text keys with embedded numbers, use an appropriate encoding or comparer; a delimiter alone does not create numeric sorting. For byte keys, make sure the byte encoding and the comparer jointly produce the intended order. Use `Memory<byte>` rather than unsupported `byte[]` keys, and retain ownership of the bytes.
## Choose forward or reverse access
## Forward and reverse traversal
A forward iterator follows the configured comparer. A reverse iterator traverses that same order backward. Reverse traversal does not change equality or re-encode the stored data.
@@ -42,7 +42,7 @@ With concurrent writers, sequence assignment and method return order are differe
Failed existence checks and cancelled atomic callbacks commonly report operation index `0`. That sentinel is not a new successful write. See [atomic operations](../usage/atomic-operations.mdx) for the specific return contracts.
## Understand the recovery boundary
## Operation indexes after recovery
WAL records retain their original operation indexes. Reopen combines recovered WAL information with persisted high-water metadata so later allocations continue above the recovered sequence.
@@ -43,7 +43,7 @@ The disk abstraction is `IDiskSegment<TKey, TValue>`. Both single-part and multi
<p>Layers separate newer and older storage generations. Parts divide one logical disk segment into ordered ranges. A multipart segment still occupies one disk-segment slot or one position in the bottom queue.</p>
</AlertBox>
## Understand why part reuse matters
## Immutable parts and reuse
<LsmPartReuse/>
@@ -98,7 +98,7 @@ This prints `Ada`, then `False`. JSON is an explicit format choice here; the eng
The serializer can be called from concurrent application, recovery, or maintenance work. Keep it safe for concurrent use. Do not retain or mutate input buffers without owning a copy, and do not return a reusable scratch buffer that another operation can overwrite.
## Choose a stable byte representation
## Serializer format contracts
| Choice | Contract to preserve |
| --- | --- |
@@ -109,7 +109,7 @@ The serializer can be called from concurrent application, recovery, or maintenan
Changing a serializer's output changes what WAL recovery, disk reads, and backup records need to decode. Test a value written by the previous application version with the new reader before treating a deployment as compatible.
## Understand what metadata validates
## Metadata validation and compatibility
The loader checks the persisted key type, value type, comparer type, key-serializer type, and value-serializer type. Metadata also records the configured hasher type and Bloom density, but those are not the same persisted-format checks.
@@ -98,7 +98,7 @@ Returning `false` cancels the new engine write. It cannot undo a change already
`TryAtomicAddOrUpdate` can retry its adder/updater when a segment freezes or fills. Build replacement values from the input of each invocation, and keep callbacks free of external effects that cannot safely repeat. An in-place increment of a shared object can be applied more than once before one write is accepted.
## Understand shallow immutability
## Shallow immutability
Records, `readonly` fields, read-only interfaces, and readonly structs do not automatically make their contents immutable:
@@ -11,7 +11,7 @@ description: Create and verify complete live-backup generations, choose memory c
titleId="backups-title"
subtitle="Create a complete live-backup generation while the tree remains open, verify a restore, and configure automatic generation and retention policies."/>
## Choose a backup shape
## Live backup, offline copy, and rebuild
| Approach | What it captures | When it fits |
| --- | --- | --- |
@@ -81,7 +81,7 @@ Restore into an unused data directory and, if configured separately, an unused W
Use the local provider, a WAL-enabled target, and compatible [data contracts](recovery.mdx#preserve-the-data-contracts). Restore copies disk files, rebuilds backed-up memory records into a read-only WAL, and creates fresh mutable WAL and metadata files. It does not need the original production WALs.
## Choose the in-memory view
## In-memory capture modes
Memory records are included by default, including deletion markers needed to hide older records in copied disk files. Their batch is compressed independently of disk files, with Zstd level 0 and 1 MiB blocks by default.
@@ -138,7 +138,7 @@ await backup.CreateGenerationAsync();
Console.WriteLine($"Final completed generation: {backup.CurrentGenerationId}");
```
The final-generation sequence stops automatic triggers and waits for active backup work before making the manual request. See [request and completion behavior](#understand-requests-and-completion) below for the individual methods.
The final-generation sequence stops automatic triggers and waits for active backup work before making the manual request. See [request and completion behavior](#backup-requests-and-completion) below for the individual methods.
### Schedule choices
@@ -151,7 +151,7 @@ The final-generation sequence stops automatic triggers and waits for active back
`BackupAfterMerge` defaults to `true`. Successful **normal** merges request a generation after `Start()`; bottom-segment merges do not. A schedule remains useful when writes stay in memory without enough activity to trigger a normal merge.
## Understand requests and completion
## Backup requests and completion
Each coordinator runs at most one generation at a time. Requests do not form a backlog:
@@ -9,10 +9,10 @@ layout:
---
<div class="zt-docs-overview">
<SectionHeader class="zt-guide-intro" eyebrow="DURABILITY / RECOVER AND RESTORE" title="Understand logging, diagnostics, and restore" titleTag="h1" titleId="durability-title" subtitle="Choose a WAL policy, capture storage diagnostics, or restore a backup generation. The recovery guide covers the public APIs available for specialized inspection and recovery tools."/>
<SectionHeader class="zt-guide-intro" eyebrow="DURABILITY / RECOVER AND RESTORE" title="Write-ahead logging, recovery, and backups" titleTag="h1" titleId="durability-title" subtitle="Choose a WAL policy, capture storage diagnostics, or restore a backup generation. The recovery guide covers the public APIs available for specialized inspection and recovery tools."/>
<Grid columns="1" columnsMd="3" class="gap-4 zt-guide-next zt-docs-paths">
<DocCard number="01 / LOG RECENT WRITES" title="WAL modes" summary="Choose a logging path, understand compressed tails, and close the tree cleanly. AsyncCompressed is the default." href="/docs/durability/wal-modes/" icon="tabler:shield-check" label="Understand the default WAL"/>
<DocCard number="01 / LOG RECENT WRITES" title="WAL modes" summary="Choose a logging path, understand compressed tails, and close the tree cleanly. AsyncCompressed is the default." href="/docs/durability/wal-modes/" icon="tabler:shield-check" label="Default WAL behavior"/>
<DocCard number="02 / INSPECT AND RECOVER" title="Recovery" summary="Capture checksum and reader errors, integrate logging, and reuse public WAL and segment APIs in specialized tools." href="/docs/durability/recovery/" icon="tabler:database" label="Inspect WAL diagnostics"/>
<DocCard number="03 / RESTORE A GENERATION" title="Backups" summary="Run a complete backup and restore example, then add memory capture policy, scheduling, and retention." href="/docs/durability/backups/" icon="tabler:stack-2" label="Create a verified backup"/>
</Grid>
@@ -5,7 +5,7 @@ description: Understand automatic incomplete-tail recovery, capture WAL corrupti
<SectionHeader class="zt-guide-intro" eyebrow="DURABILITY / DIAGNOSTICS AND TOOLS" title="Inspect WALs and build recovery tools" titleTag="h1" titleId="recovery-title" subtitle="ZoneTree handles incomplete WAL tails during startup. For checksum and other read errors, logging and public storage APIs support diagnosis and specialized recovery tools."/>
## Understand what detection provides
## WAL corruption diagnostics
Current-format WAL records carry checksums. A mismatch is reported as a read failure; it does not repair the record or establish which bytes should replace it. Reconstructing records that fail checksum validation requires a specialized tool and a policy for deciding which records can be trusted.
@@ -114,7 +114,7 @@ Use the original key/value types, serializers, comparer, compatible hasher, and
See [serializers and comparers](../concepts/serializers-and-comparers.mdx) and [deletion markers and TTL](../concepts/deletion-markers-and-ttl.mdx) for those contracts.
## Choose a recovery procedure
## Inspection and reconstruction workflow
1. Preserve the affected database and any separately configured WAL location, along with the diagnostic logs. Use a separate working copy for inspection.
2. Identify the affected segment, record failures, and file format with the public readers and metadata APIs.
@@ -46,7 +46,7 @@ For codec defaults and block-size controls, see [compression](../storage/compres
Existing WAL files keep their detected logging mode and recorded compression format. Changing the factory configures newly created WALs; reopening an existing file does not convert it to another mode. The built-in provider detects the existing mode from its filename, so preserve WAL filenames when moving a database.
## Understand the compressed tail
## Compressed blocks and the tail file
Compressed WALs have a main file of committed blocks and a companion `.tail` file for the unfinished block. Keep both when making an offline copy.
@@ -132,7 +132,7 @@ Run `dotnet run` from the same project directory. It prints `Persisted 20: Grace
<AlertBox
class="zt-guide-callout"
tone="info"
title="Choose durability before relying on crash recovery"
title="Persistence and crash recovery"
icon="tabler:shield-check"
>
<p>
@@ -143,7 +143,7 @@ Run `dotnet run` from the same project directory. It prints `Persisted 20: Grace
</p>
</AlertBox>
## Choose what to learn next
## Further reading
Continue with one of these guides, or [browse the Usage overview](usage/index.mdx) to compare reads, writes, atomic operations, transactions, scans, and maintenance.
@@ -157,7 +157,7 @@ Continue with one of these guides, or [browse the Usage overview](usage/index.md
label="Opening a tree"/>
<DocCard
number="NEXT / DATA OPERATIONS"
title="Choose the right API"
title="Read and write APIs"
summary="Match simple writes, conditional changes, single-key updates, and transactions to your data rules."
href="/docs/usage/reads-and-writes/"
icon="tabler:code"
+7 -7
View File
@@ -92,7 +92,7 @@ if (tree.TryGet(42, out var value))
Keep the maintainer alive with the tree to schedule background merges
and clean inactive caches.
<a href="/docs/usage/maintenance/"
>Understand maintenance <Icon name="tabler:arrow-right"/></a>
>Background maintenance <Icon name="tabler:arrow-right"/></a>
</p>
</Panel>
</Grid>
@@ -117,7 +117,7 @@ if (tree.TryGet(42, out var value))
summary="See how mutable memory, read-only segments, compaction, and disk parts work together. Use that model to understand writes, reads, and maintenance."
href="/docs/concepts/lsm-tree/"
icon="tabler:stack-2"
label="Understand the architecture"/>
label="LSM-tree architecture"/>
<DocCard
number="03 / PRODUCTION"
title="Make the operational choices"
@@ -176,7 +176,7 @@ if (tree.TryGet(42, out var value))
<ul class="list-none p-0 m-0">
<li>
<a href="/docs/concepts/key-ordering/"
>Choose key order and prefix layouts</a>
>Key ordering and prefix layouts</a>
</li>
<li>
<a href="/docs/usage/iteration-and-range-scans/"
@@ -206,7 +206,7 @@ if (tree.TryGet(42, out var value))
<ul class="list-none p-0 m-0">
<li>
<a href="/docs/durability/wal-modes/"
>Choose a write-ahead logging mode</a>
>Write-ahead logging modes</a>
</li>
<li>
<a href="/docs/durability/recovery/"
@@ -228,12 +228,12 @@ if (tree.TryGet(42, out var value))
aria-labelledby="docs-performance-title"
>
<Icon name="tabler:database" class="zt-docs-topic-icon"/>
<h3 id="docs-performance-title">Understand storage costs</h3>
<h3 id="docs-performance-title">Storage costs</h3>
<p>Work out what drives disk reads, rewrite work, and memory use.</p>
<ul class="list-none p-0 m-0">
<li>
<a href="/docs/storage/disk-segments/"
>Understand disk-segment layouts</a>
>Disk-segment layouts</a>
</li>
<li>
<a href="/docs/tuning/write-amplification/"
@@ -247,7 +247,7 @@ if (tree.TryGet(42, out var value))
<a href="/docs/storage/memory-usage/">Account for memory usage</a>
</li>
<li>
<a href="/docs/storage/compression/">Choose compression settings</a>
<a href="/docs/storage/compression/">Compression settings</a>
</li>
</ul>
</Panel>
@@ -25,7 +25,7 @@ Both paths expose a compression method, method-specific level, and block size, b
Changing compression options does not rewrite existing files. A merge may write new files with the new profile while reusing eligible parts with the earlier profile. Multipart descriptors have their own metadata encoding; disk data-block settings do not configure that encoding.
<AlertBox class="zt-guide-callout" tone="info" title="Choose recovery guarantees through the WAL mode" icon="tabler:shield-check">
<AlertBox class="zt-guide-callout" tone="info" title="WAL mode and durability" icon="tabler:shield-check">
<p>A compression method or level does not define when a write is durable. <code>AsyncCompressed</code>, <code>SyncCompressed</code>, plain <code>Sync</code>, and <code>None</code> have different logging and failure behavior. Choose the mode with the <a href="/docs/durability/wal-modes/">WAL modes guide</a>, then tune compression within that choice.</p>
</AlertBox>
@@ -83,7 +83,7 @@ Console.WriteLine($"Read {value.Length} characters after merging.");
The two methods are intentionally independent; this is an example profile, not a universal performance recommendation. `SetDiskSegmentCompressionBlockSize()` is a factory shorthand for the disk block-size option.
## Understand the unit of a disk read
## Block reads and caching
Disk files contain individually addressable blocks. A cold read locates and reads the relevant compressed block, decompresses it, and serves the requested bytes. Subsequent reads can reuse the decompressed block while it remains cached.
@@ -40,7 +40,7 @@ An illustrative multipart segment with two variable-value parts looks like this:
These are segment files. Metadata and any record WALs belong to the wider database layout described in the [Storage overview](index.mdx).
## Understand the four record layouts
## Fixed-size and variable-size record layouts
Each physical single-part segment selects a layout from the runtime key and value types. Types with no managed references use the fixed-size path; strings, arrays, and types containing references use the variable-size path.
@@ -113,7 +113,7 @@ File IDs depend on the tree's history. An empty current disk slot may enumerate
<p><code>GetFiles()</code> describes a segment; it does not freeze the tree or acquire a backup lease. During concurrent maintenance, use the live-backup API to coordinate segment lifetimes and capture in-memory records and restore metadata.</p>
</AlertBox>
## Understand when files disappear
## File lifetime after a merge
Merges publish replacement segments rather than rewriting the record data of an active segment. Once a segment is retired, its files can be reclaimed when no reader or backup still leases them. Sparse-index files are auxiliary persisted indexes, separate from that immutable record data.
@@ -6,7 +6,7 @@ description: Choose local or RAM-backed files, reopen an in-memory tree correctl
<SectionHeader
class="zt-guide-intro"
eyebrow="STORAGE / BACKING STORE"
title="Choose where ZoneTree's files live"
title="Local, in-memory, and custom file providers"
titleTag="h1"
titleId="file-stream-providers-title"
subtitle="The storage engine works through file and stream abstractions. Keep the same ordered API while choosing local persistence, RAM-backed files, or an application-specific provider."/>
+1 -1
View File
@@ -9,7 +9,7 @@ layout:
---
<div class="zt-docs-overview">
<SectionHeader class="zt-guide-intro" eyebrow="STORAGE / FROM RECORDS TO RESOURCES" title="Understand where your data lives and what it costs" titleTag="h1" titleId="storage-title" subtitle="Connect the logical tree to its physical files, backing store, and memory footprint. Then choose compression and cache settings with a clear view of their scope."/>
<SectionHeader class="zt-guide-intro" eyebrow="STORAGE / FROM RECORDS TO RESOURCES" title="Physical storage and resource use" titleTag="h1" titleId="storage-title" subtitle="Connect the logical tree to its physical files, backing store, and memory footprint. Then choose compression and cache settings with a clear view of their scope."/>
<Grid columns="1" columnsMd="3" class="gap-3 zt-guide-outcomes">
<Panel class="p-0" bodyClass="p-4"><strong>Representation</strong><p>Sorted records become block-addressable files, indexes, and reusable parts.</p></Panel>
@@ -17,7 +17,7 @@ All three are record counts, not byte budgets. The disk threshold is checked aft
For terminology and file ownership, see [segments and parts](../concepts/segments.mdx) and [disk segment files](../storage/disk-segments.mdx).
## Choose a segment mode and target range
## Segment modes and part targets
`MultiPartDiskSegment` is the default. It represents one logical segment with ordered physical parts and allows eligible parts to be reused. `SingleDiskSegment` uses a single-part layout; it still can have separate data, header, and sparse-index files.
@@ -79,7 +79,7 @@ Console.WriteLine($"Physical files: {disk.GetFiles().Length}");
The small dataset produces a final part below the configured minimum, which is a normal outcome. With concurrent writers, coordinate the observation before comparing file sets or merge outputs.
## Choose block size and sparse density for reads
## Block size and sparse-index density
Disk compression defaults to Zstd level 0 with 4 MiB blocks. A smaller block can reduce unrelated bytes decompressed for isolated reads, while a larger block can improve locality and compression. A value larger than one block still needs all of its payload bytes. Existing files retain their recorded block size and method; a configuration change alone does not rewrite them. See [compression](../storage/compression.mdx) for a controlled comparison.
+1 -1
View File
@@ -14,7 +14,7 @@ layout:
<Grid columns="1" columnsMd="2" class="gap-4 zt-guide-next zt-docs-paths">
<DocCard number="01 / READ WORK" title="Read-path caching" summary="Separate block retention, deserialized-record caches, sequential lookup hints, and scan prefetch." href="/docs/tuning/read-path-caching/" icon="tabler:settings" label="Match caches to reads"/>
<DocCard number="02 / MERGE WORK" title="Write amplification" summary="Understand reuse eligibility and observe unchanged data files across a localized update." href="/docs/tuning/write-amplification/" icon="tabler:git-branch" label="Measure rewrite work"/>
<DocCard number="03 / FILE SHAPE" title="Disk segment tuning" summary="Distinguish writable capacity, disk-layer rollover, part targets, block size, and sparse density." href="/docs/tuning/disk-segments/" icon="tabler:database" label="Choose the right boundary"/>
<DocCard number="03 / FILE SHAPE" title="Disk segment tuning" summary="Distinguish writable capacity, disk-layer rollover, part targets, block size, and sparse density." href="/docs/tuning/disk-segments/" icon="tabler:database" label="Segment and part sizing"/>
<DocCard number="04 / PAYLOAD COST" title="Large values" summary="Translate record counts into bytes, evaluate value ownership, and compare direct payloads with references." href="/docs/tuning/large-values/" icon="tabler:stack-2" label="Budget payload work"/>
</Grid>
@@ -5,7 +5,7 @@ description: Translate record limits into payload costs, choose direct values or
<SectionHeader class="zt-guide-intro" eyebrow="TUNING / PAYLOAD SIZE" title="Budget large values in bytes and work" titleTag="h1" titleId="large-values-title" subtitle="Compare direct payloads, references, and split records. Translate their record counts into retained memory, serialized bytes, and merge I/O."/>
## Choose what belongs in the value
## Payload storage models
| Model | Useful when | Responsibility to account for |
| --- | --- | --- |
@@ -11,7 +11,7 @@ Write amplification compares internal bytes written with the application's logic
Record overwrite distribution as well as volume. Ten thousand updates concentrated in one range can produce different merge work from the same count spread across the whole keyspace.
## Understand when a part can be reused
## Part reuse conditions
<LsmPartReuse/>
@@ -65,7 +65,7 @@ Console.WriteLine($"New data files: {after.Except(before).Count()}");
Application writes are coordinated in this example, and both merges finish before inspection. The file counts vary with randomized boundaries. Shared filenames demonstrate reuse; use I/O measurements for the actual bytes written. The part targets here make the mechanism observable, rather than prescribe a production profile.
## Choose the lever that matches the pressure
## Rewrite costs and tradeoffs
| Observation | Experiment | Tradeoff to watch |
| --- | --- | --- |
@@ -53,7 +53,7 @@ tree.TryAtomicAddOrUpdate(
Run it again against the same directory to increment the persisted counter. The callback reports `Added` on initialization and `Updated` on a later increment.
## Understand the coordination boundary
## Coordination across storage layers
The newest value may be in mutable memory, a read-only segment, or a disk layer. Atomic methods synchronize this sequence across those layers:
@@ -36,7 +36,7 @@ if (tree.TryDelete(7, out var deletedAt))
For setup and resource lifetime, see [opening a tree](opening-a-tree.mdx).
## Choose an operation
## Read and write APIs
| Your data rule | API | Important detail |
| --- | --- | --- |
@@ -124,7 +124,7 @@ Use [transactions](transactions.mdx) when the invariant spans several keys in on
Different key groups can use different write disciplines. For example, profile replacement can use `Upsert` while a separate counter uses atomic methods. Keep the discipline consistent for every writer that can change the same invariant.
<Panel class="p-0 zt-guide-next-panel" bodyClass="p-5">
<strong>Choose the next operation</strong>
<strong>Range scans and coordinated updates</strong>
<p>
Read a range, update one key based on its current value, or coordinate a
group of keys.
@@ -58,7 +58,7 @@ catch (TransactionAbortedException)
`OpenOrCreateTransactional()` returns the transactional API. It differs from the ordinary tree: reads and writes in a transaction take a transaction ID, and success depends on the commit outcome.
## Choose the smallest coordination scope
## Single-key and multi-key coordination
| Data rule | API |
| --- | --- |
@@ -68,7 +68,7 @@ catch (TransactionAbortedException)
Concurrent application threads alone do not require transactions. Use them for a multi-key invariant, and make the reads that decide the writes part of that transaction.
## Understand the transaction lifecycle
## Transaction lifecycle
1. `BeginTransaction()` allocates a transaction ID.
2. Reads and writes use that ID.