This page describes Calíope 1.7, the version I am building right now. 1.6 is on the App Store for the Mac, iPad, iPhone, Apple Watch, Apple TV and Apple Vision Pro. The changelog says which version each feature landed in.
Adds an “Open in Calíope” button to every topic. It only works with the app installed.
How to run CQL, which consistency level to pick, how pages and tracing arrive, what Stop does, and what CQL doesn’t have.
Where it is: Workspace › Tools › CQL Query
Applies to:CassandraScyllaDB
The CQL Query sends the server what you type, over the open session. Run (⌘↩) runs the whole text statement by statement, or only the selection if there is one. A BEGIN BATCH … APPLY BATCH goes whole, and a ; inside a literal, a $$…$$ block or a comment doesn’t split. The first statement that fails stops the rest, and the error says which one (“Statement 2: …”). A USE changes the session’s keyspace, and the bar shows it.
The consistency level
Every piece of data lives on several replicas, and the consistency level says how many of them must answer before the server replies. You pick it in Consistency, and it applies to what you run next:
- LOCAL_ONE, the default, waits for one replica in the local data center: it’s the fastest, and a read may not see a recent write yet.
- ONE, TWO and THREE wait for that many replicas, wherever they are.
- QUORUM waits for a majority of the replicas, LOCAL_QUORUM for a majority in the local data center and EACH_QUORUM for a majority in each one. A write and a read at QUORUM always meet on some replica.
- ALL waits for all of them, and fails as soon as one is down.
- ANY only works for writes, and SERIAL and LOCAL_SERIAL are for lightweight transactions (IF NOT EXISTS, IF …).
If there aren’t enough live replicas, the server answers with an error that says how many were needed and how many there were. View solution takes you to the DBA Handbook chapter that explains the error: the consistency one for this or a timeout, the roles and permissions one for an Unauthorized, and the common errors one for the others it explains, a syntax error included.
Pages
A SELECT arrives in pages, and Rows per page sets how many rows each one asks for: 200 unless you change it. Next page fetches the next one while the server says there may be more; sometimes the last one arrives empty, and then it says so: “There are no more rows.”.
Tracing
With Trace on, the server records where each statement went, and the footer carries the link to its trace.
What you see
The grid writes each value as cqlsh does, and each column header carries its type. The JSON view runs a SELECT again as SELECT JSON and shows what the server returns; for any other statement there’s no JSON. Server warnings appear in a strip, “Server warnings”: Cassandra warns, for example, about a count(*) without a partition key, and ScyllaDB doesn’t. The footer gives the rows, the time and how many statements ran.
What writes INSERT, UPDATE, DELETE, TRUNCATE, CREATE, ALTER, DROP, GRANT, REVOKE and a BATCH first go through the session’s environment label guard, which may ask for confirmation. A write says “Applied. CQL doesn’t report how many rows it wrote.”: the protocol doesn’t return how many rows it touched.
Stop
While it runs, Stop (⌘.) stops waiting for the answer, and the connection stays open. But the protocol has no way to cancel on the server: whatever has started, the server finishes. A stopped UPDATE may have been applied.
What CQL doesn’t have
There’s no EXPLAIN and no multi-statement transactions: a logged BATCH is applied whole or not at all, but it doesn’t isolate whoever reads in the meantime, and tracing is the closest thing to a plan. A query that doesn’t filter by the partition key is rejected by the server unless it carries ALLOW FILTERING, which makes it read every partition: on a large table that’s expensive, and the answer is almost always another table with another key.
The schema tree, where the DDL comes from, the row sample, and what can and can’t be edited by primary key.
Where it is: Workspace › Tools › Keyspaces
Applies to:CassandraScyllaDB
The browser reads system_schema and shows each keyspace with its tables, materialized views, types, indexes, functions and aggregates. A keyspace is read when you expand or select it, not all at once, and an empty category isn’t drawn. Filter by name filters the tree by name. The keyspaces that belong to the server itself (system*, and audit in ScyllaDB) go last, collapsed, under “Server keyspaces”.
The partition key
In Cassandra the primary key has two parts. The partition key decides which nodes each row lives on: rows with the same partition key live together, and an efficient query gives it in full. The clustering columns order the rows inside the partition, in the order the table declares (ASC or DESC). The rest are regular columns, and a static one keeps one value per partition, not per row. Structure shows each column with its role, its position and its order, the full primary key and the table’s properties.
DDL
The server writes the DDL: it’s requested with DESCRIBE, which Cassandra and ScyllaDB accept for keyspace, table, view, type, index, function and aggregate, and the panel says it came from there. A table’s DDL is followed by its indexes and its views. If a server doesn’t accept DESCRIBE, the DDL is rebuilt from system_schema and the panel says that too; functions and aggregates aren’t rebuilt. Copy DDL copies all of it.
Rows
The sample reads 100 rows per page, with no WHERE and no ALLOW FILTERING, in the order the server walks its partitions, which isn’t insertion order. Next page fetches the next one and Reload rows starts over. Show the partition token adds the token() column, the number that decides which node each partition lands on, and Write time and TTL of adds writetime and ttl for a regular column: when the value was written, in microseconds, and how many seconds it has left before it expires. It doesn’t work for a key column, a counter or a non-frozen collection.
Editing rows Insert row…, above the grid, and on each row the “…” menu with Edit row… and Delete row… always write by the whole key, partition and clustering, and with a lightweight transaction: inserting is INSERT … IF NOT EXISTS, so a key that already exists isn’t replaced; saving is UPDATE … IF EXISTS and deleting is DELETE … IF EXISTS, and if the row is no longer on the server you’re told. Saving only writes what changed, and No value (null) clears the cell. What you type is converted to the type the server declares before anything is sent: dates and times in UTC (2026-01-31 10:00:00), a blob in hex (0x…), a duration like 1h30m and a decimal with a point or a comma. After writing, the row is read back and changes in place, without reloading the page. The delete alert names the whole key.
ScyllaDB
In ScyllaDB 2025.1 a keyspace with tablets does not allow lightweight transactions, and saving fails with the server’s message (“LWT is not yet supported with tablets”). A keyspace created with AND tablets = {'enabled': false} is edited as in the other versions.
What isn’t edited here
A collection, a UDT, a tuple, a custom type and a static column are changed in the CQL Query, and the sheet says so. Nor can you edit a counter table, which only allows adding or subtracting, a server keyspace, or a table whose key has a type that can’t be typed on one line. There’s no TRUNCATE and no delete by filter: that’s what the CQL Query is for. With an environment label that’s “read-only”, nothing that writes is drawn, and with one that “confirms writes”, the guard asks before every write.
Which nodes it shows and where they come from, what the ring share is, how a node is known to be down, and what’s left to nodetool.
Where it is: Workspace › Tools › Cluster
Applies to:CassandraScyllaDB
The Cluster shows the nodes known to the node your session is connected to, the coordinator: its own row from system.local and those of its peers, from system.peers_v2 in Cassandra (or system.peers if it doesn’t have it) and from system.peers in ScyllaDB. The peers aren’t asked anything: what you see is what the coordinator knows about them. At the top are the cluster name, the partitioner, how many nodes are up —with the ones that aren’t named one by one— and whether they all publish the same schema version; if they don’t, a schema change hasn’t reached all of them yet.
The ring
Cassandra and ScyllaDB spread data over a ring of tokens: each row lands on the token its partition key gives, and each node owns the ranges that end at its tokens (the Tokens column says how many it has). Ring share is the part of the ring covered by each node’s tokens without counting replicas, the same figure nodetool status gives for a keyspace with replication factor 1. Without a keyspace, nodetool gives the effective share, which counts the copies: with three nodes and factor 3, 100% for each one. It’s only computed with Murmur3Partitioner, the default partitioner; with any other, the column stays blank and the note says so.
Status
In ScyllaDB the status comes from system.cluster_status. In Cassandra it comes from system_views.gossip_info, which has a catch: a node that shuts down cleanly says goodbye and shows as Down, but one that crashes without saying goodbye stays NORMAL. So it’s read twice, a few seconds apart, and if its heartbeat didn’t advance it shows as Down: no heartbeat. A node that’s joining, leaving or moving shows as in transition, with the server’s word. And a peer the coordinator has no status row for is Unknown: a node that couldn’t be seen isn’t claimed to be down. Every status carries its symbol and its name, not just a color.
Refresh
Nothing reloads by itself, because each read waits those seconds to measure the heartbeat: read again with Refresh. If a read fails and there was an earlier one, the earlier one stays on screen and you’re told it isn’t current.
The version in ScyllaDB
ScyllaDB peers publish a Cassandra compatibility version, not their own: the column shows the coordinator’s, read from system.versions, and a note says so.
What isn’t here
Cassandra’s replication is the ring itself, and it’s shown in this tab: there’s no separate replica monitor. The Cluster only reads: what’s the job of nodetool —repair, cleanup, decommissioning or moving a node, snapshots—, of JMX or of ScyllaDB’s REST API is left out.
Keywords: cassandra, scylladb, cluster, ring, node, token, coordinator, gossip, heartbeat, nodetool, owns, data center, rack, schema version
What the connected node says about itself —version, caches, compactions, disk, latencies and settings—, where it comes from in each product, and what’s left to nodetool.
Where it is: Workspace › Tools › Node
Applies to:CassandraScyllaDB
The Node shows what the node your session is connected to says about itself, read from its virtual tables: system_views in Cassandra and the system tables in ScyllaDB. Everything is that node’s: the peers aren’t asked anything, and in a cluster each node has its own caches, its own compactions and its own disk. To see another one, the session has to connect to it.
At the top Version is the server’s: the one from system.local in Cassandra and the one from system.versions in ScyllaDB, which is its own and not the Cassandra compatibility version. Next comes a card per cache, with its hit rate, how many entries it holds and how much of its capacity it uses: in Cassandra the key, row and counter caches, from system_views.caches; in ScyllaDB the Row cache, from the cache group of system.runtime_info. A cache with capacity 0 is turned off in the server’s configuration —Cassandra’s row cache ships that way— and the card says so. Compactions running counts the ones running now: in Cassandra they come from system_views.sstable_tasks, with their progress; in ScyllaDB, from system.compactions_in_progress, which gives no progress but does say how many SSTables go in. If there are any, their table appears below.
The sections
Below is a table per section, and each product declares its own: what the server doesn’t publish isn’t drawn.
- Thread pools (Cassandra only, system_views.thread_pools): each pool’s active, pending and blocked tasks. Pending and blocked are the ones that tell you whether the node is keeping up.
- Latency per table (Cassandra only, coordinator_read_latency and coordinator_write_latency): read and write p50 and p99, in milliseconds. It’s the latency this node sees as coordinator of the requests it receives, not that of its replicas.
- Size per table: in Cassandra, the disk space from system_views.disk_usage, rounded up to whole MiB; in ScyllaDB, the estimate from system.size_estimates summed per table, which gives 0 for a table whose data hasn’t been written to disk yet and includes the tables ScyllaDB creates for indexes and for lightweight transactions (…$paxos), because they really take up space.
- Large partitions: in Cassandra, the largest partition of each table, from system_views.max_partition_size; in ScyllaDB, only those that crossed the server’s threshold when compacted, from system.large_partitions, so empty is normal.
- Compaction history: the 100 most recent compactions from system.compaction_history, with what they read and wrote, and the total alongside.
- Runtime figures: the counters the server publishes, with their names: system_views.cql_metrics in Cassandra and system.runtime_info in ScyllaDB, which also carries how long it’s been up, as the server writes it.
- Settings: the configuration the node runs with, from system_views.settings in Cassandra and from system.config in ScyllaDB, which also says where each value comes from. Filter settings narrows the list by name.
Size, partitions and latencies leave out the server’s keyspaces; the history doesn’t. If a section can’t be read, you’re told which one and why, and the others carry on.
Refresh
Nothing reloads by itself: read again with Refresh. If a read fails and there was an earlier one, the earlier one stays on screen and you’re told it isn’t current.
What isn’t here
The Node only reads. What’s the job of nodetool —flushing memtables, starting or stopping a compaction, changing the log level, rebuilding indexes—, of JMX or of ScyllaDB’s REST API is left out, and a setting isn’t changed from here: it’s changed in the server’s configuration.
Keywords: cassandra, scylladb, node, version, cache, hit rate, compaction, sstable, thread pool, latency, disk, size, large partition, settings, configuration, uptime, nodetool, jmx
Which connections the connected node has open, with what user, driver, protocol and TLS, which ones are Calíope’s, and why they aren’t disconnected from here.
Where it is: Workspace › Tools › Clients
Applies to:CassandraScyllaDB
Clients shows the connections open on the node your session is connected to, as the node itself publishes them: system_views.clients in Cassandra and system.clients in ScyllaDB. Only that node’s: each node publishes its own, and the ones a client has open with another node of the cluster don’t appear here. To see them, the session has to connect to that node.
The columns
- Client: the address and port the connection comes from, with the host name below when the server knows it and it isn’t the address itself.
- User: the user it authenticated as.
- Driver: the name and version the driver declared when it connected. cqlsh introduces itself as “DataStax Python Driver” in Cassandra and as “ScyllaDB Python Driver” in ScyllaDB, and opens two connections: a control one and another for the queries.
- Protocol: the native protocol version the connection negotiated.
- TLS: whether the connection is encrypted and, when the server says so, with which TLS version.
- Stage: the connection’s stage, written the way the server writes it (ScyllaDB, in capitals: READY).
- Requests and Keyspace (Cassandra only): how many requests the connection has made and the keyspace in use, which shows a dash if it never ran USE.
- Shard (ScyllaDB only): the node’s shard that serves the connection.
A column a product doesn’t publish isn’t drawn, and a value the server doesn’t give shows a dash.
Calíope
Calíope’s connections carry their mark —a glyph and the name Calíope, not just a colour— and come first; the rest follow by address and port. Calíope introduces itself to the server with the name “Caliope”, and that’s how they’re recognised: all of the app’s are marked, this window’s and those of any other device with Calíope open against the same node, because the address isn’t enough to tell which one is this window’s (behind a tunnel or a NAT, they all come from the same one).
Filtering Filter by address, user, driver… narrows the table by address, port, host name, user, driver and its version, keyspace or stage, and the summary says how many of the total are shown.
Refresh
The list doesn’t reload by itself: read it again with Refresh. If a read fails and there was an earlier one, the earlier one stays on screen and you’re told it isn’t current.
What isn’t here
Clients only reads. Calíope doesn’t disconnect anyone, because CQL has no statement to close another client’s connection, and the tab’s footer says so. That’s left to nodetool, JMX or ScyllaDB’s REST API.
How to read a Cassandra or ScyllaDB trace, what each product publishes besides —ScyllaDB’s slow queries, what is running now in Cassandra— and how to switch ScyllaDB’s slow log on.
Where it is: Workspace › Tools › Traces
Applies to:CassandraScyllaDB
Traces shows what the server records about the requests it is asked to trace, and what each product publishes besides: slow queries in ScyllaDB and what is running now in Cassandra. The tabs depend on the product, and a tab the product doesn’t have isn’t drawn.
Traced sessions (both)
A trace is what the coordinator and each replica recorded while serving one request. The server keeps it in system_traces.sessions (one row per request) and system_traces.events (one per step). Only what you ask for is traced: switch on Trace in the CQL Query before running, and the result’s footer brings a button with the trace’s identifier that opens it here directly. Traces expire after 24 hours.
The list shows the most recent sessions. In ScyllaDB they’re found through its time index (sessions_time_idx), going back from the server’s clock, and the total isn’t read. Cassandra has no such index: the table is read up to a ceiling and sorted here, and if the read hit the ceiling the screen says so, because there may be more recent ones that weren’t read. In ScyllaDB every slow query also writes its session, so the list holds more than the ones you asked to trace.
The time of each row (Start) opens its trace: start, duration, command, coordinator, client, the user (only ScyllaDB records it) and, when the server records it, the consistency level; the statement, with Open in the CQL Query; and the events in order. Each event gives Elapsed —the microseconds its node had been working on the request—, a bar proportional to that time, the activity, Node and the thread. Two nodes don’t share a clock: each event’s time is its own node’s, and comparing two nodes by that column misleads.
The server writes the trace while the request is still going: a trace you just asked for may arrive without events or with its duration unfinished. When it opens it’s read again a few times, half a second apart, until it brings its duration; if it doesn’t, you’re told, and reading it again with Refresh a few seconds later is enough. Back to the traced sessions goes back to the list.
Slow (ScyllaDB only)
ScyllaDB records in system_traces.node_slow_log every query that goes over a threshold, with its start, duration, statement, tables, user, and the node and shard that served it. They’re found through node_slow_log_time_idx, from the last 24 hours. The time opens that query’s trace —ScyllaDB writes one for each slow query— and the button at the end of the row takes it to the CQL Query.
The log comes switched off, and neither the threshold nor whether it’s on can be read over CQL: they belong to ScyllaDB’s REST API (port 10000). That’s why the tab shows the most recent entry and the shortest one read, which is all CQL lets you know. To switch it on with a threshold of 500 µs (the threshold is in microseconds), and to read how it’s set:
curl -X POST "http://<node>:10000/storage_service/slow_query?enable=true&threshold=500"
curl http://<node>:10000/storage_service/slow_query
The second one returns something like {"enable": true, "ttl": 86400, "threshold": 500, "fast": false}. It’s switched on node by node, it isn’t in scylla.yaml, and restarting the node switches it off. Calíope doesn’t do it for you because it only speaks CQL: the REST API is ScyllaDB’s administration door, separate from CQL and usually unreachable from outside the cluster, and what is the job of nodetool, JMX or the REST API stays outside.
Running (Cassandra only)
What is running now on the session’s node, from system_views.queries: the thread, Queued and Running in microseconds, and the request. The read of this list is one of them, and each node publishes only its own. Cassandra doesn’t publish its slow queries over CQL: it writes them to its text log from slow_query_log_timeout on, which the footer gives as the node has it.
Refresh
Nothing reloads by itself: the tab is read again with Refresh, and the bar says when the last read was. If a read fails and there was an earlier one, the earlier one stays on screen and you’re told it isn’t current.
All of this only reads: traces and the slow log are written by the server, and they expire by themselves.
What a Cassandra or ScyllaDB role is, where the list and the detail come from, on which resources permissions are granted, what gets written and how the password travels, and why there are no roles without authentication.
Where it is: Workspace › Tools › Roles
Applies to:CassandraScyllaDB
Roles shows the server’s roles and what each one can do, and lets you create them, change them, grant them permissions and delete them.
What a role is
A role is both a user and a group. If it has Can log in set and a password, someone connects with it; if not, it gathers permissions and is granted to other roles, which inherit what it has. A Superuser role can do everything, even if the list doesn’t name each permission, and the detail says so.
No authentication, no roles
Roles exist only when the server asks for a user and password on connecting (PasswordAuthenticator). With AllowAllAuthenticator, which is what Cassandra and ScyllaDB ship with, anyone gets in without credentials and there is nothing to manage: the tab says so with its reason and asks the server nothing, since it would answer with an error.
The list and the detail
The list comes from LIST ROLES, the same in both products: the name, Superuser, Can log in and, in Cassandra only, Data centers. The filter narrows by name. When you pick a role, the detail shows Own permissions, from LIST ALL PERMISSIONS OF; apart, Inherited from the roles it belongs to, each with the role it comes from; and Belongs to, from LIST ROLES OF. What is inherited isn’t revoked here: it is removed on the role it comes from, or the role is taken out of that group.
Resources and permissions
A permission is granted on a resource: All keyspaces, A keyspace, A table, All roles, A role or All functions. The permissions are ALL PERMISSIONS, SELECT, MODIFY, CREATE, ALTER, DROP, AUTHORIZE, DESCRIBE and EXECUTE; Cassandra adds SELECT_MASKED and UNMASK, for masked columns, which ScyllaDB doesn’t have. Not every permission applies to every resource, and the server rejects a pair that makes no sense with its own message.
What gets written New role… creates a role with its name, its password and whether it has Can log in or is Superuser; without a password the role can’t log in even with LOGIN. Change… sends only what you change (ALTER ROLE); Cassandra won’t let a role’s password change twice in less than 5 seconds. Grant permission… grants a permission on a resource, and Add to a role… grants membership of another role; the button at the end of each row revokes it. Delete role… deletes the role with its permissions and memberships. Removing something —deleting or revoking— first asks for a confirmation that names the role.
Everything that writes goes through the connection’s protection: with a read-only environment label nothing that writes is drawn, and with one that confirms, each change asks before it is sent.
The password
CQL doesn’t let the password be sent apart: it travels inside the statement (CREATE ROLE … WITH PASSWORD = '…') to the server, which keeps it as a hash, not in clear. Over the network it is encrypted only if the connection uses TLS. In Command Log the statement is kept with the password covered.
Refresh
Nothing reloads by itself: the list is read again with Refresh, and the bar says when the last read was. If a read fails and there was an earlier one, the earlier one stays on screen and you’re told it isn’t current.
Keywords: cassandra, scylladb, roles, role, permissions, users, grant, revoke, create role, alter role, drop role, list roles, superuser, login, password, authentication, passwordauthenticator, allowallauthenticator
What a CQL report is: a single SELECT with :name parameters, prepared and bound by each column’s type, that reads every page up to the cap.
Where it is: Workspace › Tools › Reports
Applies to:CassandraScyllaDB
In a Cassandra or ScyllaDB session, New report creates a CQL report: its text is a single SELECT statement, and it starts as SELECT * FROM demo.productos LIMIT 100. Any other statement —an INSERT, a USE—, two statements or an unnamed ? are rejected before anything is sent to the server.
Parameters
A parameter is written :name in the text and declared with Declare parameters. Calíope turns each :name into ? and prepares the statement: the server says the CQL type of each marker, and the value is written by that type, never inside the text. A uuid goes as a uuid and a decimal exactly, without passing through floating point; a value that doesn’t fit its column is rejected with the parameter’s name. If the server counts a different number of markers than there are parameters, the report fails and gives both numbers.
What is read
Every time you press Run, Calíope reads every page of the result, 200 at a time, up to the report’s row cap, and says so if it was cut. Stop stops it between one page and the next. Column types come from what the server declares about the result: integers, decimal, float and double as numbers, timestamp as a date and time in UTC, date as a date, blob as binary and boolean; collections, user-defined types, uuid and inet come out as cqlsh writes them.
The rest
What a report reads is not kept in the Command Log: it’s the tool reading, not a statement from the CQL Query. Export and History work as in any report. A CQL report only runs in a Cassandra or ScyllaDB session, takes no multi-statement script, and Schedule doesn’t help with it: only reports on a relational database can be scheduled.