Repository navigation
fix(server): bind locally loaded graphs in Gremlin Server - #3351
Conversation
Gremlin Server opens the graphs in conf/graphs on its own, once, while it is being prepared, and drops any graph whose open fails there. With PD briefly unreachable at that moment, the REST GraphManager still opened the graph a few seconds later, after its PD readiness wait, but never told Gremlin about it: loadGraph() stored the graph without firing GRAPH_CREATE, unlike the create paths. The Server then answered REST and passed readiness while every Gremlin call on that graph failed with "Could not rebind" until the process restarted. loadGraph() now offers each locally loaded graph to Gremlin through the same GRAPH_CREATE event the create paths use. When Gremlin's own open succeeded, both opens return the same cached instance, so the rebind is harmless. reload() also gets its Gremlin binding back this way, since dropping a graph removed it. A graph whose spaceGraphName differs from DEFAULT-<conf file name> (a different store name or a non-DEFAULT graphspace) is skipped with a warning so it is not bound under a second name, and a failed bind is logged without unloading the graph from REST. Fixes apache#3228
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3351 +/- ##
============================================
+ Coverage 54.31% 57.86% +3.55%
+ Complexity 2250 130 -2120
============================================
Files 770 517 -253
Lines 67952 49910 -18042
Branches 9213 7332 -1881
============================================
- Hits 36905 28881 -8024
+ Misses 27470 18144 -9326
+ Partials 3577 2885 -692
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: Score 7/10. The production change reads correctly: bindLocalGraph() fires GRAPH_CREATE through notifyEvent() after loadGraph() stores the graph, the DEFAULT-<file> guard keeps the event from adding a second Gremlin or REST name, and a failed bind is contained. One Important problem in the new tests: testGraphOpenedByGremlinKeepsItsInstance, the only test for the normal case where Gremlin already holds the graph, never reaches bindLocalGraph(). loadGraph() throws a NullPointerException before the bind, loadGraphsFromLocal() logs and swallows it, and both assertions pass anyway (details inline). Evidence: read the full diff 4b77b7b..85f4de9 (4 files) in a scratch worktree, together with GraphManager (constructor, init(), reload(), loadGraphsFromLocal(), loadGraph(), createGraphLocal(), listenChanges(), notifyEvent()), ContextGremlinServer (injectGraph(), removeGraph(), injectTraversalSource()), HugeGremlinServer.prepare(), the HugeGraphServer startup order, HugeFactory.open(), HugeFactoryAuthProxy.open() and HugeConfig(Configuration). The exact-head build-server / Server memory tests job log shows the NPE from this test, and its UnitTestSuite run passes (1170 tests). The only blocking check failure, pd_store / hstore, is an Apache RAT failure on two date-rolled bolt log files under hugegraph-store/apache-hugegraph-store-1.8.0/logs/, which is not caused by this diff. I did not run the tests locally or reproduce the PD restart scenario.
Add a case where a GRAPH_CREATE listener fails: the error is logged and the graph stays loaded in REST. Also drop the instanceof HugeGraph guard in bindLocalGraph(); loadGraph() already casts the graph's configuration to HugeConfig, so only HugeGraph instances reach it.
When Gremlin Server opens a conf/graphs file first, HugeFactory caches the graph with a HugeConfig built from a Configuration, which has no file. The REST load of the same path gets that cached instance back, and the assert comparing its config file with the loaded path threw a NullPointerException under -ea before bindLocalGraph() ran. loadGraphsFromLocal() logged and swallowed it, so the unit test for this normal case passed without the rebind ever happening. The assert now skips the comparison when the config has no file, and the test checks that GRAPH_CREATE fired and that the __g_ traversal source still belongs to the instance Gremlin opened.
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: Score 9/10. At 664eb9a the one point from the earlier review at 85f4de9 is fixed: the loadGraph() assert now skips the comparison when a Gremlin-opened graph's config has no file, so testGraphOpenedByGremlinKeepsItsInstance reaches bindLocalGraph() and checks the rebind and the __g_ source. bindLocalGraph() fires GRAPH_CREATE before Gremlin Server starts, the DEFAULT-<file> guard keeps it from adding a second name, and a failed bind leaves the graph in REST; I found no new defect. Evidence: read the full diff against merge-base 4b77b7b (4 files) with GraphManager (constructor, init(), reload(), loadGraphsFromLocal(), listenChanges(), notifyEvent()), ContextGremlinServer.injectGraph(), the HugeGraphServer prepare, REST start, startPrepared() order, HugeFactory.open() (cached by graphspace and lowercased store) and HugeFactoryAuthProxy.open() (one proxy per graph); the only GRAPH_CREATE listeners are in GraphManager and ContextGremlinServer. CI run 38697372320 at this head passes; the Server memory tests log shows bind_static loading with no can't be loaded error, the name-mismatch WARN and the single failed-bind ERROR. Not reproduced against a rolling 3 PD + 3 Store cluster.
…bind-local-graphs # Conflicts: # hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: Score 9/10. At f0dce63 the production change holds up. bindLocalGraph() fires GRAPH_CREATE after loadGraph() stores the graph, and the only listeners on that event are the REST map in GraphManager.listenChanges() and ContextGremlinServer.injectGraph(). Both are idempotent for the instance Gremlin already holds, because HugeFactory caches graphs by graphspace and lowercased store, and HugeFactoryAuthProxy keeps one proxy per graph. The DEFAULT-<file> guard stops a second name from being added, and a failed bind leaves the graph in REST. The merge from master touches only the UnitTestSuite registration on this PR's side. The only point here is a wording issue in the operations doc (inline). Evidence: read the full diff against merge-base a0aae8e (4 files), GraphManager (constructor and init(), which both run loadGraphsFromLocal(), so the event fires twice at startup, which is harmless; reload(), dropGraph(), dropGraphLocal(), notifyEvent()), ContextGremlinServer, HugeGremlinServer.prepare(), the HugeGraphServer order of prepare, REST start and startPrepared(), HugeFactory.open(), HugeFactoryAuthProxy.open() and StandardHugeGraph.spaceGraphName(). I also diffed 664eb9a against this head for GraphManager and the new test: the only change is the two master lines in dropGraphSpace() and PdRegisterLabel. CI run 38781033639 at this head is still running Server memory tests and Server rocksdb tests; every completed job passes. I did not run the tests locally or reproduce the rolling PD restart.
"Images that include apache#3228" named the bug report, which reads as images that have the bug. Name the fixing PR instead.
Purpose of the PR
Gremlin Server opens the graphs in
conf/graphsitself, once, insideHugeGremlinServer.prepare(). TinkerPop'sDefaultGraphManagerlogs and drops any graph whose open throws. That open runs before the RESTGraphManagerexists, so it also runs before thepd.stores_wait_timeoutwait. When PD is briefly unreachable at that point, the Gremlin open fails. A few seconds later REST opens the same graph successfully, after its wait.GraphManager.loadGraph(String, String)then stored the graph without firingGRAPH_CREATE, which is the only wayContextGremlinServerbinds a graph after startup. The Server answered REST and passed readiness, and every Gremlin call on that graph failed withCould not rebind [DEFAULT-hugegraph]until the process restarted.Graphs created through the API or loaded from PD meta don't have this problem, because
createGraph()andcreateGraphLocal()already notifyGRAPH_CREATE.Main Changes
GraphManager.loadGraph()calls a newbindLocalGraph(), which firesGRAPH_CREATEfor the loaded graph through the samenotifyEvent()the create paths use. This resolves the TODO from chore: add TODO breadcrumbs for the Helm chart's server workarounds #3260.gremlin.graphfactory,HugeFactorycaches graphs by name, andHugeFactoryAuthProxycaches one proxy per graph, so Gremlin is handed the instance it already holds.injectGraph()overwrites the binding, the__g_traversal source and the global binding, and it runs beforestartPrepared(), so no request can see a half-updated binding.spaceGraphName()differs fromDEFAULT-<conf file name>: thestorevalue differs from the file name, orgraphspaceisn'tDEFAULT. Gremlin binds that file asDEFAULT-<file>, so firing the event would add a second binding and a second REST entry under the other name.loadGraph()'s assert that the graph's config file matches the loaded path now skips the check when the config has no file. That is the case when Gremlin opened the graph first, and under-eathe assert threw before the bind.catch (Throwable)inloadGraphsFromLocal()with the same result, and failing startup over it is a separate decision.reload()drops graphs withGRAPH_DROP, which removes their Gremlin binding, and the local reload never brought it back. It does now.helm/hugegraph/docs/operations.md: theCould not rebindrow now says which log lines to check on images with this fix. The per-Pod Gremlin check inhelm teststays, since older images still need it.Verifying these changes
GraphManagerLocalGraphBindTest, registered inUnitTestSuite, uses the memory backend and needs no PD. It checks that a local load firesGRAPH_CREATEonce, and that loading again keeps the same instance. A graph whose store name differs from its file name is not rebound. With a realContextGremlinServerand no static graph (the failed-open case), the graph ends up bound along with its__g_traversal source. When Gremlin opened the graph itself, the rebind still fires and its instance and__g_traversal source stay the ones REST holds. A failed bind leaves the graph loaded in REST.bindLocalGraph()call removed, 5 of the 6 tests fail; the name-mismatch case expects no bind.mvn test -pl hugegraph-server/hugegraph-test -P unit-test: 1171 tests, 0 failures.run-api-test.sh rocksdb(packaged Server with auth, as in CI): 189 tests, 0 failures, and the runtime log check passes.Does this PR potentially affect the following parts?
conf/graphsto Gremlin Server a second time; graphs Gremlin already holds are unchangedDocumentation Status
Doc - TODO: required documentation is pending; complete it before merging.Doc - Done: documentation is included here or linked below.Doc - No Need: no user-visible documentation is affected.Documentation files in this PR or paired hugegraph-doc PR:
helm/hugegraph/docs/operations.mdConceptual overview
Concept illustration: re-offer locally loaded graphs to Gremlin through GRAPH_CREATE after name checks; failed binding keeps the REST graph. Not end-to-end test evidence.