Replication Overview
Harper's replication system is designed to make distributed data replication fast and reliable across multiple nodes. You can build a distributed database that ensures high availability, disaster recovery, and data localization — all without complex setup. Nodes can be added or removed dynamically, you can choose which data to replicate, and you can monitor cluster health without jumping through hoops.
Peer-to-Peer Model
Harper replication uses a peer-to-peer model where every node in your cluster can send data to and receive data from other nodes. Nodes communicate over WebSockets, allowing data to flow in both directions. Harper automatically manages these connections and subscriptions, so you don't need to manually track data consistency. Connections between nodes are secured and reliable by default.
Configuration
Connecting Nodes
To connect nodes to each other, provide hostnames or URLs in the replication section of harper-config.yaml. Each node specifies its own hostname and the routes (other nodes) it should connect to:
replication:
hostname: server-one
routes:
- server-two
- server-three
Routes can also be specified as URLs or with explicit port numbers:
replication:
hostname: server-one
routes:
- wss://server-two:9933
- hostname: server-three
port: 9933
By default, replication connects on the secure port 9933.
replication:
securePort: 9933
You can also manage nodes dynamically through the Operations API without editing the config file.
Gossip Discovery
Harper automatically replicates node information to other nodes in the cluster using gossip-style discovery. This means you only need to connect to one existing node in a cluster, and Harper will automatically detect and connect to all other nodes bidirectionally — unless those nodes advertise directional routes, as described below.
As of v5.2, this full-mesh, bidirectional auto-connect behavior applies to nodes with no directional routes. A node configured with directional routes advertises a constrained registry record instead, so a node that discovers it — and has no directional route of its own for it — does not open a replication subscription to it. Containment is a property of the discovered node's advertised record, not of the discovering node's configuration: configuring directional routes on one node does not stop that node from subscribing to a discovered peer that still advertises the legacy full-mesh record. See Controlling Replication Flow.
Data Selection
By default, Harper replicates all data in all databases. You can narrow replication to specific databases:
replication:
databases:
- data
- system
All tables within a replicated database are replicated by default. To exclude a specific table from replication, set replicate: false in the table definition:
type LocalTableForNode @table(replicate: false) {
id: ID!
name: String!
}
Transactions are replicated atomically, which may span multiple tables. You can also control how many nodes data is replicated to using sharding configuration.
Securing Connections
Harper supports PKI-based security and authorization for replication connections. Two authentication methods are supported:
- Certificate-based authentication (recommended for production): Nodes are identified by the certificate's common name (CN) or Subject Alternative Names (SANs).
- IP-based authentication (for development/testing): Nodes are identified by IP address when using insecure connections.
Harper can automatically perform CRL (Certificate Revocation List) and OCSP (Online Certificate Status Protocol) verification to ensure revoked certificates cannot be used. OCSP and CRL work automatically with certificates from public CAs when enableRootCAs is enabled. For self-signed certificates or private CAs without OCSP/CRL support, use Harper's manual certificate revocation feature. Certificate verification settings follow the same configuration as HTTP mTLS connections (see Certificate Verification).
Providing Your Own Certificates
If you have certificates from a public or corporate CA, enable enableRootCAs so nodes validate against the standard root CA list:
replication:
enableRootCAs: true
Ensure the certificate's CN matches the node's hostname.
Setting Up Custom Certificates
There are two ways to configure Harper with your own certificates:
- Use the
add_certificateoperation to upload them. - Specify certificate paths directly in
harper-config.yaml:
tls:
certificate: /path/to/certificate.pem
certificateAuthority: /path/to/ca.pem
privateKey: /path/to/privateKey.pem
Harper will load the provided certificates into the certificate table and use them to secure and authenticate connections. If you have a publicly-signed certificate, you can omit the certificateAuthority and enable enableRootCAs to use the bundled Mozilla CA store instead.
Cross-Generated Certificates
Harper can generate its own certificates for secure connections — useful when no existing certificates are available. When you run add_node over SSL with temporary credentials, Harper automatically handles certificate generation and signing:
{
"operation": "add_node",
"hostname": "server-two",
"verify_tls": false,
"authorization": {
"username": "admin",
"password": "password"
}
}
On a fresh install, set verify_tls: false temporarily to accept the self-signed certificate. Harper then:
- Creates a certificate signing request (CSR) and sends it to
server-two. server-twosigns the CSR and returns the signed certificate and CA.- The signed certificate is stored for all future connections.
Credentials are not stored — they are discarded immediately after use. You can also provide credentials in HTTP Authorization format (Basic, Token, or JWT).
Revoking Certificates
Added in: v4.5.0Certificates used in replication can be revoked using the certificate serial number. Use either the revoked_certificates attribute in the hdb_nodes system table or route config:
Via the operations API:
{
"operation": "update_node",
"hostname": "server-two",
"revoked_certificates": ["1769F7D6A"]
}
Via harper-config.yaml:
replication:
routes:
- hostname: server-three
port: 9930
revokedCertificates:
- 1769F7D6A
- QA69C7E2S
Insecure IP-Based Authentication
For development, testing, or secure private networks, you can disable TLS and use IP addresses to authenticate nodes. Configure replication on an insecure port and set up IP-based routes:
replication:
port: 9933
routes:
- 127.0.0.2
- 127.0.0.3
Warning: Never use insecure connections for production systems accessible from the public internet.
Loopback addresses (127.0.0.X) are a convenient way to run multiple nodes on a single machine for local development.
Controlling Replication Flow
By default, Harper replicates all data in all databases with symmetric bidirectional flow. To restrict replication to one direction between certain nodes, set sends and receives on the route configuration:
replication:
databases:
- data
routes:
- hostname: node-two
replicates:
sends: false
receives: true
- hostname: node-three
replicates:
sends: true
receives: false
In this example, the local node only receives from node-two (one-way inbound) and only sends to node-three (one-way outbound).
Per-database controlled flow
Added in: v5.1.0Routes have accepted per-database sendsTo / receivesFrom entries since v5.1.0, but a directional route was stored without being enforced on live connections until v5.1.15: before that, the direction gates read only the peer's advertised hdb_nodes record, so traffic flowed both ways regardless of what the route declared. The behavior described below is the v5.1.15-and-later behavior.
You can also scope flow per database, so different databases flow in different directions between the same two nodes. Use sendsTo / receivesFrom entries with a database:
replication:
databases:
- cardata
- config
- system
routes:
- hostname: node-two
replicates:
sendsTo:
- database: config
- database: system # push central config (users, roles, schemas) downstream
receivesFrom:
- database: cardata # aggregate telemetry upstream
If this is node-one's configuration, node-two needs the inverse directional route:
replication:
hostname: node-two
databases:
- cardata
- config
- system
routes:
- hostname: node-one
replicates:
sendsTo:
- database: cardata
receivesFrom:
- database: config
- database: system
Each entry can also carry excludeTables to keep specific tables out of a database that otherwise flows in that direction:
replication:
routes:
- hostname: node-two
replicates:
sendsTo:
- database: cardata
excludeTables:
- raw_telemetry # everything in cardata except this table
sendsTo / receivesFrom are declared from the perspective of the node whose harper-config.yaml they're in, for its route to that one peer. A route that declares replicates with sends, receives, sendsTo, or receivesFrom is authoritative on each side; a route without those fields — or no route at all — falls back to the peer's advertised hdb_nodes self-record.
The sending side needs the matching sendsTo entry when it has a directional route for that peer. To aggregate a database upstream instead of pushing it downstream — for example, so a role created on a roadside node reaches a middle-tier node — the roadside node's directional route to middle needs sendsTo: [{ database: system }]; without it, middle's subscription attempt is rejected as unauthorized. If roadside has no directional route to middle, it instead authorizes the send from middle's advertised receivesFrom and may serve system without a local sendsTo entry.
Likewise, the receiving side needs a matching receivesFrom only when it has its own directional route for that peer: a middle-tier node with a directional route to roadside is gated by that route, so omitting receivesFrom there means it never attempts the subscription. If middle has no route to roadside at all — or only a plain, non-directional one — it falls back to roadside's advertised hdb_nodes self-record. That self-record qualifies each sendsTo entry with the hostname of the route that produced it: middle subscribes because roadside's route to middle carries database: system, while core appears in no such route and matches no entry. On a receiver with no directional route for the peer, omitting receivesFrom is therefore not a way to block inbound replication.
Replicating the system database with controlled flow
Changed in: v5.2.0
Before v5.2, replicating the system database under controlled flow was discouraged: because hdb_nodes (the node registry) lives in system and each node advertised itself as a full-mesh participant, replicating system caused every node to discover and directly connect to every other node — collapsing a constrained topology into a full mesh.
As of v5.2 you can replicate system while keeping a constrained topology among nodes that all advertise directional routes. When a node has directional routes, it advertises a directional registry record derived from those routes (which neighbors it sends to / receives from) instead of a blanket "connect to everyone." A discovered non-neighbor with a directional registry record therefore is not subscribed to and does not receive a replication connection. This lets central configuration — users, roles, and schemas — propagate transitively across the whole cluster while user-database connections stay on the routes you configured. For example, in a roadside → middle → core aggregation tree, a role created on a roadside node reaches the core through the middle tier, yet the core never opens a direct replication subscription to a roadside node.
Notes and current limitations:
- This applies only when a node has directional routes in its
harper-config.yaml(replicateswithsends/receivesorsendsTo/receivesFrom). A node with no directional routes keeps the legacy full-mesh advertisement — including a node scoped only throughadd_node/set_node, which does not produce a directional self-record (see Add Node). A dynamically provisioned tier that replicatessystemwithout config routes therefore still collapses to a full mesh.add_nodescoping is also not durable: every node rewrites its own registry row from its config routes on restart or component reload, so entries anadd_nodecall wrote onto the added node's row are superseded — and a node with no directional routes re-advertises the legacy full-mesh record. - The no-direct-connection guarantee requires every discoverable participant to advertise directional routes. A node that advertises the legacy full-mesh record can still be reached through the advertised-record fallback when a receiver has no directional route for it.
- This constrains replication subscriptions only. On-demand residency/retrieval connections (for example, sharded or invalidated-cache reads) use a separate mechanism governed by data residency, not by this registry record, and can still open a direct socket to a non-neighbor node.
- Central visibility of every node is not guaranteed: an aggregation node may not list every distant leaf in its
hdb_nodesregistry (the registry relay differs from data relay). This does not open a connection either way. - Route changes to a node's own directionality take effect on restart.
- Any node that receives
systemmust be trusted with its contents. The database includes encryptedsystem.hdb_secretrows, and a joined Harper Pro node holds the cluster custody key that can decrypt them, so sendingsystemto a node is a disclosure decision regardless of direction. If a node is outside your confidentiality boundary, do not replicatesystemto it.excludeTableson the entries that carrysystem(see Per-database controlled flow) can keep individual tables off a node, but it is not a general substitute for that decision: a node without its ownhdb_secretrows cannot resolve the secret references a replicated component deploy carries, so deploys that use registry or git credentials fail there. - Within that boundary, direction is still an integrity control. Replicating
systemupstream (edge → core) propagateshdb_user/hdb_rolealong with everything else in the database: a compromised edge node can create a user or role with cluster-wide privileges or alter its advertised directionality inhdb_nodes, and those changes reach every node on the upstream path. To keep an edge node from writing to the cluster's identity state while still receiving it, enforce downstream-onlysystemreplication with directional routes on both nodes: includesystemin the central node'ssendsToand the edge node'sreceivesFrom, and omit it from the central node'sreceivesFromand the edge node'ssendsTo. A plain or absent route on either side falls back to the peer's advertised registry record and does not enforce that direction.
Explicit Subscriptions
By default, Harper automatically manages connections and subscriptions between nodes. Explicit subscriptions exist only for testing, debugging, and legacy migration — they should not be used for production replication and will likely be removed in v5.
With explicit subscriptions, Harper no longer guarantees data consistency. If you want unidirectional replication, use controlled replication flow instead.
To explicitly subscribe, use add_node with subscription definitions:
{
"operation": "add_node",
"hostname": "server-two",
"subscriptions": [
{
"database": "dev",
"table": "my-table",
"publish": true,
"subscribe": false
}
]
}
Update a subscription with update_node:
{
"operation": "update_node",
"hostname": "server-two",
"subscriptions": [
{
"database": "dev",
"table": "my-table",
"publish": true,
"subscribe": true
}
]
}
Monitoring Replication
Added in: v4.5.0 (cluster status timing statistics)
Use cluster_status to monitor the state of replication:
{
"operation": "cluster_status"
}
See Clustering Operations for the full response schema and field descriptions.
Initial Synchronization and Resynchronization
When a new node is added and its database has not been previously synced, Harper downloads the full database from the first node it connects to. After the initial sync completes, the node enters replication mode and receives incremental updates.
If a node goes offline and comes back, it resynchronizes automatically to catch up on missed transactions.
You can also specify a start_time in the add_node operation to limit the initial download to data since a given point in time:
{
"operation": "add_node",
"hostname": "server-two",
"start_time": "2024-01-01T00:00:00.000Z"
}
Replicated Transactions
The following data operations are replicated across the cluster:
- Insert
- Update
- Upsert
- Delete
- Bulk loads (CSV data load, CSV file load, CSV URL load, import from S3)
Destructive schema operations are not replicated: drop_database, drop_table, and drop_attribute must be run on each node independently.
Users and roles are not replicated across the cluster by default. They do propagate when the system database (where hdb_user and hdb_role live) is included in replication; as of v5.2 this no longer forces a full mesh — see Replicating the system database with controlled flow.
Certain management operations — including component deployment and rolling restarts — can also be replicated across the cluster.
Inspecting Cluster Configuration
Query the hdb_nodes system table to inspect the current known nodes and their configuration:
{
"operation": "search_by_value",
"database": "system",
"table": "hdb_nodes",
"attribute": "name",
"value": "*"
}
The hdb_certificate table contains the certificates used for replication connections.
See Also
- Clustering Operations — Operations API for managing cluster nodes and subscriptions
- Sharding — Distributing data across a subset of nodes
- Certificate Management