guides/statistics.md

# RocksDB Statistics Guide

This guide explains how to collect and use RocksDB statistics in Erlang RocksDB. Statistics provide insights into database performance, I/O operations, cache efficiency, and more.

## Getting Started

### Creating a Statistics Object

First, create a statistics object and attach it to your database:

```erlang
%% Create a statistics collector
{ok, Stats} = rocksdb:new_statistics(),

%% Open database with statistics enabled
{ok, Db} = rocksdb:open("my_db", [
    {create_if_missing, true},
    {statistics, Stats}
]).
```

### Reading Statistics

Use `rocksdb:statistics_ticker/2` to read counter values:

```erlang
{ok, KeysWritten} = rocksdb:statistics_ticker(Stats, number_keys_written),
io:format("Keys written: ~p~n", [KeysWritten]).
```

Use `rocksdb:statistics_histogram/2` to read histogram data:

```erlang
{ok, WriteHist} = rocksdb:statistics_histogram(Stats, db_write),
io:format("Write latency - median: ~.2f us, p99: ~.2f us~n",
          [maps:get(median, WriteHist), maps:get(percentile99, WriteHist)]).
```

### Histogram Data Format

Histogram results are returned as a map with the following keys:

| Key | Type | Description |
|-----|------|-------------|
| `median` | float | 50th percentile value |
| `percentile95` | float | 95th percentile value |
| `percentile99` | float | 99th percentile value |
| `average` | float | Mean value |
| `standard_deviation` | float | Standard deviation |
| `max` | float | Maximum observed value |
| `count` | integer | Number of samples |
| `sum` | integer | Sum of all samples |

### Setting Statistics Level

Control the level of detail collected:

```erlang
%% Set to collect all statistics
ok = rocksdb:set_stats_level(Stats, stats_all),

%% Or disable expensive timing stats
ok = rocksdb:set_stats_level(Stats, stats_except_timers).
```

Available levels:
- `stats_disable_all` - Disable all statistics
- `stats_except_tickers` - Collect histograms only
- `stats_except_histogram_or_timers` - Collect tickers only (no histograms or timing)
- `stats_except_timers` - Collect everything except timing measurements
- `stats_except_detailed_timers` - Collect everything except detailed timing
- `stats_except_time_for_mutex` - Collect everything except mutex timing
- `stats_all` - Collect all statistics (default)

### Cleanup

Release the statistics object when done:

```erlang
ok = rocksdb:close(Db),
ok = rocksdb:release_statistics(Stats).
```

---

## Complete Ticker Reference

Tickers are simple counters that track cumulative values.

### Database Operation Tickers

| Ticker | Description |
|--------|-------------|
| `number_keys_written` | Total number of keys written to the database |
| `number_keys_read` | Total number of keys read from the database |
| `number_keys_updated` | Total number of keys updated (via merge) |
| `bytes_written` | Total bytes written to the database |
| `bytes_read` | Total bytes read from the database |
| `iter_bytes_read` | Total bytes read through iterators |

### Iterator Tickers

| Ticker | Description |
|--------|-------------|
| `number_db_seek` | Number of seek operations on iterators |
| `number_db_next` | Number of next operations on iterators |
| `number_db_prev` | Number of prev operations on iterators |
| `number_db_seek_found` | Number of seek operations that found a key |
| `number_db_next_found` | Number of next operations that found a key |
| `number_db_prev_found` | Number of prev operations that found a key |

### Block Cache Tickers

| Ticker | Description |
|--------|-------------|
| `block_cache_miss` | Total block cache misses |
| `block_cache_hit` | Total block cache hits |
| `block_cache_add` | Number of blocks added to cache |
| `block_cache_add_failures` | Number of failures adding blocks to cache |
| `block_cache_index_miss` | Index block cache misses |
| `block_cache_index_hit` | Index block cache hits |
| `block_cache_filter_miss` | Filter block cache misses |
| `block_cache_filter_hit` | Filter block cache hits |
| `block_cache_data_miss` | Data block cache misses |
| `block_cache_data_hit` | Data block cache hits |
| `block_cache_bytes_read` | Total bytes read from block cache |
| `block_cache_bytes_write` | Total bytes written to block cache |

**Example - Calculate cache hit ratio:**

```erlang
{ok, Hits} = rocksdb:statistics_ticker(Stats, block_cache_hit),
{ok, Misses} = rocksdb:statistics_ticker(Stats, block_cache_miss),
HitRatio = case Hits + Misses of
    0 -> 0.0;
    Total -> Hits / Total * 100
end,
io:format("Block cache hit ratio: ~.2f%~n", [HitRatio]).
```

### Memtable Tickers

| Ticker | Description |
|--------|-------------|
| `memtable_hit` | Number of reads served from memtable |
| `memtable_miss` | Number of reads not found in memtable |

### Write Path Tickers

| Ticker | Description |
|--------|-------------|
| `write_done_by_self` | Writes completed by the calling thread |
| `write_done_by_other` | Writes batched and completed by another thread |
| `wal_file_synced` | Number of WAL file sync operations |
| `stall_micros` | Total microseconds spent in write stalls |

### Compaction Tickers

| Ticker | Description |
|--------|-------------|
| `compact_read_bytes` | Bytes read during compaction |
| `compact_write_bytes` | Bytes written during compaction |
| `flush_write_bytes` | Bytes written during memtable flush |
| `compaction_key_drop_newer_entry` | Keys dropped due to newer version existing |
| `compaction_key_drop_obsolete` | Keys dropped due to being obsolete (deleted) |
| `compaction_key_drop_range_del` | Keys dropped due to range delete |
| `compaction_key_drop_user` | Keys dropped by user compaction filter |
| `compaction_cancelled` | Number of cancelled compactions |
| `number_superversion_acquires` | Superversion acquire operations |
| `number_superversion_releases` | Superversion release operations |

### BlobDB Tickers

| Ticker | Description |
|--------|-------------|
| `blob_db_num_put` | Number of put operations |
| `blob_db_num_write` | Number of write operations |
| `blob_db_num_get` | Number of get operations |
| `blob_db_num_multiget` | Number of multi-get operations |
| `blob_db_num_seek` | Number of seek operations |
| `blob_db_num_next` | Number of next operations |
| `blob_db_num_prev` | Number of prev operations |
| `blob_db_num_keys_written` | Number of keys written |
| `blob_db_num_keys_read` | Number of keys read |
| `blob_db_bytes_written` | Total bytes written |
| `blob_db_bytes_read` | Total bytes read |
| `blob_db_write_inlined` | Writes stored inline (not in blob) |
| `blob_db_write_inlined_ttl` | Inline writes with TTL |
| `blob_db_write_blob` | Writes stored in blob files |
| `blob_db_write_blob_ttl` | Blob writes with TTL |
| `blob_db_blob_file_bytes_written` | Bytes written to blob files |
| `blob_db_blob_file_bytes_read` | Bytes read from blob files |
| `blob_db_blob_file_synced` | Number of blob file syncs |
| `blob_db_blob_index_expired_count` | Expired blob index entries |
| `blob_db_blob_index_expired_size` | Size of expired blob index entries |
| `blob_db_blob_index_evicted_count` | Evicted blob index entries |
| `blob_db_blob_index_evicted_size` | Size of evicted blob index entries |
| `blob_db_gc_num_files` | Blob files processed by GC |
| `blob_db_gc_num_new_files` | New blob files created by GC |
| `blob_db_gc_failures` | Number of GC failures |
| `blob_db_gc_num_keys_relocated` | Keys relocated during GC |
| `blob_db_gc_bytes_relocated` | Bytes relocated during GC |
| `blob_db_fifo_num_files_evicted` | Files evicted by FIFO compaction |
| `blob_db_fifo_num_keys_evicted` | Keys evicted by FIFO compaction |
| `blob_db_fifo_bytes_evicted` | Bytes evicted by FIFO compaction |
| `blob_db_cache_miss` | Blob cache misses |
| `blob_db_cache_hit` | Blob cache hits |
| `blob_db_cache_add` | Blobs added to cache |
| `blob_db_cache_add_failures` | Failed blob cache additions |
| `blob_db_cache_bytes_read` | Bytes read from blob cache |
| `blob_db_cache_bytes_write` | Bytes written to blob cache |

### Transaction Tickers

| Ticker | Description |
|--------|-------------|
| `txn_prepare_mutex_overhead` | Time spent waiting on prepare mutex (ns) |
| `txn_old_commit_map_mutex_overhead` | Time spent waiting on commit map mutex (ns) |
| `txn_duplicate_key_overhead` | Time spent on duplicate key checking (ns) |
| `txn_snapshot_mutex_overhead` | Time spent waiting on snapshot mutex (ns) |
| `txn_get_try_again` | Number of TryAgain errors from transaction gets |

---

## Complete Histogram Reference

Histograms track the distribution of values over time.

### Database Operation Histograms

| Histogram | Description |
|-----------|-------------|
| `db_get` | Get operation latency (microseconds) |
| `db_write` | Write operation latency (microseconds) |
| `db_multiget` | Multi-get operation latency (microseconds) |
| `db_seek` | Iterator seek latency (microseconds) |

**Example - Monitor read latency:**

```erlang
{ok, GetHist} = rocksdb:statistics_histogram(Stats, db_get),
io:format("Get latency:~n"),
io:format("  Median: ~.2f us~n", [maps:get(median, GetHist)]),
io:format("  P95:    ~.2f us~n", [maps:get(percentile95, GetHist)]),
io:format("  P99:    ~.2f us~n", [maps:get(percentile99, GetHist)]),
io:format("  Max:    ~.2f us~n", [maps:get(max, GetHist)]).
```

### Compaction and Flush Histograms

| Histogram | Description |
|-----------|-------------|
| `compaction_time` | Compaction duration (microseconds) |
| `flush_time` | Memtable flush duration (microseconds) |

### I/O Histograms

| Histogram | Description |
|-----------|-------------|
| `sst_read_micros` | SST file read latency (microseconds) |
| `sst_write_micros` | SST file write latency (microseconds) |
| `table_sync_micros` | SST file sync latency (microseconds) |
| `wal_file_sync_micros` | WAL file sync latency (microseconds) |
| `bytes_per_read` | Bytes per read operation |
| `bytes_per_write` | Bytes per write operation |

### BlobDB Histograms

| Histogram | Description |
|-----------|-------------|
| `blob_db_key_size` | Key size distribution (bytes) |
| `blob_db_value_size` | Value size distribution (bytes) |
| `blob_db_write_micros` | Write latency (microseconds) |
| `blob_db_get_micros` | Get latency (microseconds) |
| `blob_db_multiget_micros` | Multi-get latency (microseconds) |
| `blob_db_seek_micros` | Seek latency (microseconds) |
| `blob_db_next_micros` | Next latency (microseconds) |
| `blob_db_prev_micros` | Prev latency (microseconds) |
| `blob_db_blob_file_write_micros` | Blob file write latency (microseconds) |
| `blob_db_blob_file_read_micros` | Blob file read latency (microseconds) |
| `blob_db_blob_file_sync_micros` | Blob file sync latency (microseconds) |
| `blob_db_compression_micros` | Compression time (microseconds) |
| `blob_db_decompression_micros` | Decompression time (microseconds) |

### Transaction Histograms

| Histogram | Description |
|-----------|-------------|
| `num_op_per_transaction` | Number of operations per transaction |

---

## Example: Comprehensive Monitoring

Here's a complete example that monitors key database metrics:

```erlang
-module(db_monitor).
-export([report/1]).

report(Stats) ->
    %% Operation counts
    {ok, KeysWritten} = rocksdb:statistics_ticker(Stats, number_keys_written),
    {ok, KeysRead} = rocksdb:statistics_ticker(Stats, number_keys_read),

    %% Cache efficiency
    {ok, CacheHits} = rocksdb:statistics_ticker(Stats, block_cache_hit),
    {ok, CacheMisses} = rocksdb:statistics_ticker(Stats, block_cache_miss),
    CacheHitRatio = safe_ratio(CacheHits, CacheHits + CacheMisses),

    %% Memtable efficiency
    {ok, MemHits} = rocksdb:statistics_ticker(Stats, memtable_hit),
    {ok, MemMisses} = rocksdb:statistics_ticker(Stats, memtable_miss),
    MemHitRatio = safe_ratio(MemHits, MemHits + MemMisses),

    %% Write stalls
    {ok, StallMicros} = rocksdb:statistics_ticker(Stats, stall_micros),

    %% Latency histograms
    {ok, GetHist} = rocksdb:statistics_histogram(Stats, db_get),
    {ok, WriteHist} = rocksdb:statistics_histogram(Stats, db_write),

    io:format("=== RocksDB Statistics ===~n"),
    io:format("Keys written: ~p, Keys read: ~p~n", [KeysWritten, KeysRead]),
    io:format("Block cache hit ratio: ~.2f%~n", [CacheHitRatio * 100]),
    io:format("Memtable hit ratio: ~.2f%~n", [MemHitRatio * 100]),
    io:format("Total stall time: ~.2f ms~n", [StallMicros / 1000]),
    io:format("Get latency p99: ~.2f us~n", [maps:get(percentile99, GetHist)]),
    io:format("Write latency p99: ~.2f us~n", [maps:get(percentile99, WriteHist)]),
    ok.

safe_ratio(_, 0) -> 0.0;
safe_ratio(Num, Denom) -> Num / Denom.
```

---

## Statistics with Column Families

When using column families, create one statistics object and share it:

```erlang
{ok, Stats} = rocksdb:new_statistics(),
{ok, Db, [DefaultCf, DataCf, IndexCf]} = rocksdb:open_with_cf(
    "my_db",
    [{create_if_missing, true}, {statistics, Stats}],
    [{"default", []}, {"data", []}, {"index", []}]
),

%% Statistics are aggregated across all column families
{ok, TotalKeysWritten} = rocksdb:statistics_ticker(Stats, number_keys_written).
```

---

## Statistics with Transactions

Statistics also work with pessimistic transaction databases:

```erlang
{ok, Stats} = rocksdb:new_statistics(),
{ok, Db, _} = rocksdb:open_pessimistic_transaction_db(
    "txn_db",
    [{create_if_missing, true}, {statistics, Stats}],
    [{"default", []}]
),

%% Perform transactions...
{ok, Txn} = rocksdb:pessimistic_transaction(Db, []),
ok = rocksdb:pessimistic_transaction_put(Txn, <<"key">>, <<"value">>),
ok = rocksdb:pessimistic_transaction_commit(Txn),
ok = rocksdb:release_pessimistic_transaction(Txn),

%% Check transaction-specific stats
{ok, OpsPerTxn} = rocksdb:statistics_histogram(Stats, num_op_per_transaction),
io:format("Avg ops per transaction: ~.2f~n", [maps:get(average, OpsPerTxn)]).
```

---

## Performance Considerations

1. **Statistics overhead**: Collecting statistics has a small performance cost. Use `stats_except_timers` or `stats_except_detailed_timers` in production if timing precision isn't critical.

2. **Polling frequency**: Statistics are cumulative. Poll them periodically and compute deltas for rate metrics.

3. **Memory**: The statistics object uses minimal memory but should be released when no longer needed.

4. **Thread safety**: Statistics are thread-safe and can be read from any Erlang process.