GDB transaction control at the client side

Updated at:

Graph Database (GDB) supports transactions. By default, all operations requested in a domain-specific language (DSL) command are implemented in the same transaction. GDB also lets you control transactions at the client side. Multiple operations in the same transaction are atomic. All the operations in a transaction succeed or fail at the same time. The transaction isolation level is Read Committed.

This topic describes how to use SDKs in different languages to control transactions at the client side. The examples provide only the key code.

Transaction interface for Java

The Alibaba Cloud GDB Java SDK offers a user-friendly transaction interface. For an example, see the scriptSessionTest sample in the demo.

Demo demo = new Demo(yaml, true);
GdbClient txClient = demo.client.get();
try {
    // init parameters
    Map<String, Object> parameters = new HashMap();
    parameters.put("vertexStart", vertexStart);
    parameters.put("vertexEnd", vertexEnd);
    parameters.put("vertexLabel", vertexLabel);
    parameters.put("edgeLabel", edgeLabel);

    txClient.batchTransaction((tx, g) -> {
        // add vertex 1
        String dsl = "g.addV(vertexLabel).property(T.id, vertexStart)";
        tx.exec(dsl, parameters, DEFAULT_TIMEOUT_MILLSECOND);

        // add vertex 2
        String dsl2 = "g.addV(vertexLabel).property(T.id, vertexEnd)";
        tx.exec(dsl2, parameters, DEFAULT_TIMEOUT_MILLSECOND);

        // add edge
        String dsl3 = "g.addE(edgeLabel).from(V(vertexEnd)).to(V(vertexStart))";
        tx.exec(dsl3, parameters, DEFAULT_TIMEOUT_MILLSECOND);
    });
} catch (Exception ex) {
    throw new RuntimeException(ex);
} finally {
    // close Session GdbClient
    demo.close();
}

All operations in the batchTransaction interface, such as adding two vertices and one edge, are performed in the same transaction. The updates are written to the GDB instance only if all operations succeed and no exceptions are thrown.

Transaction interface for Go

The Alibaba Cloud GDB Go SDK also offers a user-friendly transaction interface. For an example, see the sample in the demo.

// connect GDB with auth
sessionId, _ := uuid.NewUUID()
client := goClient.NewSessionClient(sessionId.String(), settings)

client.BatchSubmit(func(c goClient.ClientShell) error {
    bindings := make(map[string]interface{})
    bindings["GDB___label"] = "goTest"
    bindings["GDB___PK1"] = "name"
    bindings["GDB___PK2"] = "age"
    dsl := "g.addV(GDB___label).property(id, GDB___id).property(GDB___PK1, GDB___PV1).property(GDB___PK2, GDB___PV2)"
   
     // Add vertex 100.
    bindings["GDB___id"] = "100"
    bindings["GDB___PV1"] = "Jack"
    bindings["GDB___PV2"] = 32
    _, err := c.SubmitScriptBound(dsl, bindings)
    if err != nil {
      return err
    }

    // Add vertex 101.
    bindings["GDB___id"] = "101"
    bindings["GDB___PV1"] = "Luck"
    bindings["GDB___PV2"] = 34
    _, err = c.SubmitScriptBound(dsl, bindings)
    if err != nil {
      return err
    }

    dsl = "g.addE(GDB___label).from(__.V(GDB___from)).to(__.V(GDB___to)).property(id, GDB___id).property(GDB___PK1, GDB___PV1)"
    // Add edge 201.
    bindings["GDB___id"] = "201"
    bindings["GDB___PV1"] = "created"
    bindings["GDB___from"] = "100"
    bindings["GDB___to"] = "101"
    _, err = c.SubmitScriptBound(dsl, bindings)
    if err != nil {
      return err
    }

    return nil
})
client.Close()

All operations in the BatchSubmit interface, such as adding two vertices and one edge, are completed in the same transaction. The updates are written to the GDB instance only if all operations return nil. If any operation returns an error (err != nil), none of the updates are written to the GDB instance.

Transaction interface for Python

The transaction interface depends on sessions. gremlin-python (v3.4.7 in the examples in this document) adds session support. You can use the session interface to manually implement transaction features.

session_id = str(uuid.uuid4())
client = Client('ws://${gdb_endpoint}:8182/gremlin', 'g', username=${username}, password=${password}, session=session_id)

try:
    # Start a transaction.
    client.submit('g.tx().open()')
    dsl ="g.addV('person').property(id, GDB___id).property('name', GDB___PV)"

    # Add vertex 100 in a synchronous operation.
    client.submit(dsl, {'GDB___id':'100', 'GDB___PV':'Jack'}).all().result()

    # Add vertex 101 in a synchronous operation.
    client.submit(dsl, {'GDB___id':'101', 'GDB___PV':'Luck'}).all().result()

    # Add edge 201 in a synchronous operation.
    client.submit("g.addE('friend').from(V('100')).to(V('101')).property(id, '201').property('time','2020-03-08')").all().result()

    # Commit the transaction.
    client.submit('g.tx().commit()')
except Exception:
    # Roll back the transaction.
    client.submit('g.tx().rollback()')
client.close()

The preceding example uses the session mechanism to implement the transaction operation interface. You can manually control the start, commit, or rollback of the transaction. The transaction adds two vertices and one edge. If all operations succeed without exceptions, the g.tx().commit() request is executed to write the updates to the GDB instance. If an exception occurs, the rollback process is triggered to cancel all updates.

Transaction interface for .NET

The transaction interface depends on sessions. The latest version of gremlin-dotnet (v3.4.7) adds session support. You can use the session interface to manually implement transaction features.

var gremlinServer = new GremlinServer(${gdb_endpoint}, 8182, username=${username}, password=${password});
var sessionId = Guid.NewGuid().ToString();
using (var gremlinClient = new GremlinClient(gremlinServer, sessionId: sessionId))
{
    try
    {
        // Start the transaction.
        await gremlinClient.SubmitAsync("g.tx().open()");
        
        string dsl ="g.addV('person').property(id,G___id).property('name',G___name)";
        Dictionary<string, object> parameters1 = new Dictionary<string, object> {{"G___id", "100"},{"G___name", "Jack"}};
        // Add vertex 100.
        await gremlinClient.SubmitAsync<dynamic>(dsl, parameters1);
        
        Dictionary<string, object> parameters2 = new Dictionary<string, object> {{"G___id", "101"},{"G___name", "Luck"}};
        // Add vertex 101.
        await gremlinClient.SubmitAsync<dynamic>(dsl, parameters2);
        
        // Add an edge.
        string dsl2 = "g.addE('known').property(id,'201').from(V('100')).to(V('101'))";
        await gremlinClient.SubmitAsync<dynamic>(dsl2);
        
        // Commit the update transaction.
        await gremlinClient.SubmitAsync("g.tx().commit()");
    }
    catch (Exception ignored)
    {
        // Roll back the update transaction.
        await gremlinClient.SubmitAsync("g.tx().rollback()");
    }
}

Transaction interface for Node.js

The transaction interface depends on sessions. The latest version of gremlin-javascript (v3.4.7) adds session support. You can use the session interface to manually implement transaction features.

const gremlin = require('gremlin');
const authenticator = new gremlin.driver.auth.PlainTextSaslAuthenticator(${username}, ${password});
const client = new gremlin.driver.Client(
    'ws://${gdb_endpoint}:8182/gremlin',
    {'authenticator': authenticator,
     'session': ${uuid-session-id}}
);

function addVertex1()
{
    return client.submit("g.addV(GDB___label).property(id, GDB___id).property('name', GDB___pv)",{
        GDB___id: "gdb_vertex_test_id_1",
        GDB___label: "gdb_vertex_test_label",
        GDB___pv: "Jack"
    }).then(data => {
        console.log("Vertex 1: %s\n", JSON.stringify(data));
    });
}

function addVertex2()
{
    return client.submit("g.addV(GDB___label).property(id, GDB___id).property('name', GDB___pv)",{
        GDB___id: "gdb_vertex_test_id_2",
        GDB___label: "gdb_vertex_test_label",
        GDB___pv: "Lucy"
    }).then(data => {
        console.log("Vertex 2: %s\n", JSON.stringify(data));
    });
}

function addEdge()
{
    return client.submit("g.addE(GDB___label).from(V(GDB___from)).to(V(GDB___to)).property(id, GDB___id)",{
        GDB___id: "gdb_edge_test_id",
        GDB___label: "gdb_edge_test_label",
        GDB___from: "gdb_vertex_test_id_1",
        GDB___to: "gdb_vertex_test_id_2"
    }).then(data => {
        console.log("Edge: %s\n", JSON.stringify(data));
    });
}

function beginTx()
{
    return client.submit("g.tx().open()",{
    }).then(data => {
        console.log("open tx\n");
    });
}

function doCommit()
{
    return client.submit("g.tx().commit()",{
    }).then(data => {
        console.log("commit tx\n");
    });
}

function doRollback()
{
    return client.submit("g.tx().rollback()",{
    }).then(data => {
        console.log("commit tx\n");
    });
}

client.open()
    .then(beginTx)
    .then(addVertex1)
    .then(addVertex2)
    .then(addEdge)
    .then(doCommit)
    .catch((error) => {
        doRollback()
        console.error("Error running query...");
        console.error(error);
    }).then((res) => {
        client.close()
        console.log("Finished, Press any key to exit")
        process.stdin.resume();
        process.stdin.on('data', process.exit.bind(process, 0));
    }).catch((err) => {
        console.error("Fatal error:", err);
    });

The preceding example uses callbacks to sequentially add vertices and an associated edge within a transaction, and then commits the update. If an exception occurs, the catch process is triggered to roll back the updates in the transaction.

FAQ

1. GDB transaction interface does not support concurrency

No. The GDB transaction interfaces rely on the underlying session mechanism. Operations within the same transaction are sent sequentially to the server using the same session-client. Therefore, a session-client does not support multi-threaded concurrent requests.

If you require concurrency, you can use a standard automatic transaction client. Alternatively, you can create multiple session-client instances. Each thread can use one session-client to send requests sequentially. This achieves a concurrent effect across multiple threads.

Important

The transaction isolation level for concurrent operations of multiple threads is Read-Committed. This means that only the committed data can be read.

2. Ensure the transaction is closed after the operation

Yes. When you use the client to control transaction interfaces, the server does not automatically commit or roll back transactions. You must ensure that all open transactions are closed (committed or rolled back) after the operations are complete. Incomplete transactions can cause data loss and affect updates to other data items.

GDB does not automatically close open transactions on a session if the session-client connection is disconnected. Transactions are closed only on a normal exit when session-client.close() is called.

If a session-client disconnects abnormally while a transaction is open, the server rolls back the operation by default after 10 minutes.

3. session-client connection configuration

You can set the number of connections for the session-client to 1 and not actively close the connection. In a future version, the GDB server will roll back all open transactions if the connection is disconnected while in session mode.

4. The transaction interface only supports script requests

Yes, the transaction API operations apply to only script requests, and do not apply to requests in bytecode.