MemKV
Operate

Monitoring

Health endpoints, Prometheus metrics, status API, and Kubernetes probes for MemKV.

The MemKV server serves health endpoints and Prometheus metrics on its admin HTTP endpoint (default: 0.0.0.0:9901). The MemKV client has its own metrics exporters, which are off by default.

Client-side exporters

The MemKV client (the NIXL plugin and the LD_PRELOAD shim) reports request latency, transport state, and batch size. To turn the metrics on, set one of these environment variables. If neither is set, the client collects no metrics.

VariableTypeDefaultNotes
MEMKV_OTEL_ENDPOINTURLunsetOTLP/gRPC collector that receives spans and metrics (e.g. http://otel-collector:4317). Falls back to OTEL_EXPORTER_OTLP_ENDPOINT. The published packages do not include OTLP export.
MEMKV_PROMETHEUS_BINDhost:portunsetAddress for a Prometheus scrape endpoint at /metrics inside the client process. Use it when the client runs in a long-lived process that Prometheus can reach.

Each process starts one exporter. If MEMKV_OTEL_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT is set, the client uses OTLP and does not start the Prometheus endpoint. Because the published packages do not include OTLP export, use MEMKV_PROMETHEUS_BIND, and make sure neither OTLP variable is set in the client's environment.

Health endpoints

All admin endpoints are versioned under /v1/.

EndpointPurposeResponse
GET /v1/healthLiveness probeJSON {status, rdma_active, drives_online, drives_offline, drives_total} (macOS: {status} only)
GET /v1/readyReadiness probeok (200), or 503 license expired once the installed license is fully expired, when the server rejects every request
GET /v1/statusDetailed statusJSON with memory, storage, RDMA stats
GET /v1/metricsPrometheus metricstext/plain Prometheus format
curl http://localhost:9901/v1/health
# {"status":"healthy","rdma_active":true,"drives_online":12,"drives_offline":0,"drives_total":12}

status is healthy, degraded, or unhealthy. The HTTP status code matches: 200 for healthy, 503 for unhealthy.

memkv admin status --json returns the same fields as /v1/status below, including the storage totals (raw_bytes_total, usable_bytes_total, used_bytes_total) and the byte fields for each device. memkv admin status without --json shows storage use as used / usable and a percentage.

Status endpoint

curl http://localhost:9901/v1/status
{
  "memory": {
    "used_bytes": 1073741824,
    "total_bytes": 265289728000,
    "slabs_allocated": 512,
    "slabs_total": 126464
  },
  "storage": {
    "num_devices": 24,
    "block_size_bytes": 4194304,
    "total_blocks": 1000,
    "raw_bytes_total": 384000000000000,
    "usable_bytes_total": 383979687936000,
    "used_bytes_total": 176160768,
    "devices": [
      {
        "device_id": 0,
        "path": "/dev/nvme0n1",
        "total_slots": 3815461,
        "allocated_slots": 42,
        "raw_capacity_bytes": 16000000000000,
        "usable_bytes": 15999153635328,
        "metadata_bytes": 846364672,
        "used_bytes": 176160768
      }
    ]
  },
  "rdma": {
    "active_connections": 8
  },
  "blocks_total": 1000,
  "blocks_persisted": 950,
  "uptime_seconds": 3612
}

Prometheus metrics

Request operations

MetricTypeDescription
memkv_allocate_totalCounterTotal allocate requests
memkv_lookup_totalCounterTotal lookup requests
memkv_delete_totalCounterTotal delete requests
memkv_commit_totalCounterTotal commit requests
memkv_read_totalCounterTotal direct-IO read requests
memkv_read_bytes_totalCounterTotal bytes served on direct-IO reads
memkv_write_totalCounterTotal direct-IO write requests
memkv_write_bytes_totalCounterTotal bytes persisted on direct-IO writes
memkv_connect_totalCounterTotal RDMA connect requests
memkv_exists_totalCounterTotal exists requests
memkv_not_found_totalCounterTotal not-found responses
memkv_no_space_totalCounterTotal no-space responses
memkv_invalid_requests_totalCounterTotal invalid/malformed requests
memkv_request_latency_usHistogramEnd-to-end request latency

RDMA transport

MetricTypeDescription
memkv_rdma_activeGauge1 if RDMA data path is active, 0 if fell back to local-only
memkv_rdma_connections_activeGaugeActive RDMA connections
memkv_rdma_put_latency_usHistogramRDMA put latency (server RDMA read from client on a put)
memkv_rdma_get_latency_usHistogramRDMA get latency (server RDMA write to client on a get)
memkv_rdma_errors_totalCounterRDMA transport errors (CQ/post failures)

TCP transport

MetricTypeDescription
memkv_tcp_put_totalCounterTCP put data frames served (inline put or streamed chunk)
memkv_tcp_get_totalCounterTCP get data frames served (inline get or streamed chunk)
memkv_tcp_put_bytes_totalCounterPayload bytes accepted by TCP puts
memkv_tcp_get_bytes_totalCounterPayload bytes served by TCP gets
memkv_tcp_put_latency_usHistogramServer-side TCP put handling latency (excludes socket write)
memkv_tcp_get_latency_usHistogramServer-side TCP get handling latency (excludes socket write)

The latency histograms do not include time spent waiting on the network throttle. They measure only the server's own work.

Network throttle

The server always exports these metrics. When network.throttle is off, they stay at zero.

MetricTypeDescription
memkv_throttle_bytes_totalCounterBulk bytes metered against the cap, RDMA and TCP combined
memkv_throttle_delay_seconds_totalCounterCumulative seconds transfers spent waiting on the cap
memkv_throttle_waits_totalCounterTransfers the cap delayed; divide the delay total by this for mean wait

Useful queries:

# Is the cap binding right now? Seconds of delay per second of wall time.
rate(memkv_throttle_delay_seconds_total[1m])

# Achieved bulk rate in bits/s, to compare against your configured cap.
rate(memkv_throttle_bytes_total[1m]) * 8

# Mean delay per throttled transfer.
rate(memkv_throttle_delay_seconds_total[5m])
  / rate(memkv_throttle_waits_total[5m])

If latency is high and the first query stays at zero, the throttle is not the cause. Check the drives.

Storage devices

MetricTypeLabelsDescription
memkv_device_slots_usedGaugedeviceAllocated slots per device
memkv_device_capacity_raw_bytesGaugedeviceRaw NVMe capacity per device in bytes
memkv_device_capacity_usable_bytesGaugedevicePer-device bytes available for user data (raw minus MemKV metadata regions)
memkv_device_bytes_usedGaugedevicePer-device bytes currently allocated to blocks (allocated_slots * block_size)
memkv_storage_raw_bytesGauge—Sum of raw NVMe capacity across all devices
memkv_storage_usable_bytesGauge—Sum of usable capacity (data region only) across all devices
memkv_storage_used_bytesGauge—Sum of bytes allocated to blocks across all devices
memkv_trim_extents_totalCounterdeviceExtents trimmed per device
memkv_trim_bytes_totalCounterdeviceBytes trimmed per device
memkv_trim_latency_usHistogramdeviceTRIM batch latency
memkv_trim_errors_totalCounterdeviceTRIM errors per device
memkv_trim_pendingGauge—Pending TRIM requests

Capacity reclaim

MetricTypeDescription
memkv_storage_evictions_totalCounterBlocks reclaimed from storage, least recently used first
memkv_storage_evictions_unindexed_totalCounterBlocks reclaimed oldest-first because they had no recency to sort by
memkv_storage_evicted_bytes_totalCounterCapacity reclaimed, charged at allocation granularity
memkv_storage_eviction_passes_totalCounterReclaim passes run
memkv_storage_eviction_starved_passes_totalCounterPasses that found nothing to reclaim while still above the high watermark
memkv_resident_evictions_totalCounterBlocks whose memory slab was released under memory pressure; the bytes stay durable

What to watch:

  • Alert when memkv_storage_eviction_starved_passes_total rises. Storage is above its high watermark and nothing is left to reclaim, so writes will soon fail. Add capacity, or find what is holding the blocks.
  • Fill ratio: memkv_storage_used_bytes / memkv_storage_usable_bytes. Normally it moves between the two watermarks. If it stays at the high watermark, reclaim cannot keep up with writes.
  • memkv_storage_evictions_unindexed_total rising steadily: the server is reclaiming blocks oldest first, because they were stored before the last restart and have not been read since. This is expected for a while after a restart on large drives, and it slows as clients read their working set. If it does not slow, the server is reclaiming blocks without regard to which ones are in use.
  • Passes per eviction: if memkv_storage_eviction_passes_total rises much faster than memkv_storage_evictions_total, reclaim passes run but find little. Widen the gap between the watermarks.

Memory and blocks

MetricTypeDescription
memkv_blocks_totalGaugeTotal blocks in index
memkv_blocks_persistedGaugeBlocks persisted to storage
memkv_blocks_by_stateGaugeBlock count by state
memkv_slabs_allocatedGaugeAllocated memory slabs
memkv_slabs_totalGaugeTotal memory slabs
memkv_memory_used_bytesGaugeMemory used for blocks
memkv_memory_total_bytesGaugeTotal memory available
memkv_uptime_secondsGaugeServer uptime in seconds (since process start)

Admin API

MetricTypeLabelsDescription
memkv_admin_requests_totalCounterpathAdmin endpoint requests by path

Storage pipeline (hot-path tracing)

Detailed timings inside the write path. Use them to diagnose slow writes under sustained load.

MetricTypeDescription
memkv_persist_usHistogramDirect-write end-to-end wall time (request entry → persist return)
memkv_persist_pre_wait_usHistogramEntry to just before the writer-worker handoff (extent lock, journal Alloc, …)
memkv_writer_wait_usHistogramWait time on the io_uring writer-worker channel
memkv_persist_post_wait_usHistogramAfter writer-worker completion to persist return (journal Commit, dirty queue)
memkv_iouring_write_usHistogramio_uring write submit-to-completion latency
memkv_iouring_fsync_usHistogramio_uring fsync submit-to-completion latency
memkv_iouring_fsync_batch_sizeHistogramData writes drained per fsync completion
memkv_allocate_duration_usHistogramFreeList slot allocate time
memkv_journal_flush_duration_usHistogramJournal buffer flush (fdatasync) time
memkv_journal_pending_entriesGaugeUn-flushed journal entries across devices
memkv_dirty_queue_depthGaugePending entries in per-device dirty queues
memkv_btree_cache_hits_totalCounterBTree leaf cache hits
memkv_btree_cache_misses_totalCounterBTree leaf cache misses
memkv_btree_leaf_miss_usHistogramBTree leaf miss-path resolution time
memkv_freelist_cas_retries_totalCounterFreeList head CAS retry count
memkv_slab_double_free_totalCounterBounce-buffer slabs freed more than once

Timings for each request stage, labeled op and step:

MetricTypeLabelsDescription
memkv_server_step_secondsHistogramop, stepTime spent in one server-side hot-path step
memkv_server_step_countCounterop, stepTimes that step ran
memkv_server_step_bytes_totalCounterop, stepPayload bytes through that step (omitted where nil)

The client metric memkv_client_step_seconds uses the same op and step labels. To find where a slow put spends its time, compare memkv_server_step_seconds{op="commit",step="total"} (time on the server) with memkv_client_step_seconds{op="put",step="commit"} (time including the network).

Kubernetes probes

livenessProbe:
  httpGet:
    path: /v1/health
    port: 9901
  initialDelaySeconds: 5
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /v1/ready
    port: 9901
  initialDelaySeconds: 5
  periodSeconds: 5