Three Cassandra panels refuse rather than report a zero
Table, index and storage statistics send no statement at all and carry the measurement behind the refusal, because no honest count or size exists to publish.
On 21 August 2026 the monitoring Tables tab, connected to Apache Cassandra 5.0.9,
read Tables 0 over 0 rows, Size 0 B over Total, and Vacuum 0 over
OK. In the same frame, on the same connection, the Overview tab read
Tables 6 / 2 indexes out of system_schema. Nothing had failed. A panel with
no data had drawn the only number it had a slot for, and every one of those zeros
claimed a measurement.
A row count and a size would each have to come from a catalog read on this engine, and the case for refusing the panel outright is contained in what those two reads actually measure.
What the estimate table actually counts
system.size_estimates is the table a browser reaches for. It publishes 17 rows
per table, one per token range, refreshed every five minutes. It does not count
rows. It estimates partitions, from flushed SSTables only - two
departures from the number a reader assumes they are looking at, both measured
against 5.0.9:
| Table | Real rows | size_estimates sum |
Error |
|---|---|---|---|
| Just loaded, not yet flushed | 500 | 0 | everything |
| One partition per row | 500 | 525 | 5% high |
| 10 partitions x 50 clustering rows | 500 | 143 | 71% low |
The clustered shape, which is the one the data model encourages, is out by more than two thirds. Nothing on screen tells the reader which shape they are looking at, so no caveat beside the figure would make it usable: the error is not a margin but a function of a modelling decision the panel cannot see.
SELECT COUNT(*) is exact, and it is a full scan of the ring. A user can type one;
nothing in the tree or the panels does. There is a second reason beyond cost:
measured on a node with a 10 ms read timeout,
SELECT COUNT(*) FROM probe.customers was the single statement that returned a
server read timeout while every single-partition read succeeded.
What the disk usage view actually rounds to
system_views.disk_usage is the size half, and it reports whole mebibytes.
Measured: 1 MiB for a table holding 19,476 bytes, and 0 MiB for a table
holding 500 rows that had not yet been flushed.
A byte figure derived from that is wrong by up to 50 times, and wrong upward, which is the direction that gets a capacity decision made; summing it per keyspace multiplies the rounding.
So, the boundary. No row count and no size is reported anywhere on this engine.
The estimate table counts partitions per token range from flushed data only, and
the disk usage view reports whole mebibytes, so both are withheld rather than
approximated. There is no rowCount and no size on any table in the tree,
databaseSize reads N/A, and databaseSizeBytes is omitted from the payload
rather than set to zero.
Omitting rather than zeroing matters down to the field: the Storage tab read that
value as ?? 0, so a zeroed field produced a 0 B total and divided a 0.0%
breakdown out of it. With the field absent, its two size cards read N/A and the
breakdown is replaced by a sentence.
Refusing versus answering empty
An empty array is an answer: the engine was asked, looked, and found nothing. That is a different fact from this engine has no such measurement, and on a monitoring surface the difference is the message.
getMonitoringData reads seven panels independently. A refused panel is left out
of the payload with the engine’s own sentence recorded against it, and the
unavailable-panel component renders that sentence where the figures would be.
Until this changed, three panels answered [] for questions they could never
answer, and two more did on builds with no system_views keyspace. The
browser showed the cost: on ScyllaDB the Sessions tab read Active 0 / Idle 0 /
Wait 0 / Sessions (0) / No active sessions found for a question that build
cannot be asked at all.
Three panels are refused on every Cassandra build, each carrying the measurement behind the refusal:
| Panel | Why it sends nothing |
|---|---|
| Table statistics | A count would come from size_estimates (143 for a 500-row clustered table) and a size from disk_usage (1 MiB for 19,476 bytes) |
| Index statistics | system_schema.indexes names an index, its table and its target column, all already in the tree, and nothing in CQL reports a secondary index’s size or usage |
| Storage statistics | disk_usage, max_partition_size and max_sstable_size are all whole mebibytes per table |
None of the three sends a statement: the refusal is a declared property of the engine, not the outcome of a failed read.
One panel stays empty on purpose. Slow queries: no aggregate of finished statements is readable from CQL, and the slow-query threshold writes to the node’s log file rather than to a table, so the panel carries that sentence as its empty state.
The Vacuum card applies the same rule to a capability rather than to data. It
reads N/A over Not supported instead of a green OK for an operation this
engine does not have: compaction, cleanup, flush and repair are nodetool
commands over JMX, not statements a session can send.
What the panels that do answer read from
Refusing this much is only defensible if the rest are real reads:
- Version and uptime from
system.local:release_version, andgossip_generationcompared againsttoTimestamp(now())so the arithmetic uses the server’s clock, and that generation is a start time by measurement against container start times rather than by its name. - Table and index counts from
system_schema, scoped to the pinned keyspace. - A connection count from
system_views.clients- omitted, not zeroed, when the read is refused: a successfulCOUNT(*)always returns exactly one row, so an empty result set is the signature of the degradation. - One performance field,
cacheHitRatio, from the key-cache row ofsystem_views.caches. The rest are omitted: Cassandra publishes per-table percentiles rather than cluster rates, so there is no queries-per-second figure, and it keeps no deadlock counter at all. Those cards readN/Abeside Not measured rather than drawing a flat line along zero. - Active sessions from
system_views.queries: a thread id, the task text and two microsecond readings. No user, no keyspace, no client address, so user and database are reported asunknownrather than borrowed from the connected role.
That is the capability declaration at work: a control renders from what the provider says it can answer.
A structural probe instead of matching an error message
The two panels whose only source is system_views must know whether the keyspace
exists before reading it. The first implementation asked by sending the
read and inspecting the refusal. Through the driver, all of these arrive as a
ResponseError with code 8704, keyspace and table properties undefined:
| Sent | Server | Message |
|---|---|---|
system_views.clients |
ScyllaDB 2026.2.4 | Keyspace system_views does not exist |
system_views.cliets |
Cassandra 5.0.9 | table cliets does not exist |
system_viewz.clients |
Cassandra 5.0.9 | keyspace system_viewz does not exist |
Reading the sentence works until a release rephrases it, at which point five
panels go dark and no test notices. The discriminator is now a catalog read:
system_virtual_schema.keyspaces, sent once per successful connect, measured at
4.5 ms on 5.0.9 and 1.6 ms on ScyllaDB. The catalog choice was itself a
measurement: a virtual keyspace appears in neither engine’s
system_schema.keyspaces, so keying on the ordinary catalog would have blanked
all five panels on the engine that answers them. What comes back is not a boolean
but the reason, because a missing catalog, a catalog that lists no
system_views, and a refused grant send a reader to three different places. On a
build without the keyspace, a monitoring refresh sends three fewer statements
than the catch-the-refusal version did.
ScyllaDB is where the rule gets exercised. It has no system_views keyspace at
all: the editor, the object browser and every column and index read work, and the
panels fed by a virtual table say why they cannot. A zero would
have been easier to render, and would have been a lie about a measurement nobody
took.