Transactions in AGS
This page describes how to manage data consistency and isolation with Aerospike Graph Service (AGS).
Queries
AGS supports different transaction isolation levels for read-only queries and Gremlin mutation queries.
Read-only queries
Read-only queries follow an eventual consistency model. If updates occur concurrently on the same vertex or edge, the query results may temporarily reflect stale or inconsistent data while mutations are in progress.
Gremlin mutation queries
Mutation queries create, update, or delete vertices and edges.
The consistency of mutation queries depends on whether the Aerospike database namespace is configured with strong consistency (SC) mode or available and partition-tolerant (AP) mode.
Mutation queries modify the graph using the Gremlin steps listed below. If a query contains any of the following mutation steps, it is classified and executed as a mutation query. Otherwise, it is treated as a read-only query.
| Operation | Gremlin step |
|---|---|
| Create edge | addE() |
| Create vertex | addV() |
| Merge edge | mergeE() |
| Merge vertex | mergeV() |
| Update vertex property | v(<>).property("key", "value") |
| Update edge property | e(<>).property("key", "value") |
| Delete vertex | v(<>).drop() |
| Delete edge | e(<>).drop() |
| Delete vertex property | v(<>).properties("key").drop() |
| Delete edge property | e(<>).properties("key").drop() |
Transactions in strong consistency mode
When the database namespace uses strong consistency (SC) mode:
- With transactions enabled, all mutation queries are atomic and isolated, guaranteeing no data loss, even during a cluster split
- With transactions enabled and configured, every mutation traversal runs atomically and in isolation.
AGS protects mutations even during network partitions. The only exception is the drop() step for a supernode vertex, which remains best-effort because of the volume of connected edges.
Transaction options in SC mode
AGS provides two options to support atomicity when SC mode is enabled:
- Aerospike transactions coordinate atomic execution for each iteration of a mutation step that touches multiple records.
- TinkerPop transactions let clients define explicit transaction scopes. Start a transaction in client code, run multiple traversals, then
commit()orrollback()the scope. TinkerPop transactions are available only in SC mode.
You can enable one or both options after your graph is running on an SC namespace.
Enable Aerospike and TinkerPop transactions
The following requirements and configuration options ensure write consistency across supported mutation operations in SC mode.
Prerequisites
- Use Aerospike Enterprise Edition 8.0.0 and later on your database cluster.
- Configure the namespace of your Aerospike database to use strong consistency. SC is required to enable both Aerospike and TinkerPop transactions.
- Enable SC for the target graph in AGS.
After SC is enabled, you can optionally turn on one or both features in AGS. Enable them independently using the following configuration options.
Aerospike transaction configuration options
Set the following configuration options:
-
Set this value to
trueto enable transactions. -
Optional timeout in seconds for transactions. By default, this is
0, which uses the server mrt-duration setting. The default mrt-duration is 10 seconds.
TinkerPop transaction configuration options
Set the following configuration options:
-
Set this value to
trueto allow transaction scopes from Gremlin client code. -
Optional timeout in seconds for TinkerPop transactions. By default, this is
0, which uses the server mrt-duration setting. The default mrt-duration is 10 seconds.
Supported mutation queries in SC mode
All Gremlin mutation steps are fully supported. Common examples include the following:
| Operation | Gremlin step |
|---|---|
| Create vertex | addV() |
| Create edge | addE() |
| Update vertex property | V().property("key", "value") |
| Update edge property | E().property("key", "value") |
| Delete vertex | V(...).drop() |
| Delete edge | E(...).drop() |
| Delete property | V().properties("key").drop() |
| Merge vertex | mergeV() |
| Merge edge | mergeE() |
How to use TinkerPop transactions
TinkerPop transactions require SC mode. They are not available on AP namespaces.
In a TinkerPop transaction, traversal steps must follow these rules:
- Scans and indexes are not available inside a transaction, so you cannot query for vertices or edges in the transaction scope. Address elements by ID (except when adding new elements). Resolve IDs outside the transaction with
g, then use those IDs inside the transaction withgtx. Keep transactions short and focused to minimize potential contention. - A single TinkerPop transaction can modify a maximum of 4096 records. Adding or removing an edge touches 3 records (the two endpoint vertices and the edge). With that in mind, removing a vertex removes all of its connected edges. If a vertex is a supernode, it also cannot be removed within a transaction.
- Once a traversal in the TinkerPop transaction modifies a record, that record is locked for the duration of the transaction and other transactions or traversals cannot update it. Use TinkerPop transactions sparingly in areas of the graph with many concurrent writes.
Add multiple elements in one TinkerPop transaction
The following TinkerPop transaction example creates two vertices and two edges, each with a property, and updates a property on a pre-existing vertex. If any step within the transaction scope fails, none of the transaction changes are applied. If all steps succeed, all transaction changes are applied atomically to the graph.
// Existing GraphTraversalSourceGraphTraversalSource g;
// Begin a transaction scopeGraphTraversalSource gtx = g.tx().begin();
// Get the ID of an existing vertex outside the transactionObject vertexId1 = g.V().has("foo", "bar").id().next();
try { // Create two vertices and an edge between them, all inside the transaction Object vertexId2 = gtx.addV("allOrNothing").property("allOrNothing", true).id().next(); Object vertexId3 = gtx.addV("allOrNothing").property("allOrNothing", true).id().next();
// Create edges and update a property on an existing vertex gtx.addE("allOrNothing").property("allOrNothing", true) .from(__.V(vertexId1)).to(__.V(vertexId2)).next(); gtx.addE("allOrNothing").property("allOrNothing", true) .from(__.V(vertexId2)).to(__.V(vertexId3)).next(); gtx.V(vertexId1).property("allOrNothing", true).next();
// Atomically apply all changes gtx.tx().commit();} catch (Exception e) { // If any transaction step fails, none of the transaction changes are applied gtx.tx().rollback();}Query outside the TinkerPop transaction, then mutate inside the transaction
You cannot run a read query inside a TinkerPop transaction, but you can read IDs outside the transaction with g and then reference those IDs with gtx inside the TinkerPop transaction. The following example shows how.
// Existing GraphTraversalSourceGraphTraversalSource g;
// Begin a transaction scopeGraphTraversalSource gtx = g.tx().begin();
try { // Create a controller vertex inside the transaction Object controllerId = gtx.addV("controller").id().next();
// Read candidate device IDs outside the transaction List<Vertex> devices = g.V().hasLabel("device").has("state", "idle").toList();
// Mutate those devices inside the transaction for (Vertex device : devices) { gtx.V(device.id()).property("state", "connected").next(); gtx.addE("connectedTo").from(__.V(device.id())).to(__.V(controllerId)).next(); }
gtx.tx().commit();} catch (Exception e) { gtx.tx().rollback();}When concurrent writes are possible, use retries (see below) or verify the current value before updating state to connected.
Per transaction timeout for TinkerPop transactions
Set a timeout for a single TinkerPop transaction using the parameter aerospike.graph.tx.timeout. The value is in seconds.
final GraphTraversalSource gtx = g.tx().begin().with("aerospike.graph.tx.timeout", 30);To make this the default for all TinkerPop transactions, set aerospike.graph.tx.timeout in the configuration.
Retry patterns for concurrent write situations
Aerospike and TinkerPop transactions prevent concurrent writes to the same graph elements. Use retry patterns in client code to handle concurrent write situations. The example below demonstrates a retry pattern for a transaction.
for (int i = 0; i < RETRY_COUNT; i++) { try { // Write operation that may throw an exception from a concurrent update g.V(1).drop().iterate(); break; } catch (final Exception e) { // Back off exponentially Thread.sleep(Math.min(Math.pow(10, i), 5000)); }}Available and partition-tolerant mode
In available and partition-tolerant (AP) mode, Aerospike and TinkerPop transactions are not available. Some writes may be lost during a cluster split. However, mutations which use the following Gremlin steps are atomic and isolated:
| Operation | Gremlin step |
|---|---|
| Create vertex | addV() |
| Update vertex property | v(<>).property("key", "value") |
| Update edge property | e(<>).property("key", "value") |
| Delete vertex property | v(<>).properties("key").drop() |
| Delete edge property | e(<>).properties("key").drop() |
| Merge vertex | mergeV() |
For all other mutation traversals in AP mode, plan for eventual consistency and retries if conflicts arise.