Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
63bd54a
Document declarative slot residency and conversion
kriszyp Oct 23, 2026
bc09e25
docs: state application branch residency limitation
kriszyp Oct 23, 2026
6285d39
docs: make conversion steps and example independently readable
kriszyp Oct 23, 2026
b9326e4
docs: limit online residency adoption to key policies
kriszyp Oct 23, 2026
6eef513
docs: clarify shard agreement for legacy wire fallback
kriszyp Oct 23, 2026
a3b021b
docs: require replica drain before residency downgrade
kriszyp Oct 23, 2026
eb21665
docs: state the slot-map precedence over replicateTo
kriszyp Oct 24, 2026
af6170e
Complete residency references and verified rollback procedure
kriszyp Oct 24, 2026
e7aac2f
docs: clarify initialization and conversion boundaries
kriszyp Oct 24, 2026
fe2ab73
docs: clarify residency prerequisites and confirmation behavior
kriszyp Oct 24, 2026
5a3c6fd
docs: clarify confirmation failures and unknown slot deletion
kriszyp Oct 24, 2026
474e8d7
Clarify single-key reads and unknown-slot deletion guidance
kriszyp Oct 24, 2026
afd1354
Document residency quarantine, writer replicas and confirmation timeouts
kriszyp Oct 25, 2026
94dd035
Connect replica lifetime settings to declarative writer caches
kriszyp Oct 25, 2026
c568f5f
Clarify quarantined declarations and audit blob retention costs
kriszyp Oct 25, 2026
6fbcc0c
Clarify residency activation and transaction confirmation examples
kriszyp Oct 25, 2026
99202a8
Link the subscription projection limitation directly
kriszyp Oct 25, 2026
d196d2c
docs: link confirmation outcomes directly
kriszyp Oct 25, 2026
8edb11a
docs: clarify new-owner confirmation scope
kriszyp Oct 25, 2026
7a2a361
Document database-wide sharding and regional slot expressions
kriszyp Oct 25, 2026
d287cce
docs: define sharded recovery and existing-owner admission
kriszyp Oct 26, 2026
cb94b25
docs: preserve ordinary writes during sharding root recovery
kriszyp Oct 26, 2026
13df85f
docs: clarify sharding placement and recovery boundaries
kriszyp Oct 26, 2026
afdbff6
docs: explain local-only slot transition refusal
kriszyp Oct 27, 2026
a266db2
Clarify local-only writer replica retention
kriszyp Oct 27, 2026
dfdaa20
Document the attribute-slot retry gap and marker-gated refusal
kriszyp Oct 27, 2026
4094d27
Clarify the binding affected by the attribute-slot retry gap
kriszyp Oct 27, 2026
21c2f99
docs: describe the 5.4 abort-marker default and minimal retry fallback
kriszyp Oct 28, 2026
9c36871
Document primary-key sharding and defer concurrent attribute moves
kriszyp Oct 28, 2026
6c4312c
Remove leftover attribute admission prose and clarify key conversion …
kriszyp Oct 28, 2026
0aba8cf
Point confirmation-failure guidance at the single explanation
kriszyp Nov 2, 2026
cfdf7b2
Correct what the rowFilter warnings say a subscriber can learn
kriszyp Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions reference/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,17 @@ replication:
- `securePort` β€” Secure replication port; _Default_: `9933` (changed from `9925` in v4.5.0)
- `threads` β€” Worker threads dedicated to replication, which then own the replication port and every subscription and load no application code; requires `port` or `securePort`; see [Dedicated Replication Threads](../replication/overview.md#dedicated-replication-threads); _Default_: `0` (replication runs on the HTTP worker threads) (Added in: v5.4.0)
- `enableRootCAs` β€” Verify against Node.js Mozilla CA store; _Default_: `true`
- `pingInterval` β€” Interval between replication keep-alive pings (ms); _Default_: `30000`
- `pingTimeout` β€” Time without received socket activity before a replication connection is considered dead (ms); _Default_: twice `pingInterval`.
- `confirmationTimeout` β€” Timeout in milliseconds for explicit `replicatedConfirmation`, read when a waiter starts. _Default_: `900000`, or `blobTimeout` when greater. An explicitly configured value overrides that fallback, including a smaller value. Range: `1`–`2147483647`. (Added in: v5.4.0)
- `blobTimeout` β€” Blob transfer timeout (ms); _Default_: `120000`
- `blobGapReconnectMs` β€” Interval (ms) for the blob-gap watchdog: when a transient blob save failure pins a replication resume cursor, the connection is forced to reconnect on this cadence so the gapped blob is re-streamed and, during a bulk copy, the copy resumes from the last banked cursor. Lower values heal gaps faster at the cost of more reconnects on a link whose faults never heal; _Default_: the `blobTimeout` value
- `copyCursorFlushBytes` β€” Bytes of applied bulk-copy data between durable flushes of the copy resume cursor (RocksDB); _Default_: `67108864`
- `copyCursorFlushIntervalMs` β€” Maximum time (ms) between durable flushes of the bulk-copy resume cursor (RocksDB); _Default_: `5000`
- `blobSendDrainTimeout` β€” Maximum time (ms) a worker waits for in-flight replication blob **sends** to finish before shutting down during a restart, so a rolling restart (e.g., a component deploy reload) doesn't interrupt a transfer in progress. Only sends that are still making progress are waited on; `0` disables draining; _Default_: `600000`
- `failOver` β€” Failover to alternate node if peer unreachable; _Default_: `true`
- `shard` β€” Shard ID for traffic routing; see [Sharding](../replication/sharding.md)
- `shards` β€” Complete array of unique shard IDs, integers `0`–`65535`; order does not matter. Required for a declarative database with no installed map; custom-map conversion can omit it. All peers must agree, and the local `shard` must be included when configured. Changing it never recomputes an installed map; see [Complete shard-id set](../replication/sharding.md#complete-shard-id-set). (Added in: v5.4.0)
- `mtls.certificateVerification` β€” Certificate revocation checking (CRL/OCSP) for replication connections; see [Certificate Verification](../security/certificate-verification.md)
- `logging` β€” Replication event logging; sub-options: `path`, `level`, `tag`, `stdStreams`. See [Logging Configuration](../logging/configuration.md)

Expand Down
6 changes: 6 additions & 0 deletions reference/database/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ Two details matter outside a request context:
- **`await` the `transaction()` call, and catch it.** An unawaited call lets the timer callback finish before the commit resolves, so a failed write is never observed; awaiting without a `catch` still leaves the rejection unhandled, because nothing awaits the callback itself.
- **Guard against overlap when a job can outlast its interval.** A timer fires on schedule regardless of whether the previous run has committed, so two runs would open two transactions over the same rows. The `running` flag above skips a tick instead.

### Replication confirmation

Set `replicatedConfirmation` on a fresh context before calling `transaction()` to request confirmation for its writes. Pass the callback's `txn` argument to table methods to bind them explicitly. Reusing a context with an active transaction does not change that transaction's confirmation settings. See the [independent-transaction example](../replication/sharding.md#programmatic-replication-control), [uncertain-outcome behavior](../replication/sharding.md#confirmation-failure-after-a-local-commit), and [LMDB confirmation behavior](../replication/sharding.md#confirmation-outcomes).

For `@sharded` tables, confirmation counts other owners in each affected owner set of the first written database; a temporary replica cannot satisfy it. `replicateTo` does not change those owners. See [declarative ownership](../replication/residency.md#ownership-reads-and-writes).

### Nesting

If `transaction()` is called with a context that already has an active transaction, it reuses that transaction, executes the callback immediately, and returns. This makes `transaction()` safe to call defensively to ensure a transaction is always active.
Expand Down
40 changes: 25 additions & 15 deletions reference/database/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,19 @@ type MyTable @table {

Optional arguments:

| Argument | Type | Default | Description |
| ------------------------ | --------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `table` | `String` | type name | Override the table name |
| `database` | `String` | `"data"` | Database to place the table in |
| `expiration` | `Int` | β€” | Seconds until a record goes stale (useful for caching tables) |
| `eviction` | `Int` | `0` | Additional seconds after `expiration` before a record is physically removed |
| `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
| `replicate` | `Boolean` | true | Enable replication of this table |
| `replicaExpiration` | `Float` | `3600` | Seconds a record fetched from another node is kept as a [local replica](#replicaexpiration-and-replicarefreshfraction); `0` stops new admissions |
| `replicaRefreshFraction` | `Float` | `0.5` | Fraction of `replicaExpiration` after which a read of a replica renews its lifetime; `1` never renews |
| `audit` | `Boolean` | `logging.auditLog` for ordinary tables | Enable the table's transaction log; new tables with `@fullText` require explicit `true` |
| `cacheControl` | `String` | β€” | `Cache-Control` header value emitted on anonymous GET/HEAD 200/304 responses for this table |
| `randomAccessFields` | `Boolean` | `storage.randomAccessFields` | [Pin this table's record encoding](#randomaccessfields) |
| Argument | Type | Default | Description |
| ------------------------ | --------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `table` | `String` | type name | Override the table name |
| `database` | `String` | `"data"` | Database to place the table in |
| `expiration` | `Int` | β€” | Seconds until a record goes stale (useful for caching tables) |
| `eviction` | `Int` | `0` | Additional seconds after `expiration` before a record is physically removed |
| `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
| `replicate` | `Boolean` | true | Enable replication of this table |
| `replicaExpiration` | `Float` | `3600` | Seconds to keep a [local residency replica](#replicaexpiration-and-replicarefreshfraction); `0` stops new admissions |
| `replicaRefreshFraction` | `Float` | `0.5` | Fraction of `replicaExpiration` after which a read of a replica renews its lifetime; `1` never renews |
| `audit` | `Boolean` | `logging.auditLog` for ordinary tables | Enable the table's transaction log; new tables with `@fullText` require explicit `true` |
| `cacheControl` | `String` | β€” | `Cache-Control` header value emitted on anonymous GET/HEAD 200/304 responses for this table |
| `randomAccessFields` | `Boolean` | `storage.randomAccessFields` | [Pin this table's record encoding](#randomaccessfields) |

**`expiration`, `eviction`, and `scanInterval`**

Expand Down Expand Up @@ -141,11 +141,13 @@ Local replicas are RocksDB-only. On the deprecated LMDB engine, remote reads rem

On RocksDB, schema rescans apply changes to already-loaded tables on other workers. The new lifetime applies to subsequent admissions and renewals; existing deadlines are not rewritten. Setting `replicaExpiration: 0` stops new admissions and the sweep; existing replicas remain until overwritten or evicted.

On a table whose records live on specific nodes (see [sharding](../replication/sharding.md#by-record-content-setresidency)), a node that does not hold a record fetches it from a node that does. The fetched record is kept locally as a _replica_: a complete copy that reads and searches on that node use without contacting another node again. A replica does not move the record and is never replicated onward. A write by an owner reaches the node as an invalidation, which turns the replica back into a stub, so the next read fetches the new version. Tables sharded by primary key only (`setResidencyById`) keep no replicas.
On a table whose records live on specific nodes (see [sharding](../replication/sharding.md#by-record-content-setresidency)), a node that does not hold a record fetches it from a node that does. The fetched record is kept locally as a _replica_: a complete copy that reads and searches on that node use without contacting another node again. A replica does not move the record and is never replicated onward. A write by an owner reaches the node as an invalidation, which turns the replica back into a stub, so the next read fetches the new version. Tables using the older [`setResidencyById`](../replication/sharding.md#by-primary-key-only-setresidencybyid) mechanism keep no replicas. Schema-defined `@sharded` tables follow the rules below.

- **`replicaExpiration`** β€” Seconds a replica is kept. A sweep runs every quarter of this time, with a minimum interval of one second, and reduces replicas past their lifetime back to their indexed stubs. A replica can still be served until the sweep reaches it; the next read after demotion fetches the record again. `0` disables new replica admissions and the sweep. A replica never outlives the record's own `expiration`, if it has one.
- **`replicaRefreshFraction`** β€” A read of a replica after this fraction of `replicaExpiration` has passed renews the lifetime, so records that stay in use stay local. Must be greater than `0` and at most `1`; `1` gives every replica a fixed lifetime.

[Schema-defined sharding](../replication/residency.md#ownership-reads-and-writes) tables with secondary indexes can retain replicas for key-derived slots. With positive `replicaExpiration`, a non-owner writer also keeps its complete write as a temporary replica under the same expiry and replacement rules. This local copy never counts as owner confirmation. Setting the lifetime to `0` does not drain existing replicas; see the [rollback procedure](../replication/residency.md#rollback).

```graphql
# Keep fetched records for a day; a read after six hours renews them
type Order @table(replicaExpiration: 86400, replicaRefreshFraction: 0.25) {
Expand Down Expand Up @@ -238,6 +240,12 @@ export class Product extends tables.Product {

See [REST Headers / Cache-Control](../rest/headers.md#cache-control) for the full caching behavior and how `Cache-Control` interacts with authentication headers.

### `@sharded`

<VersionBadge version="v5.4.0" />

Defines immutable primary-key placement on a schema-defined RocksDB table. LMDB declarations are refused; legacy residency functions are unchanged. `slots` is the database-wide count (`1` through `65536`), adopting the installed map's count or defaulting to `1024`. The key hash selects a slot and the installed database map selects its shard. `by` and `slotFrom` are not supported yet; see [concurrent-move design (#1178)](https://github.com/HarperFast/harper-pro/issues/1178) and [Schema-defined sharding](../replication/residency.md).

### `@export`

Exposes the table as an externally accessible resource endpoint, available via REST, MQTT, and other interfaces.
Expand Down Expand Up @@ -767,7 +775,9 @@ export class Reports extends tables.Reports {

For vector queries, `rowFilter` participates in HNSW traversal. A caller therefore receives the k nearest _matching_ records rather than "nearest k, minus filtered records." `rowFilter` can apply to every candidate visited; prefer indexed query conditions for predicates that can be expressed declaratively.

`rowFilter` applies only when a non-raw `put` or `invalidate` event carries an authoritative row value. Delete tombstones, value-less invalidations, raw events, and published messages may not contain the complete current record. A subscription that needs such events can also provide an `eventFilter`:
On a non-owner for a sharded table, invalidation values contain only the local index projection. A `rowFilter` on an unindexed field therefore sees it as missing, so a negated predicate can pass; do not use one to decide whether a subscriber may learn a record's id or indexed fields. See the [subscription limitation](../replication/residency.md#subscription-filters).

`rowFilter` applies only when a non-raw `put` or `invalidate` event carries a row value. Delete tombstones, value-less invalidations, raw events, and published messages may not contain the complete current record. A subscription that needs such events can also provide an `eventFilter`:

```javascript
subscribe(request) {
Expand Down
6 changes: 6 additions & 0 deletions reference/database/system-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ This table is managed automatically by the Data Loader. No direct interaction is

## Replication Tables

### `hdb_residency_slots`

<VersionBadge version="v5.4.0" />

Stores one authoritative [shard map](../replication/residency.md#installed-maps) snapshot per database, keyed by `database`, with `version`, `digest`, `slots`, and complete nonoverlapping `ranges`. Install custom maps through the super-user `initialize_shard_map` operation. Active assignments cannot move, and deleting an installed map is refused. A lost or corrupted snapshot must be recovered; it is never regenerated from configuration.

### `hdb_nodes`

Stores the configuration and state of known nodes in a cluster, including connection details, replication settings, and revoked certificate serial numbers.
Expand Down
Loading
Loading