We are using AWS Neptune Graph database and need to query a subgraph. We have about 300-400 independent graphs on our database and only need to query one of them at a time.
Our model is mainly this: graph model We have 300-400 "A" Vertices. This is our Top-level element and every graph starting with "A" is independent of the other graphs. Every "A" vertex can have several(20-100) "B" vertices. Every "B" vertex can have several(2-10) "C" vertices. So we have A, B, C as a hierarchy. Under that we have our "data" vertices, which is more or less several vertices (D,E,F,G) in a row and that could be for a couple of times (1-10). The last "G" vertex is connected to the next "D" vertex under the next "B" vertex.
We need to query a full graph starting from A or just some specific B's.
Currently we are using this query to get the whole graph (all vertices and edges) and then drop some of the connections. This query is the first query in our transaction. The transaction needs some time, because it will add a lot of vertices and egdes. This first query in our transaction blocks all other queries. We are getting a ConcurrentModificationException.
If the long transaction to query the graph for A(ID: 4711) is running:
-> The result is always a ConcurrentModificationException.
It looks like the query is really blocking the complete index for all vertices. It is not possible to add any other vertices.
We have a background process which is updating the database and this transaction takes up to 30 seconds. We have then a lot of updates of this kind. Like every minute a different A subgraph gets updated. And on the other hand there a the users who uses the app and can also edit the data. The current problem is that changing data of a A(ID:4711) subgraph is also blocking the changing of A(4712) subgraph even if there is absolutely no connection between them.
g.V().hasLabel("A").has("v",4711).outE("s").inV()
.hasLabel("B").hasId("c4...a","a0...f","ac...3")
.outE("h").inV()
.bothE("sv","ev")
.bothV().not(hasLabel("C")).simplePath()
.barrier()
.repeat(outE().not(hasLabel("sv","ev")).simplePath().inV())
.until(or(outE().count().is(0),hasLabel("C"),hasLabel("AP","AC").bothE("sv","ev").count().is(P.gt(0))))
.path().unfold().dedup().or(hasLabel("sp").has("ev"),hasLabel("ev")).barrier()
.drop()
Is there any chance to rewrite this query that it will not block the database? I mean the subgraph are complete independent and I don't get why this query blocks the Index at full range.
If I run the query on our DEV environment with %%gremlin profile:
Predicates
==========
# of predicates: 79
Results
=======
Count: 12
Index Operations
================
Query execution:
# of statement index ops: 3283
# of unique statement index ops: 1984
Duplication ratio: 1.65
# of terms materialized: 598
Serialization:
# of statement index ops: 3
# of unique statement index ops: 3
Duplication ratio: 1.0
# of terms materialized: 21
If I run that query even on a database with more data, I get this:
Predicates
==========
# of predicates: 62
Results
=======
Count: 66
Index Operations
================
Query execution:
# of statement index ops: 26412
# of unique statement index ops: 9403
Duplication ratio: 2.81
# of terms materialized: 1706
Serialization:
# of statement index ops: 3
# of unique statement index ops: 3
Duplication ratio: 1.0
# of terms materialized: 94
For me it looks like we have far too many index operations with this query. Do you have any idea how we can prevent that query from scanning the database/index?
Amazon Neptune will take locks when queries are updating the database. If two threads try to alter more or less the same part of the graph, concurrently, one of them will likely receive a Concurrent Modification Exception (CME). These are retryable exceptions. Typically it is recommended to retry using an exponential backoff approach.
If enough of the graph has been locked by one query, and kept locked for an extended period, other mutation requests can potentially receive a CME response, again, depending on what they are trying to modify.
When performing an operation like drop()
, especially on vertices, keep in mind that this also requires dropping all adjacent edges, which can in turn impact the vertices on the other ends of those edges, in terms of locks.
One strategy to consider is to drop edges in batches before dropping vertices. This technique lends itself to a multi threaded approach where each thread drops edges in batches, and after the edges have been dropped, then the nodes can also be deleted.
The transaction semantics, isolation levels, and locking strategies, that Neptune uses are described here