From 40f7288aa04fa73d72402d171f13daa02d6dc127 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sat, 29 Aug 2026 22:46:32 +0530 Subject: [PATCH] fix(server): keep the hbase meta table when clearing a graph Clearing a graph (DELETE /graphs/{name}/clear) calls truncate() on each of the three stores. HbaseSystemStore.tableNames() appends the meta table, so the truncate also wiped the backend version that init() had written there, and the next start of the server failed the version check with "The backend store version is inconsistent" (#2209). Add HbaseStore.tableNamesToTruncate(), all the tables by default, and override it in HbaseSystemStore to leave out the meta table only. The schema store still truncates its counters table, so schema ids restart from the beginning after a clear, as they do on RocksDB. The rollback path re-enables the same tables it disabled. Add a core test that clears a graph and checks that the version check still passes, the schema and the data are gone, and the schema ids are handed out again from the start. It runs on every backend of the CI matrix. Supersedes #2911 by LYD031106. closes #2209 --- .../backend/store/hbase/HbaseStore.java | 31 ++++++++++-- .../hugegraph/core/MultiGraphsTest.java | 50 +++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/hugegraph-server/hugegraph-hbase/src/main/java/org/apache/hugegraph/backend/store/hbase/HbaseStore.java b/hugegraph-server/hugegraph-hbase/src/main/java/org/apache/hugegraph/backend/store/hbase/HbaseStore.java index 1d75c00944..56f7210cc8 100644 --- a/hugegraph-server/hugegraph-hbase/src/main/java/org/apache/hugegraph/backend/store/hbase/HbaseStore.java +++ b/hugegraph-server/hugegraph-hbase/src/main/java/org/apache/hugegraph/backend/store/hbase/HbaseStore.java @@ -114,6 +114,15 @@ protected List tableNames() { .collect(Collectors.toList()); } + /** + * The tables to truncate when the graph is cleared, all the tables of + * the store by default. A store that keeps backend metadata which must + * survive a clear excludes it here, see {@link HbaseSystemStore}. + */ + protected List tableNamesToTruncate() { + return this.tableNames(); + } + public String namespace() { return this.namespace; } @@ -371,7 +380,7 @@ public void truncate() { }; // Truncate tables - List tables = this.tableNames(); + List tables = this.tableNamesToTruncate(); Map> futures = new HashMap<>(tables.size()); try { @@ -383,7 +392,7 @@ public void truncate() { wait.apply(entry.getKey(), entry.getValue()); } } catch (Exception e) { - this.enableTables(); + this.enableTables(tables); throw new BackendException( "Failed to disable table for '%s' store", e, this.store); } @@ -397,7 +406,7 @@ public void truncate() { wait.apply(entry.getKey(), entry.getValue()); } } catch (Exception e) { - this.enableTables(); + this.enableTables(tables); throw new BackendException( "Failed to truncate table for '%s' store", e, this.store); } @@ -405,8 +414,8 @@ public void truncate() { LOG.debug("Store truncated: {}", this.store); } - private void enableTables() { - for (String table : this.tableNames()) { + private void enableTables(List tables) { + for (String table : tables) { try { this.sessions.enableTable(table); } catch (Exception e) { @@ -575,6 +584,18 @@ protected List tableNames() { return tableNames; } + @Override + protected List tableNamesToTruncate() { + /* + * Keep the meta table: it holds the backend version written by + * init(), truncating it makes the version check fail at the next + * startup (see #2209) + */ + List tableNames = this.tableNames(); + tableNames.remove(this.meta.table()); + return tableNames; + } + @Override public void init() { super.init(); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java index 70aa5df7d0..7ed172acd9 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java @@ -71,6 +71,56 @@ public void testWriteAndReadVersion() { destroyGraphs(graphs); } + @Test + public void testTruncateBackendKeepsVersionAndResetsSchemaIds() { + // hstore keeps the schema in PD meta after truncate and caches id ranges + Assume.assumeFalse("skip this test for hstore", + "hstore".equals(graph().backend())); + + HugeGraph graph = openGraphs("truncate_g").get(0); + try { + // Start from a clean backend in case a previous run failed midway + graph.clearBackend(); + graph.initBackend(); + graph.serverStarted(GlobalMasterInfo.master("server-truncate")); + + BackendStoreInfo backendStoreInfo = graph.backendStoreInfo(); + Assert.assertTrue(backendStoreInfo.checkVersion()); + + SchemaManager schema = graph.schema(); + schema.propertyKey("name").asText().create(); + VertexLabel person = schema.vertexLabel("person") + .properties("name") + .useAutomaticId().create(); + graph.addVertex(T.label, "person", "name", "marko"); + graph.tx().commit(); + Assert.assertEquals(1L, graph.traversal().V().count().next()); + + graph.truncateBackend(); + + // The backend version written by init() survives the truncate + Assert.assertTrue(backendStoreInfo.exists()); + Assert.assertTrue(backendStoreInfo.checkVersion()); + // The schema and the data are gone + Assert.assertEquals(0L, graph.traversal().V().count().next()); + Assert.assertTrue(schema.getVertexLabels().isEmpty()); + Assert.assertTrue(schema.getPropertyKeys().isEmpty()); + // The schema id counters are reset: the same ids are handed out + schema.propertyKey("name").asText().create(); + VertexLabel person2 = schema.vertexLabel("person") + .properties("name") + .useAutomaticId().create(); + Assert.assertEquals(person.id(), person2.id()); + graph.addVertex(T.label, "person", "name", "marko"); + graph.tx().commit(); + Assert.assertEquals(1L, graph.traversal().V().count().next()); + + graph.clearBackend(); + } finally { + destroyGraphs(ImmutableList.of(graph)); + } + } + @Test public void testCreateMultiGraphs() { List graphs = openGraphs("g_1", NAME48);