Skip to content

Commit

Permalink
Add possibility to remove composite index entries
Browse files Browse the repository at this point in the history
With this change it will be possible to remove stale composite index entries from the graph

Related to #1099

Signed-off-by: Oleksandr Porunov <alexandr.porunov@gmail.com>
  • Loading branch information
porunov committed Apr 27, 2022
1 parent b65d2f0 commit 13ada03
Show file tree
Hide file tree
Showing 14 changed files with 1,221 additions and 448 deletions.
3 changes: 3 additions & 0 deletions docs/advanced-topics/eventual-consistency.md
Expand Up @@ -144,6 +144,9 @@ Index entries might point to nonexistent vertices or edges. Similarly, a
vertex or edge appears in the graph but is not yet indexed and hence
ignored by global graph queries.

In some situations due to server failures permanent index inconsistency can
happen. See how to deal with permanent stale index entries [here](stale-index.md).

**Half-Edges**
Only one direction of an edge gets persisted or deleted which might lead
to the edge not being or incorrectly being retrieved.
Expand Down
172 changes: 172 additions & 0 deletions docs/advanced-topics/stale-index.md
@@ -0,0 +1,172 @@
# Permanent stale index inconsistency

In some situations due to crashes of storage database, index database, or JanusGraph instances
permanent stale index may appear.
One of such cases could be a vertex removal from the graph but due to the crash during index persistence
there is a chance that index won't be updated ever. In such situation `IllegalStateException` will be
thrown with the message `Vertex with id %vertexId% was removed.` during attempts of removing such vertex in normal way.
This problem is known and should be temporal limitation until this issue is fixed in JanusGraph.
As for now there are several workarounds described below to fix permanent stale indices.

## StaleIndexRecordUtil

`StaleIndexRecordUtil.class` is available in `janusgraph-core` module and is meant to be used as a helper class
to fix permanent stale index entries.

`StaleIndexRecordUtil.forceRemoveElementFromGraphIndex` can be used to force remove an entry of any element from
a graph index. Right now the limitations of this method is that it can be used with Composite indices only.
An example of using this method is below:
```java
StandardJanusGraph graph = (StandardJanusGraph) JanusGraphFactory.open(configuration);
StandardJanusGraphTx tx = (StandardJanusGraphTx) graph.newTransaction();
JanusGraphManagement mgmt = graph.openManagement();

// Let's say we want to remove non-existent vertex from a stale index.
// We will assume the next constraints:
// vertex id is 12345;
// Composite index name is: nameIndex
// There is a single indexed property: name
// Value of the vertex property is: HelloWorld

long vertexId = 12345L;
String compositeIndexName = "nameIndex";
String propertyKeyName = "name";
String value = "HelloWorld";

PropertyKey propertyKey = mgmt.getPropertyKey(propertyKeyName);
long propertyKeyId = propertyKey.longId();
IndexRecordEntry namePropertyIndexRecord = new IndexRecordEntry(propertyKeyId, value, propertyKey);
IndexRecordEntry[] fullIndexRecord = new IndexRecordEntry[]{namePropertyIndexRecord};

JanusGraphElement elementToBeRemoved = new CacheVertex(tx, vertexId, ElementLifeCycle.New);

// After the below method is executed index entry of the vertex 12345 should be removed from the index which
// effectively fixes permanent stale index inconsistency
StaleIndexRecordUtil.forceRemoveElementFromGraphIndex(
elementToBeRemoved,
fullIndexRecord,
graph,
compositeIndexName
);
tx.commit();
mgmt.rollback();
```

## Manually mutate index record

In case a stale index have missing added elements to the graph then it's possible to manually add or remove any
index records of the index. To do so you will need to use `BackendTransaction` directly.

**Vertex index record update**

```java
StandardJanusGraph graph = (StandardJanusGraph) JanusGraphFactory.open(configuration);
StandardJanusGraphTx tx = (StandardJanusGraphTx) graph.newTransaction();
ManagementSystem mgmt = (ManagementSystem) graph.openManagement();

// Let's say we want to remove non-existent vertex from a stale index.
// We will assume the next constraints:
// vertex id is 12345;
// Composite index name is: nameIndex
// There is a single indexed property: name
// Value of the vertex property is: HelloWorld

long vertexId = 12345L;
String compositeIndexName = "nameIndex";
String propertyKeyName = "name";
String value = "HelloWorld";

PropertyKey propertyKey = mgmt.getPropertyKey(propertyKeyName);
long propertyKeyId = propertyKey.longId();
IndexRecordEntry namePropertyIndexRecord = new IndexRecordEntry(propertyKeyId, value, propertyKey);
IndexRecordEntry[] fullIndexRecord = new IndexRecordEntry[]{namePropertyIndexRecord};

JanusGraphElement elementToBeRemoved = new CacheVertex(tx, vertexId, ElementLifeCycle.New);

JanusGraphIndex indexToBeUpdated = managementSystem.getGraphIndex(compositeIndexName);
JanusGraphSchemaVertex indexSchemaVertex = managementSystem.getSchemaVertex(indexToBeUpdated);
CompositeIndexType compositeIndexTypeToBeUpdated = (CompositeIndexType) indexSchemaVertex.asIndexType();

Serializer serializer = graph.getDataSerializer();
boolean hashKeys = graph.getIndexSerializer().isHashKeys();
HashingUtil.HashLength hashLength = graph.getIndexSerializer().getHashLength();

IndexUpdate<StaticBuffer, Entry> update = IndexRecordUtil.getCompositeIndexUpdate(
compositeIndexTypeToBeUpdated,
IndexMutationType.DELETE,
fullIndexRecord,
elementToBeRemoved,
serializer,
hashKeys,
hashLength
);

BackendTransaction backendTransaction = tx.getTxHandle();
backendTransaction.mutateIndex(update.getKey(), Collections.emptyList(), Collections.singletonList(update.getEntry()));
transaction.commit();
tx.commit();
mgmt.rollback();
```

In case above you wanted to add index entry instead of removing it you would need to use `IndexMutationType.ADD` instead
of `IndexMutationType.DELETE` as provide entries collection as a second parameter into `mutateIndex` method instead of third parameter.
I.e. `backendTransaction.mutateIndex(update.getKey(), Collections.emptyList(), Collections.singletonList(update.getEntry()));`

**Edge index record update**

Edge index record update is currently more limited to Vertex index record update above as you will need to find `relationId`
as well as both ids of the connected vertices. It may be more challenging in case you don't have those elements in the graph.
Thus, below example shows how to remove index record of *existing* edge which leads to stale index. You shouldn't repeat
below steps unless you want to force remove existing edge index record.
```java
StandardJanusGraph graph = (StandardJanusGraph) JanusGraphFactory.open(configuration);
StandardJanusGraphTx tx = (StandardJanusGraphTx) graph.newTransaction();
ManagementSystem mgmt = (ManagementSystem) graph.openManagement();

// Let's say we want to remove existent edge from an index.
// We will assume the next constraints:
// Composite index name is: nameIndex
// There is a single indexed property: name
// Value of the vertex property is: HelloWorld

String compositeIndexName = "nameIndex";
String propertyKeyName = "name";
String value = "HelloWorld";

PropertyKey propertyKey = mgmt.getPropertyKey(propertyKeyName);

Edge edge = tx.traversal().E().has(propertyKeyName, value).next();
RelationIdentifier relationIdentifier = (RelationIdentifier) edge.id();
long relationId = relationIdentifier.getRelationId();
EdgeLabel edgeLabel = managementSystem.getEdgeLabel(edgeName);

IndexRecordEntry[] fullIndexRecord = new IndexRecordEntry[]{new IndexRecordEntry(relationId, value, propertyKey)};

InternalVertex internalVertex1 = (InternalVertex) edge.outVertex();
InternalVertex internalVertex2 = (InternalVertex) edge.inVertex();
JanusGraphElement elementToBeRemoved = new StandardEdge(relationId, edgeLabel, internalVertex1, internalVertex2, ElementLifeCycle.New);

JanusGraphIndex indexToBeUpdated = managementSystem.getGraphIndex(compositeIndexName);
JanusGraphSchemaVertex indexSchemaVertex = managementSystem.getSchemaVertex(indexToBeUpdated);
CompositeIndexType compositeIndexTypeToBeUpdated = (CompositeIndexType) indexSchemaVertex.asIndexType();

Serializer serializer = graph.getDataSerializer();
boolean hashKeys = graph.getIndexSerializer().isHashKeys();
HashingUtil.HashLength hashLength = graph.getIndexSerializer().getHashLength();

IndexUpdate<StaticBuffer, Entry> update = IndexRecordUtil.getCompositeIndexUpdate(
compositeIndexTypeToBeUpdated,
IndexMutationType.DELETE,
fullIndexRecord,
elementToBeRemoved,
serializer,
hashKeys,
hashLength
);

BackendTransaction backendTransaction = tx.getTxHandle();
backendTransaction.mutateIndex(update.getKey(), Collections.emptyList(), Collections.singletonList(update.getEntry()));
transaction.commit();
tx.commit();
mgmt.rollback();
```

0 comments on commit 13ada03

Please sign in to comment.