Skip to content
Open
73 changes: 65 additions & 8 deletions reference/analytics/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,71 @@ Harper automatically tracks the following metrics for all services. Applications

### Resource Usage Metrics

| `metric` | Key attributes | Other | Unit | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------ | ------------------- | ------- | --------------------------------------------------------------------------------- |
| `database-size` | `size`, `used`, `free`, `audit` | `database` | bytes | Database file size breakdown |
| `main-thread-utilization` | `idle`, `active`, `taskQueueLatency`, `rss`, `heapTotal`, `heapUsed`, `external`, `arrayBuffers` | `time` | various | Main thread resource usage: idle/active time, queue latency, and memory breakdown |
| `resource-usage` | (see below) | | various | Node.js process resource usage (see [resource-usage](#resource-usage-metric)) |
| `storage-volume` | `available`, `free`, `size` | `database` | bytes | Storage volume size breakdown |
| `table-size` | `size` | `database`, `table` | bytes | Table file size |
| `utilization` | | | % | Percentage of time the worker thread was processing requests |
| `metric` | Key attributes | Other | Unit | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `database-size` | `size`, `used`, `free`, `audit` | `database` | bytes | Database file size breakdown |
| `main-thread-utilization` | `idle`, `active`, `taskQueueLatency`, `rss`, `heapTotal`, `heapUsed`, `external`, `arrayBuffers` | `time` | various | Main thread resource usage: idle/active time, queue latency, and memory breakdown |
| `read-transaction-queue-depth` | `depth`, `maxDepth` | | count | Open tracked read transactions (see [transaction queue depth](#transaction-queue-depth-metrics)) |
| `resource-usage` | (see below) | | various | Node.js process resource usage (see [resource-usage](#resource-usage-metric)) |
| `storage-volume` | `available`, `free`, `size` | `database` | bytes | Storage volume size breakdown |
| `table-size` | `size` | `database`, `table` | bytes | Table file size |
| `transaction-commit-time` | `mean`, `median`, `p90`, `p95`, `p99`, `p999` | | ms | Duration from write commit submission to settlement (see [transaction queue depth](#transaction-queue-depth-metrics)) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transaction-commit-time belongs in #572's ### Storage Metrics section, not in this table — it isn't a resource-usage gauge. It flows through recordAction() with no byThread flag, so it aggregates as a cross-thread distribution, unlike every other row here. Dropping the row also resolves the incomplete and self-contradictory attribute list.

Suggested change
| `transaction-commit-time` | `mean`, `median`, `p90`, `p95`, `p99`, `p999` | | ms | Duration from write commit submission to settlement (see [transaction queue depth](#transaction-queue-depth-metrics)) |

| `utilization` | | | % | Percentage of time the worker thread was processing requests |
| `write-transaction-queue-depth` | `depth`, `maxDepth` | | count | In-flight write transaction commits (see [transaction queue depth](#transaction-queue-depth-metrics)) |

#### Transaction Queue Depth Metrics

<VersionBadge version="v5.2.0" />

`write-transaction-queue-depth` and `read-transaction-queue-depth` expose how many transactions are
in flight against the storage engine per worker thread — a concurrency and throughput signal, not a
reliable predictor on their own of the `Outstanding write transactions have too long of queue, please
try again later` (HTTP 503) rejection: `maxDepth` amplitude reflects concurrent commits, not whether
any single one is approaching the
[`storage.maxTransactionQueueTime`](../database/storage-tuning.md#storagemaxtransactionqueuetime)
duration limit (default 45s) that actually trips the 503.

`transaction-commit-time` records each commit's submit-to-settle duration on that same clock, and a
rising `p99`/`p999` (in the `hdb_analytics` aggregate table, where percentiles are computed — they
aren't present on `hdb_raw_analytics`) is a leading indicator of _gradual_ slowdowns approaching that
limit. It doesn't help with a single commit that hangs indefinitely, though: the metric only records
once a commit settles, so a genuinely wedged commit contributes no sample at all, while
`write-transaction-queue-depth`'s `depth` stays elevated on that thread for as long as the commit
remains outstanding. Harper also logs once per stuck commit when the 503 check itself fires, which is
the authoritative signal for that specific failure.
Comment on lines +198 to +205

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With #572 owning the metric, this paragraph should shrink to the part that is genuinely specific to queue depth: the contrast between a settled-only distribution and a gauge that stays elevated while a commit is wedged. That contrast is the most useful thing in this section and it is not in #572, so keep it — just drop the raw/aggregate percentile aside, which #572 covers more completely (and which correctly lists all nine percentiles).

The log sentence stays; I verified it against the source.

Suggested change
`transaction-commit-time` records each commit's submit-to-settle duration on that same clock, and a
rising `p99`/`p999` (in the `hdb_analytics` aggregate table, where percentiles are computed — they
aren't present on `hdb_raw_analytics`) is a leading indicator of _gradual_ slowdowns approaching that
limit. It doesn't help with a single commit that hangs indefinitely, though: the metric only records
once a commit settles, so a genuinely wedged commit contributes no sample at all, while
`write-transaction-queue-depth`'s `depth` stays elevated on that thread for as long as the commit
remains outstanding. Harper also logs once per stuck commit when the 503 check itself fires, which is
the authoritative signal for that specific failure.
[`transaction-commit-time`](#storage-metrics) records each commit's submit-to-settle duration on that
same clock, and a rising `p99`/`p999` is a leading indicator of _gradual_ slowdowns approaching that
limit. It doesn't help with a single commit that hangs indefinitely, though: the metric only records
once a commit settles, so a genuinely wedged commit contributes no sample at all, while
`write-transaction-queue-depth`'s `depth` stays elevated on that thread for as long as the commit
remains outstanding. Harper also logs once per stuck commit when the 503 check itself fires, which is
the authoritative signal for that specific failure.


| Field | Unit | Description |
| ---------- | ----- | ------------------------------------------------------- |
| `depth` | count | Instantaneous depth sampled at emit time |
| `maxDepth` | count | High-water mark since this thread's last emitted sample |

- **`write-transaction-queue-depth`** counts write commits handed to the storage engine whose commit
promises have not yet settled — how many commits this thread is juggling concurrently. This is
in-flight, not durability: under `storage.writeAsync: true` a settled commit promise does not
guarantee the write has been synced to disk.
- **`read-transaction-queue-depth`** counts concurrently open tracked read transactions, including
ones opened with snapshot disabled. A high count can mean either many short-lived reads or a few
long-lived ones — the count alone can't distinguish them, so use it as a concurrency signal; a
duration-based metric would be needed to identify a single transaction held open long enough to
hold back compaction.

Both metrics are gauges tracked only on the RocksDB write/read path, sampled per worker thread. On an
LMDB-backed database (`storage.engine: lmdb`), `depth` and `maxDepth` for both metrics always read `0`
— indistinguishable from a healthy, empty queue — regardless of actual read/write load. All per-thread
Comment on lines +222 to +224

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"On an LMDB-backed database ... always read 0" is imprecise, and the imprecision hides a worse failure mode. These counters are per-thread and process-global — no database/table attribute, which matches the empty "Other" column in the table. Mixed installs are normal, since storage.engine only selects the engine for new databases while existing ones keep theirs. On a mixed install the gauges don't read 0; they report RocksDB traffic only and silently under-count, which is harder to notice than a flat zero.

Suggested change
Both metrics are gauges tracked only on the RocksDB write/read path, sampled per worker thread. On an
LMDB-backed database (`storage.engine: lmdb`), `depth` and `maxDepth` for both metrics always read `0`
— indistinguishable from a healthy, empty queue — regardless of actual read/write load. All per-thread
Both metrics are gauges tracked only on the RocksDB write/read path, sampled per worker thread. They
carry no `database` dimension, so activity against LMDB-backed databases is not counted at all: on a
mixed install they report RocksDB traffic only and silently under-count, and on an install with no
RocksDB databases (`storage.engine: lmdb`) both always read `0` — indistinguishable from a healthy,
empty queue — regardless of actual read/write load. All per-thread

analytics reporting, including these gauges, piggybacks on the thread having recorded some other
analytics-eligible activity in the period — a thread with no recordable activity in a given second
emits no row at all rather than an explicit `depth: 0`. Absence of a sample is not the same as a
healthy reading, particularly for `read-transaction-queue-depth` on an otherwise-quiet thread holding a
single long-lived read.

The raw per-thread entries in `hdb_raw_analytics` retain each thread's true instantaneous `depth` and
per-period `maxDepth`; treat those as the reliable source for spike detection. The aggregate
`hdb_analytics` table is not a sum of per-thread peaks — each thread's `maxDepth` is first averaged
across its raw samples for the period, then those per-thread averages are summed — so a brief
single-thread spike is diluted rather than preserved. Always alert on `hdb_raw_analytics.maxDepth` (or
lower the sampling/aggregation period) rather than relying on the aggregate table to catch short
spikes. Tune the concrete alert threshold against a baseline for your workload, since absolute depth
scales with worker-thread count and per-transaction size.

#### `resource-usage` Metric

Expand Down
2 changes: 1 addition & 1 deletion reference/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ storage:
- `compression` — LZ4 record compression; _Default_: `true` (enabled by default since v4.3.0). Sub-options: `dictionary`, `threshold`
- `compactOnStart` — Compact all non-system databases on startup; _Default_: `false` (Added in: v4.3.0)
- `compactOnStartKeepBackup` — Retain compaction backups; _Default_: `false`
- `maxTransactionQueueTime` — Max write queue time before 503; _Default_: `45s`
- `maxTransactionQueueTime` — Max time a single write commit may stay unsettled before Harper starts rejecting writes with 503; see [Storage Tuning](../database/storage-tuning.md#storagemaxtransactionqueuetime); _Default_: `45s`
- `noReadAhead` — Advise OS against read-ahead; _Default_: `false`
- `prefetchWrites` — Prefetch before write transactions; _Default_: `true`
- `path` — Database files directory; _Default_: `<rootPath>/database`
Expand Down
2 changes: 1 addition & 1 deletion reference/database/storage-tuning.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Type: `string` (duration)

Default: `45s`

The maximum estimated time a write may wait in the commit queue before Harper rejects new writes with HTTP 503. Acts as backpressure when downstream disk I/O cannot keep up with incoming writes.
The maximum time a single write commit may remain unsettled before Harper starts rejecting new application-originated writes on that thread with HTTP 503. This is a per-commit duration check, not a queue-length threshold — it acts as backpressure when downstream disk I/O cannot keep up with incoming writes. Deletes and writes applied from a canonical source (e.g. replication or a caching source) bypass this check.

Lower this in latency-sensitive systems where it is better to shed load early than to let request queues grow. Raise it when occasional disk-write bursts are expected and the application can tolerate longer commit latency.

Expand Down
6 changes: 6 additions & 0 deletions release-notes/v5-lincoln/5.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Components can now declare recurring jobs in their configuration with a new buil

The `set_configuration` operation now accepts `"replicated": true` to apply a configuration change to all cluster nodes in a single Operations API call, with per-node outcomes reported in the response's `replicated` array. Only cluster-appropriate parameters should be replicated — see [Configuration Operations](/reference/v5/configuration/operations#set-configuration).

## Analytics

### Transaction queue depth metrics

New `write-transaction-queue-depth` and `read-transaction-queue-depth` metrics report per-thread write-commit and open-read-transaction concurrency against the storage engine, as a throughput/concurrency signal. A new `transaction-commit-time` metric records per-commit submit-to-settle duration, a leading indicator of gradual slowdowns toward the write-queue HTTP 503 rejection — see [Transaction Queue Depth Metrics](/reference/v5/analytics/overview#transaction-queue-depth-metrics).

## CLI

### Explicit Authentication for Operations API Commands
Expand Down