Clustering
Operations API for managing Harper's replication system. For an overview of how replication works, see Replication Overview. For sharding configuration, see Sharding.
All clustering operations require super_user role.
Add Node
Adds a new Harper instance to the cluster. If subscriptions are provided, it creates the specified replication relationships between the nodes. Without subscriptions or sendsTo / receivesFrom, a fully replicating system is created (all data in all databases). sendsTo / receivesFrom instead scope controlled-flow replication to the databases they name.
Parameters:
-
operation(required) — must beadd_node -
hostnameorurl(required) — the hostname or URL of the node to add -
verify_tls(optional) — whether to verify the TLS certificate. Set tofalsetemporarily on fresh installs with self-signed certificates. Defaults totrue -
authorization(optional) — credentials for the node being added. Either an object withusernameandpassword, or an HTTPAuthorizationstyle string -
retain_authorization(optional) — iftrue, stores credentials and uses them on every reconnect. Generally not recommended; prefer certificate-based authentication. Defaults tofalse -
revoked_certificates(optional) — array of revoked certificate serial numbers that will not be accepted for any connections -
shard(optional) — shard number for this node. Only needed when using sharding -
start_time(optional) — ISO 8601 UTC datetime. If set, only data after this time is downloaded during initial synchronization instead of the entire database -
subscriptions(optional) — explicit table-level replication relationships. This is optional (and discouraged). Each subscription is an object with:database— database nametable— table namesubscribe— iftrue, transactions on the remote table are replicated locallypublish— iftrue, transactions on the local table are replicated to the remote node
-
sendsTo/receivesFrom(optional) Added in: v5.1.0 Changed in: v5.2.0 — database-scoped controlled-flow entries for this node (see Controlling Replication Flow). Each entry is an object{ database?, excludeTables? }, or (less usefully) a bare string naming a peer, intended to authorize all databases for that peer rather than match a database name. An entry can also carry atarget(onsendsTo) orsource(onreceivesFrom) naming the one peer it applies to; an entry with neither matches any peer. Only the peer-agnostic object form —{ database?, excludeTables? }, with notarget/source— is reliable today. Harper's reciprocaladd_node_backregistration to the added peer carries the array over without rewriting each entry's peer reference for the new direction, so a bare string or atarget/sourcevalue that's correct for your side of the connection ends up wrong on the peer's copy, and the peer's send-authority check silently rejects the subscription — replication doesn't happen (harper-pro#710 -add_node_backdoes not rewrite peer-qualifiedsendsTo/receivesFromentries). Use config routes for anything that needs to be scoped to one peer. Unlike a config route'sreplicates.sendsTo/replicates.receivesFrom, which describes the local node's own direction, these describe the added node's perspective:sendsTolists what the added node sends (what this node receives from it), andreceivesFromlists what the added node receives (what this node sends to it). If bothsubscriptionsandsendsTo/receivesFromare provided,subscriptionstakes precedence andsendsTo/receivesFromare ignored. To replicate all databases while excluding specific tables, use a wildcard entry (excludeTableswith nodatabase).Note: Because these entries aren't scoped to one peer, the resulting
hdb_nodesrecord for the added node isn't restricted to the connection that created it — any other node that also holds the listed database(s) can match against it too. This also doesn't change how the local node advertises itself to the rest of the cluster: a node's own directional (non-mesh)hdb_nodesself-record is derived solely from itsharper-config.yamlroutes, so replicatingsystemwithout collapsing to a full mesh requires directional routes in config, not justadd_node/set_nodescoping (see Replicating thesystemdatabase with controlled flow). Nor is the scoping durable: each node rewrites its ownhdb_nodesrow from itsharper-config.yamlroutes when it restarts or reloads components, and that row replicates, so directional entries anadd_nodecall wrote onto the added node's row are superseded — a node with no directional routes goes back to advertising the legacy full-mesh record. Use config routes for any constraint that has to survive a restart or adeploy_componentreload.Changed in v5.2: these parameters have been accepted since v5.1.0, but they only gate replication as described above from v5.2 on. Before v5.2,
add_nodewrote a blanket "sends everything" flag onto the added node's registry record alongside the entries, which authorized this node to receive every database from that node and leftsendsTo'sdatabasescoping inert; in the other direction, the send-authority check honored areceivesFromentry only when it named both this node and the database, so the peer-agnostic object form recommended above did not authorize sending. As of v5.2 the blanket flag is gone — replication from the added node is limited to the databases itssendsToentries name — and an entry that omits the peer or the database is treated as a wildcard by both checks. If you already calladd_node/set_nodewithsendsTo/receivesFrom, re-check that scoping before upgrading — it can change in either direction. Databases that were replicating only because of the blanket flag stop replicating on v5.2; conversely, a peer-agnosticreceivesFromentry that v5.1's send check ignored becomes an active wildcard on v5.2, so it newly authorizes any peer that subscribes for the database it names.
Request:
{
"operation": "add_node",
"hostname": "server-two",
"verify_tls": false,
"authorization": {
"username": "admin",
"password": "password"
}
}
Request (controlled flow, scoped per database):
{
"operation": "add_node",
"hostname": "server-two",
"sendsTo": [{ "database": "cardata", "excludeTables": ["raw_telemetry"] }],
"receivesFrom": [{ "database": "config" }, { "database": "system" }]
}
Read from the added node's perspective: server-two sends cardata (except raw_telemetry) to this node, and receives config and system from it.
Response:
{
"message": "Successfully added 'server-two' to cluster"
}
Note:
set_nodeis an alias foradd_node.
Update Node
Modifies an existing Harper instance in the cluster. Will attempt to add the node if it does not exist.
Parameters:
operation(required) — must beupdate_nodehostname(required) — hostname of the remote node to updaterevoked_certificates(optional) — array of revoked certificate serial numbersshard(optional) — shard number to assign to this nodesubscriptions(required) — array of subscription objects (same structure asadd_node)
Request:
{
"operation": "update_node",
"hostname": "server-two"
}
Response:
{
"message": "Successfully updated 'server-two'"
}
Remove Node
Removes a Harper node from the cluster and stops all replication to and from that node.
Parameters:
operation(required) — must beremove_nodehostname(required) — hostname of the node to remove
Request:
{
"operation": "remove_node",
"hostname": "server-two"
}
Response:
{
"message": "Successfully removed 'server-two' from cluster"
}
Cluster Status
Returns an array of status objects from the cluster, including active WebSocket connections and replication timing statistics.
Added in: v4.4.0 ; timing statistics added in v4.5.0
Parameters:
operation(required) — must becluster_status
Request:
{
"operation": "cluster_status"
}
Response:
{
"type": "cluster-status",
"connections": [
{
"replicateByDefault": true,
"replicates": true,
"url": "wss://server-2.domain.com:9933",
"name": "server-2.domain.com",
"subscriptions": null,
"database_sockets": [
{
"database": "data",
"connected": true,
"latency": 0.7,
"thread_id": 1,
"nodes": ["server-2.domain.com"],
"lastCommitConfirmed": "Wed, 12 Feb 2025 19:09:34 GMT",
"lastReceivedRemoteTime": "Wed, 12 Feb 2025 16:49:29 GMT",
"lastReceivedLocalTime": "Wed, 12 Feb 2025 16:50:59 GMT",
"lastSendTime": "Wed, 12 Feb 2025 16:50:59 GMT"
}
]
}
],
"node_name": "server-1.domain.com",
"is_enabled": true
}
database_sockets shows the actual WebSocket connections between nodes — one socket per database per node. Timing fields:
| Field | Description |
|---|---|
lastCommitConfirmed | Last time a receipt of confirmation was received for an outgoing commit |
lastReceivedRemoteTime | Timestamp (from the originating node) of the last received transaction |
lastReceivedLocalTime | Local time when the last transaction was received. A gap between this and lastReceivedRemoteTime suggests the node is catching up |
sendingMessage | Timestamp of the transaction actively being sent. Absent when waiting for the next transaction |
Configure Cluster
Bulk creates or resets subscriptions for any number of remote nodes. Resets and replaces any existing clustering setup.
Parameters:
operation(required) — must beconfigure_clusterconnections(required) — array of node objects following theadd_nodeschema
Request:
{
"operation": "configure_cluster",
"connections": [
{
"hostname": "server-two",
"verify_tls": false,
"authorization": {
"username": "admin",
"password": "password2"
}
},
{
"hostname": "server-three",
"verify_tls": false,
"authorization": {
"username": "admin",
"password": "password3"
}
}
]
}
Response:
{
"message": "Cluster successfully configured."
}
Cluster Set Routes
Adds routes to the replication.routes configuration. Behaves as a PATCH/upsert — adds new routes while leaving existing routes untouched.
Parameters:
operation(required) — must becluster_set_routesroutes(required) — array of route strings (wss://host:port) or objects withhostnameandportproperties
Request:
{
"operation": "cluster_set_routes",
"routes": [
"wss://server-two:9925",
{
"hostname": "server-three",
"port": 9930
}
]
}
Response:
{
"message": "cluster routes successfully set",
"set": ["wss://server-two:9925", { "hostname": "server-three", "port": 9930 }],
"skipped": []
}
Cluster Get Routes
Returns the replication routes from the Harper config file.
Parameters:
operation(required) — must becluster_get_routes
Request:
{
"operation": "cluster_get_routes"
}
Response:
["wss://server-two:9925", { "hostname": "server-three", "port": 9930 }]
Cluster Delete Routes
Removes routes from the Harper config file.
Parameters:
operation(required) — must becluster_delete_routesroutes(required) — array of route objects to remove
Request:
{
"operation": "cluster_delete_routes",
"routes": [
{
"hostname": "server-three",
"port": 9930
}
]
}
Response:
{
"message": "cluster routes successfully deleted",
"deleted": [{ "hostname": "server-three", "port": 9930 }],
"skipped": []
}