TessariDBDocs
  • 0.34.2-betalatest
  • 0.34.1-beta
  • 0.34.0-beta
  • 0.33.4-beta
  • 0.33.3-beta
  • 0.33.2-beta
  • 0.33.1-beta
  • 0.33.0-beta
  • 0.32.0-beta

The web console

A panel compiled into the binary, in seven places: Run, Topics, Cluster, Access, This node, Backup and Vault.

A node started with --http serves a console at the root of that address:

$ tessaridb ./data --serve 0.0.0.0:7654 --http 0.0.0.0:8000

Then http://localhost:8000/. There is nothing to install and nothing to fetch — the page, its stylesheet, its one script and its icon are compiled into the binary. That is the point: a node on a network with no route out still has an interface, and a single remote reference — a CDN, a web font, a framework — would quietly take that away.

It is a client, and it has no private door

Everything the console does, it does through routes anything else can use: POST /session and DELETE /session to sign in and out, POST /password to change your own, POST /script to run one, GET /watch to follow a table, the /vault routes for the Vault tab, and /health, /ready and /metrics for the node tab. There is no endpoint only the console can reach.

That is a constraint rather than an observation. A console feature that needed a private route would be a capability the API does not have, and the API is the product surface. It also means every refusal you see in the panel is the store's refusal, in the store's own words — not a second permission system agreeing with the first.

Query

A script box and an answer pane. ⌘↵ or Ctrl↵ runs it.

Answers follow the same rule the terminal follows: records that share one flat set of fields are drawn as a table, anything else is drawn as JSON. Auto and JSON switch between the shaped answer and what actually came back, which is what you want the moment a rendering looks wrong.

Below it, Follow a table opens the change feed over a WebSocket. from is a position in the log rather than a moment in time, so 0 replays everything the log still holds.

Topics

Every topic in a namespace and database you choose — both picked from what the store says exists — with how many messages each holds, its last position, what it keeps (a time, a size, or both), how many readers and groups it has and how far behind the furthest of them is. Choose one and it shows, beside the bytes it holds when it is bounded by size:

  • its readers, each with its position and lag;
  • its groups, each with the last message handed out, the committed position, lag, messages in flight, width, redeliveries, dead letters and deadline — the figures INFO FOR TOPIC reports;
  • its messages, a page at a time from any position. Reading here uses READ FROM … AFTER n with no consumer, so it moves nobody's position.

It creates a topic (how long to keep, at most how many bytes to keep and the largest message, each optional) and a group (deadline required; in flight, deliveries and a dead letter optional), moves a group to another position, and removes a group or a topic. Each form says in a sentence what pressing its button will do, with the numbers from the topic's last answer — "forgets 2 messages it holds in flight", "hands out again 3 messages it already handed out". The three that lose something ask for the name to be typed again and take a reason, which the statement log keeps beside the statement.

A topic, a group, a namespace and a database are names the store reads as part of the statement, so the console checks each one against the same patterns the clients use before it sends anything, and refuses one that does not fit rather than quoting it.

Users

The listing shows the tenancy you administer — an owner of a space sees that space's users, and an owner with no space at all sees everyone. It refuses rather than narrowing where it cannot answer, so a short list is a short tenancy and never a filtered one. Clicking a row fills the forms below it.

Three things you can do, each of which is a statement:

runs
Define oneDEFINE USER … ROLE … PASSWORD '…'
Change oneALTER USER … SET PASSWORD '…' or SET ROLE …
Remove oneDROP USER …

Each shows the statement before it runs, with a password drawn as '…' — the one place a credential would otherwise sit in plain view on somebody's screen.

Change one takes one field at a time, because a password rotation must not be able to reset a role by restating it wrongly. A space cannot be changed at all: widening somebody's reach is the one change an owner of a part could use to reach the whole, so it is not offered.

Remove one asks for the name twice. The user's grants go with them, a new user of the same name inherits none of them, and if it was the last owner of the whole node there is no way back in — a closed store has no door from outside.

Every one of these is bounded by what you administer, and the node's refusal is shown as the node said it.

Node

INFO FOR NODE alongside /health, /ready and /metrics — identity and settings, whether the node is well, whether it is taking traffic, and the counters a monitor would scrape. Read each time the tab is reached rather than on load: a console left on another tab should not be polling a node nobody is looking at.

Cluster

A map of the cluster as this node sees it: itself and every peer it has been told about, each with the roles it holds, the address it answers on, and — for this node — its lease, its epoch, how many followers are collecting from it and how far the furthest of them is behind, and, on a follower, where it stands against its upstream (copying, catching up, in sync, copy failed or stranded — see Bounding the log). It is read from the node rather than from anything standing beside it, and the answer it was drawn from is one disclosure away.

Opening a node gives you its drawer, over the map rather than instead of it, because a decision about one node during a failover is being made relative to the others.

What the drawer can do, and what it says it cannot. This node's roles can be changed, and unticking every role composes DEFINE NODE ROLES NONE — the drain. The drawer names what that costs before it offers the button: the node keeps its data, its identity and its place in the membership, and stops answering clients until a role is declared again. Where a membership row declares a role for this node it says that too, because there a local drain is an override the next open discards.

Handing leadership over is the one thing named and not offered. There is no HANDOVER, STEP DOWN or YIELD in the language, so a control for it would compose nothing — and a button that composes nothing is a button that lies about the one thing an operator opens the screen to do.

A peer's drawer amends its row with ALTER REPLICA <name> ROLES …, and never sends that peer this node's drain. Removing a peer is DROP REPLICA, behind its name typed again: a row that named a node removes that node for good, so the drawer says so before the button is live. A placed range moves with ALTER REPLICA <peer> LEADS <range>, run on the store's leader from the Query tab.

Building a cluster from here

From 0.21.0-beta the tab assembles a secured cluster end to end (Adding a node, Encryption in transit):

  • Declare the membership — every member, this node included, in one transaction. Each row carries its name, peer address, client address, its node id or a pinned certificate fingerprint, its subscription (REPLICATES, STORE unless you write another reach) and its roles. The transaction it will send is shown before it is sent, and this node's own roles are declared from its row afterwards. A row with neither id nor fingerprint waits for a join token.
  • A node waiting to join — started with a peer certificate and a seed — is told to set its roles to serving: a writable node stays its own authority and collects from nobody.
  • Certificates and trust lists the certificates this node presents, with their fingerprints and how long each has left (a warning inside fourteen days), the refused certificates, and the removed nodes.
  • Revoke a certificate composes REVOKE CERTIFICATE, live only once the first eight digits are typed again, and says when the fingerprint is this node's own or one a row pins.
  • Approve a joining node issues CREATE JOIN TOKEN for a waiting row and shows the token once; the statement log keeps a placeholder in its place.
  • Failover policy composes DEFINE FAILOVER with all five clauses; the built-in periods are the placeholders.

Every one of these is a statement the Query tab would run, recorded in the statement log and in the node's audit trail like any other.

What is still not drawn, and why. There is no replication-lag figure and no leadership shown for other nodes. Nothing pulls a replica forward on a timer, so a node that is not writing has no last collection to be measured from, and this node knows which lease it holds and never which lease somebody else holds. The tab says so rather than drawing a number it would have to invent.

Backup

Writes a backup into the node's backup folder — --backup-dir, or TESSARIDB_BACKUP_DIR in the image — as a snapshot (the default), a log or a script, of the whole store or of part of it: one namespace or database for a snapshot, any you name for a script. It runs BACKUP … TO '<name>', so the name may include subfolders and never replaces an existing file. Below it, Restore a script runs RESTORE SCRIPT FROM on a file in the same folder, beside what the store already holds. Both need a store-wide owner. See Backup and restore.

Vault

Two panes. The store's key shows whether the store is sealed and until when, and unseals, seals and changes the passphrase of the key every vault without a passphrase of its own opens with. One vault does the same for a vault declared with PASSPHRASE '…', named by namespace, database and vault. Both go through the /vault routes, so a passphrase typed here is never statement text, never in the statement log and never kept by the page: every passphrase field is a password field and is emptied once it is sent.

A third pane, Records in a vault, lists a database's vaults and a vault's record ids a page at a time, reveals one record on click — the store records the read before it answers — writes one field, and shows the audit trail. The record id and the written value travel as bound parameters, so the statement log shows $id and $value rather than either; a revealed value stays on the page until Hide or the next reveal. See Vaults.

Looking at one thing

The search field opens a sheet on a namespace, a database, a table or a record — a thing you look at and close, which leaves you where you were.

A record carries its history: what it became and when, newest first, read from the log the store already writes. Nothing is enabled to get it and nothing extra is written to produce it. If the read stopped at its budget before reaching the start of the log the sheet says so, because a list of five entries that might be all of them and might not is worth less than one that tells you which.

A namespace, a database and a table have no history here, and the sheet says why rather than showing an empty list: a catalog row is deliberately kept out of the log's change projection, so there is genuinely nothing recorded to draw.

Before you expose it

The console is served on the HTTP address, so it is HTTPS exactly when the node was given a certificate (Encryption in transit). Without one, credentials travel in the clear as they do for every other route here: bind it to a loopback address, reach it over an SSH tunnel, or put a proxy in front of it. The sign-in sheet says which of the two the page arrived over, read from the page's own address rather than assumed, and from 0.23.0-beta a console reached in the clear from another machine carries a warning across the top of every page — a page opened from localhost does not, because nothing it sends crosses a network. A console a browser trusts lists the three ways to serve it over HTTPS without a warning page.

Next