Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sync (graph synchronization)

Sync makes a named graph’s contents exactly the payload you supply, committing only the delta. It is the “full replacement” verb for data whose source of truth lives outside Fluree — an ontology maintained in an editor, a reference table regenerated by a pipeline — where you want the graph to match the latest export and the commit to show exactly what changed.

InsertUpsertSync
Asserts new triples
Retracts changed valuesfor supplied (subject, predicate) pairs
Retracts triples absent from the payload
Scopepayloadpayload’s subjects/predicatesone whole named graph

Semantics

Given the graph’s current contents A and the payload B:

  • retract A − B
  • assert B − A
  • A ∩ B produces no flakes — unchanged facts do not appear in the commit
  • A = B produces no commit (committed: false)

Sync is transactional and history-preserving: one normal commit at t = current + 1; queries as-of an earlier t still see the previous contents. SHACL validation, modify-policy enforcement, and novelty backpressure apply exactly as for any other transaction.

The scope is exactly one named graph, named by the caller — never inferred from the payload. The payload may not address named graphs itself, and reserved system graphs (and, for now, the default graph) are rejected.

HTTP endpoint

curl -X POST "http://localhost:8090/v1/fluree/sync?ledger=mydb:main&graph=http://example.org/graphs/ontology" \
  -H "Content-Type: application/json" \
  -d '{
    "@context": { "ex": "http://example.org/" },
    "@graph": [
      { "@id": "ex:alice", "ex:name": "Alice", "ex:role": "engineer" },
      { "@id": "ex:bob", "ex:name": "Bob" }
    ]
  }'

Query parameters:

ParameterMeaning
ledgerTarget ledger (name:branch)
graphRequired. Target graph IRI — the sync scope
dryRun=trueStage and report the delta (asserted/retracted counts) without committing — under the same policy, header, and inline-constraint inputs the real run uses, so it reports what the real run would do (or fails the way it would)
allowEmpty=trueConfirm an explicitly empty payload ("@graph": []), which clears the graph

The payload is JSON-LD (application/json). Convert Turtle exports client-side (e.g. fluree-graph-turtle’s parse_to_json) for now.

A dry run responds with the delta report:

{ "ledger": "mydb:main", "graph": "http://example.org/graphs/ontology",
  "asserted": 2, "retracted": 2, "committed": false, "dryRun": true, "t": 7 }

Rust API

#![allow(unused)]
fn main() {
use fluree_db_api::SyncGraphOpts;

let report = fluree
    .sync_named_graph("mydb:main", "http://example.org/graphs/ontology",
                      &payload, SyncGraphOpts::default())
    .await?;
assert!(report.committed || (report.asserted == 0 && report.retracted == 0));
}

SyncGraphOpts { dry_run, allow_empty } mirror the query parameters; sync_named_graph_with additionally takes explicit TxnOpts and a PolicyContext. The builder form fluree.stage(&handle).sync_graph(graph_iri, &payload) is also available (note: the builder does not apply the allow_empty gate). Its consensus terminal build_commit() returns Ok(None) for a no-change sync. Target validation (absolute IRI, no system graphs) is enforced at staging, so it applies on every entry point.

Safety rails

  • Empty payload requires opt-in. "@graph": [] means “the graph’s desired contents are empty” — i.e. clear the graph. Without allowEmpty, it is rejected, so a truncated export cannot silently wipe the graph.
  • Explicit scope. No graph parameter, no sync. Subjects in the payload never widen or narrow the scope.
  • Dry run first. For a periodic pipeline, a dryRun call that reports an unexpectedly large retracted count is a cheap tripwire before the real run.
  • Policy model. Like CLEAR/COPY/MOVE (and unlike DELETE-WHERE), the current-contents scan is not view-policy filtered: sync is an authoritative whole-graph replacement, and a view-filtered scan would leave rows the caller cannot see in place, breaking “the graph now equals the payload”. Modify-policy is still enforced on the resulting delta.

Blank nodes

Sync skolemizes the payload’s blank nodes with a deterministic, graph-scoped key: the same blank-node label in the same target graph mints the same skolem IRI on every sync. Exporters that keep labels stable (hand-maintained Turtle, most pipeline generators) therefore resync bnode-rooted structures (OWL restrictions, RDF lists) with zero churn.

Exporters that regenerate labels on every save (e.g. Protégé’s genid…) still churn those structures: the triples are isomorphic but the labels — and therefore the skolemized identities — differ. The result is correct, just noisier commits. Structural (RDFC 1.0-style) canonicalization is the designed follow-up for label-unstable exporters.

Scale

Staging a sync materializes the target graph’s current flakes plus the payload’s flakes in memory — the same profile as CLEAR/COPY/MOVE (chunked staging for whole-graph operations is a known follow-up). A huge payload with a small delta is fine; the commit only carries the delta. A huge delta is bounded by novelty backpressure (reindex_max_bytes): the commit fails with NoveltyWouldExceed rather than overrunning memory, and the graph is left unchanged.

A huge graph is bounded by FLUREE_MAX_GRAPH_SCAN_FLAKES (default 10,000,000; 0 disables): the whole-graph scan stops there and the transaction fails with a resource-limit error naming the knob, instead of exhausting memory — an identical resync of a large graph is the worst case, since it materializes everything and commits nothing. A dry run trips the cap the same way the real run would, and CLEAR/DROP/COPY/MOVE share it. The follow-up that streams the diff instead of materializing the graph is #1691.