INFO
INFO [section]
Returns a bulk string of server and memory statistics in the standard Redis # Section / field:value (CRLF-separated) format, so redis-cli, monitoring agents, and benchmark harnesses read it unchanged. Goblin Core emits a deliberately small subset: # Server, # Replication, # Kafka, and # Memory. The optional section argument is accepted but ignored: INFO and INFO memory both return the full payload.
# Server
| field | value |
|---|---|
redis_version |
7.4.0 — the wire/behaviour compatibility level |
redis_mode |
standalone |
list_implementation |
concrete backend selected for standard list commands (pma or segmented; default segmented) |
hash_implementation |
representation selected for hashes created by standard commands (efficient or rt) |
keyspace_index |
top-level key index (swiss, or incremental linear under --real-time) |
# Replication
| field | meaning |
|---|---|
role |
master, or slave when an upstream firehose is configured |
master_replid |
the 128-bit replication lineage ID, rendered as 32 hexadecimal digits |
master_repl_offset |
highest logical mutation offset represented by this server |
kafka_acknowledged_offset |
last broker offset acknowledged by Kafka and eligible for inclusive snapshot recovery; -1 until one is known |
goblin_ready |
1 for a primary or live replica; 0 while a replica is connecting, replaying, or degraded |
master_link_status |
replica upstream link: up only after a complete handoff, otherwise down |
master_sync_in_progress |
1 while Kafka replay or buffered-firehose handoff is active |
replica_state |
connecting, reconnecting, replaying_kafka, buffering_firehose, live, or degraded |
slave_repl_offset |
replica's locally applied logical offset |
upstream_repl_offset |
latest offset observed in an upstream hello or firehose batch |
replica_lag |
known upstream-minus-local logical offset; -1 when disconnected before a newer upstream offset is known |
replica_reconnect_attempts |
connection attempts made after startup lost or could not establish the source |
replica_successful_reconnects |
reconnects that completed a safe handoff and returned to live state |
master_last_io_seconds_ago |
seconds since a successful upstream handshake or frame; -1 before either occurs |
replica_last_error |
most recent connection, lineage, replay, buffering, or apply failure; empty while live |
The logical and Kafka offsets are different coordinate systems. See Firehose replication and Kafka recovery before using them in restart automation. The replica-only fields are omitted on a primary except for goblin_ready, which is always present.
# Kafka
| field | meaning |
|---|---|
kafka_journal_enabled |
1 when the server is producing its write journal to Kafka; otherwise 0 |
kafka_ack_mode |
queued, broker, or off; broker mode withholds successful client replies and firehose delivery until Kafka acknowledges the corresponding write |
kafka_acknowledged_logical_offset |
highest Goblin replication offset whose Kafka record has been acknowledged by the broker |
kafka_pending_records |
produced records still awaiting a broker delivery result |
kafka_pending_bytes |
command and key bytes retained for those pending records |
kafka_oldest_pending_age_ms |
age of the oldest unacknowledged record |
kafka_retained_batch_bytes |
mutation payload retained until a broker acknowledgement makes its firehose batch releasable |
kafka_input_backpressured |
1 while retained Kafka payload has reached --kafka-pending-bytes and command input is paused |
The pending-byte ceiling bounds journal payload retained by the server, not librdkafka's own queue. A single atomic command, transaction, or script may cross the ceiling; Goblin then stops reading additional commands until acknowledgements release enough memory. See Kafka write log and recovery for the acknowledgement contract and its retry implications.
# Memory
| field | meaning |
|---|---|
used_memory |
committed data-store capacity: arenas, fixed index/table structures, object state, TTL tables, and actual upstream blob-pool capacity |
used_memory_rss |
the OS resident set size (ps-equivalent) |
used_memory_peak |
currently mirrors used_memory — Goblin does not yet track a separate high-water mark |
mem_reclaimable_bytes |
the dead subset of used_memory: bytes orphaned by deletes/overwrites that a compaction would free |
hash_heap_allocated_bytes |
heap-resident state owned by promoted hashes; compact hash handles and blobs live in the keyspace slot/arena instead |
realtime_hash_index_pool_bytes |
committed bytes in the fixed, prefaulted arena shared by all RT hash field indexes; 0 until that arena is initialized |
realtime_keyspace_index_pool_bytes |
committed bytes in the separate fixed RT keyspace-index arena selected by --real-time; otherwise 0 |
blob_pool_requested_bytes |
live compact zset/list blob bytes requested through the shared blob allocator |
blob_pool_capacity_bytes |
bytes the blob allocator currently holds from its upstream allocator, including live direct blobs over 1 KiB; this amount is included once in used_memory |
blob_pool_fragmentation_bytes |
retained pool capacity beyond live requested blob bytes; unlike arena dead bytes, GOBLIN.OPTIMIZE does not reclaim it |
blob_pool_live_allocations |
number of live blobs in the pool |
blob_pool_upstream_allocations |
cumulative allocations the pool has requested from its upstream resource |
maxmemory |
configured --maxmemory byte ceiling; 0 means unlimited |
maxmemory_policy |
noeviction — capacity-growing writes return OOM instead of evicting |
mem_fragmentation_ratio |
internal fragmentation — see below |
used_memory is a real number, not the RSS: it comes from GOBLIN.MEMORY-style accounting rolled up across every zset, hash, list, and the keyspace. Each arena maintains its live/dead byte counts incrementally, so the rollup reads counters rather than walking members or reparsing blobs. The shared compact-blob pool is tracked at its upstream boundary, which makes size-class rounding and retained slabs visible instead of silently reporting only requested payload bytes.
Fragmentation: a different meaning than Redis
mem_fragmentation_ratio is a Redis field, but Goblin Core measures the opposite end of the stack with it. Same name, different question — worth understanding before you alert on it.
Redis — external fragmentation (rss / used_memory)
Redis asks its allocator (jemalloc) for the bytes it needs. To serve those requests the allocator holds more than that resident — size-class rounding, per-arena free lists it hasn't returned to the OS, and so on. So in Redis:
used_memoryis what the application requested (roughly the live data),used_memory_rssis what the OS actually backs,mem_fragmentation_ratio = rss / used_memoryis the gap — overhead the allocator and OS impose below the application.
A ratio of 1.4 means jemalloc/OS holds ~40% more than the live data. Redis fights this with jemalloc active-defrag, which relocates allocations in the background. (A ratio below 1.0 is a separate alarm: part of the process has been swapped to disk.)
Goblin Core — internal fragmentation (used_memory / compacted size)
Goblin manages its own page-aligned mmap arenas for the bulk of its data, so it is the allocator for what matters. used_memory also includes the actual upstream capacity of the remaining compact-blob pool, not merely the live requests inside it. Process code, stacks, shared libraries, and general heap metadata still make RSS larger, so RSS remains the cross-engine benchmark number.
The fragmentation Goblin can actively compact lives above the allocator, inside its own arenas. Those arenas are append-structured: a deleted or overwritten member's bytes are orphaned — still mapped, no longer referenced — until a compaction rewrites the arena. Those mem_reclaimable_bytes are the actionable waste used by the ratio. Pool slack is visible separately as blob_pool_fragmentation_bytes and is not treated as compactable:
mem_fragmentation_ratio = used_memory / (used_memory − mem_reclaimable_bytes)
1.00— every allocated byte is live; nothing to reclaim.1.50— a third of the allocation is dead bytes a compaction would free.- It never drops below
1.00(dead bytes are never negative), so there is no "swapped out" reading to confuse it with.
The remedy differs, and deliberately: where Redis leans on the allocator's probabilistic background defrag, Goblin gives you a deterministic one — GOBLIN.OPTIMIZE key rewrites a key's arena tight. Full hashes instead auto-evacuate fragmented arena chunks in bounded maintenance steps once dead bytes exceed live (past a floor). mem_fragmentation_ratio is precisely the signal for when that's worth doing.
In one line
Redis's ratio asks "how much is my allocator wasting underneath me?"
Goblin's asks *"how much of what I've allocated is reclaimable dead weight I can
compact away?"* — one is the layer below you that you don't control, the other is
your own data layout, which you do.
What this looks like
> INFO memory # freshly loaded, tightly packed
used_memory:142778
used_memory_rss:3080192
mem_reclaimable_bytes:0
mem_fragmentation_ratio:1.00
# ... after deleting ~60% of a large zset ...
> INFO memory
used_memory:118018
mem_reclaimable_bytes:39000
mem_fragmentation_ratio:1.49 # a third of the allocation is now reclaimable
> GOBLIN.OPTIMIZE board
(integer) 58796 # bytes reclaimed
> INFO memory
mem_reclaimable_bytes:0
mem_fragmentation_ratio:1.00 # compacted back to tight
See also
GOBLIN.OPTIMIZE— the deterministic compaction the ratio signals for;GOBLIN.MEMORY— per-key memory breakdown.- Hard memory ceiling —
--maxmemory, accounting scope, and deterministic RESP/SBE OOM replies. - Goblin extension commands, and the memory benchmarks (RSS is the headline there).