[Feature] Orchestrate graph creation through PD: new graphs are not consistently available across Server replicas ("Could not rebind [g]")
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 25/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Đình trệ
- Công nghệ
- java
- Lĩnh vực
- backend-api-design, databases, distributed-systems
Hướng nghiên cứu
Bắt đầu với hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java, đặc biệt là createGraph và graphAddHandler, sau đó đọc GraphMetaManager.java và đường dẫn PD watch được mô tả trong issue. Truy vết cách các replica mở và bind các graph trước khi xác định API và hành vi trạng thái của phase còn lại; được xem là hoàn tất khi trạng thái sẵn sàng hoặc quyền sở hữu trên toàn cluster được nêu rõ và kiểm thử, không còn khoảng thời gian rebind đã được báo cáo.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Status (2026-08-13)
Phase 1 (minimal patch) — merged. #3138
landed on master on 2026-08-12. The Server that handles CreateGraph now
waits for its own local Gremlin binding before returning HTTP 200.
This issue stays open for Phases 2–3 below: cluster-wide readiness
(#3139) and moving graph
creation ownership into PD so Servers become pure converging replicas.
Load-balanced multi-Server deployments can still see Could not rebind on
replicas that have not yet converged. The Helm chart documents mitigations in
PR #3132.
Summary
In distributed mode (PD + HStore), creating a graph returns 200 before the
graph is usable everywhere, and in the worst case before it is usable
anywhere. The creating Server registers its own Gremlin binding through an
event it does not await, and every other Server replica converges
independently through a PD metadata watch plus a local backend open. There is
no upper bound on that window and no signal when it closes. Any deployment
that load-balances several Servers turns the window into a user-visible 400:
a query lands on a replica that has not yet bound the graph and fails with
TinkerPop's "Could not rebind" error.
I propose fixing this in three phases: make the creating replica synchronous
(a one-line parity fix), give clients a way to await cluster-wide
availability, and finally move graph creation into PD so Servers become pure
converging replicas of PD state. The last step is the same direction as
#3118 / #3119, which make Server-side init-store skippable in distributed
mode because the storage side already owns its metadata.
Reproducible symptom
Setup: current master images, PD + HStore, two or more Servers behind one
load-balanced endpoint (in Kubernetes, a ClusterIP Service; any round-robin
proxy reproduces it). Timeline:
t0 client -> LB -> Server A : POST create graph "graph_164" (DEFAULT space)
t1 Server A : opens backend, writes config to PD meta, puts the graph in
its local map, fires GRAPH_CREATE without awaiting it,
returns 200
t2 client -> LB -> Server B : POST /gremlin with
aliases {"g": "__g_DEFAULT-graph_164"}
t3 Server B : PD watch not yet delivered, or delivered but the backend
open is still running; the Gremlin global bindings contain
no __g_DEFAULT-graph_164
t4 client <- 400 : Could not rebind [g] to [__g_DEFAULT-graph_164] as
[__g_DEFAULT-graph_164] not in the Graph or TraversalSource
global bindings
tN Server B : watch fires -> reads config from PD -> opens the backend
graph -> injects the binding (also via a non-awaited event);
only now has this one replica converged
Observed end to end with Hubble driving such a deployment: creating a graph
succeeds, and the immediately following index build fails with
[2104] execute indexing failed ... Could not rebind [g] to
[__g_DEFAULT-graph_164] as [__g_DEFAULT-graph_164] not in the Graph or
TraversalSource global bindings
The [2104] prefix is Hubble's error wrapper; the embedded text is the 400
body from the Gremlin endpoint. Retrying the same request after a few
seconds succeeds once the replica converges, which is exactly the signature
of the race rather than of a broken graph.
Where the window comes from (verified on master @ 1716c774)
All paths below are relative to
hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph unless
stated otherwise.
-
The creating Server does not await its own binding registration.
core/GraphManager.java#createGraph(String, String, String, Map, boolean)
(line 1222) on the PD path opens the graph (line 1334), persists the
config to PD meta and publishes the add notification (lines 1346 to 1348,
metaManager.addGraphConfigandmetaManager.notifyGraphAdd), puts the
graph into the REST-side map (line 1350), then fires
this.eventHub.notify(Events.GRAPH_CREATE, graph)at line 1356 and moves
on.EventHub.notifysubmits the listener invocation to an executor and
returns aFuture
(hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/event/EventHub.java,
lines 166 and 192), so the 200 can be written before any listener has
run. The non-PD local path already does this correctly:
createGraphLocalcallsnotifyAndWaitEvent(Events.GRAPH_CREATE, graph)
at line 1206, whose implementation (line 1774) blocks onfuture.get().
The PD path lost that parity. -
The Gremlin binding only exists after the event listener runs.
auth/ContextGremlinServer.javalistens for GRAPH_CREATE (lines 66 to
73) andinjectGraph(lines 126 to 141) registers the graph and a
traversal source namedG_PREFIX + namewithG_PREFIX = "__g_"(line
49) into TinkerPop's server-side GraphManager and global bindings. Every
Gremlin request that aliasesghits those bindings: the REST
/gremlinendpoint (api/gremlin/GremlinAPI.java,@Path("gremlin")
line 42, forwarding at line 68) proxies to the embedded Gremlin Server
(api/gremlin/GremlinQueryAPI.javaline 61,
ServerOptions.GREMLIN_SERVER_URL), where TinkerPop's
HttpGremlinEndpointHandlerrejects unknown aliases with exactly the
observed message (TinkerPop 3.5.1, pinned bytinkerpop.versionin
hugegraph-server/pom.xmlline 45; the format string is at
HttpGremlinEndpointHandler.javaline 299 in the 3.5.1 tag). The Cypher
endpoint builds the same alias server-side
(api/cypher/CypherAPI.javaline 115,aliases.put("g", "__g_" + graphInfo)), so it fails identically. The binding name in the error,
__g_DEFAULT-graph_164, isG_PREFIXplusspaceGraphNamejoined with
DELIMITER = "-"(core/GraphManager.javalines 151 and 263), which
confirms the failure is this lookup and not a client-side typo. -
Other replicas converge through a watch with no bound and no signal.
notifyGraphAddis a KV put of the graph name onto a well-known key
(hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/meta/managers/GraphMetaManager.java
line 77). Every Server listens on that key through PD's watch mechanism
(meta/MetaManager.javaline 239 delegating toGraphMetaManagerline
168, backed bymeta/PdMetaDriver.javaline 111, which registers a PD
client watch). The handler,core/GraphManager.java#graphAddHandler
(line 2370), reads the config back from PD meta and calls
createGraph(..., init=false)at line 2415, which opens the backend
stores against PD/HStore and then fires the same non-awaited
GRAPH_CREATE event from step 1. A replica's convergence latency is
therefore watch delivery plus a full backend open plus asynchronous
event dispatch. Nothing reports completion back to PD, the creating
Server, or the client. -
Convergence failures are silent and terminal. In
graphAddHandler, a
failed open is only logged whenstartIgnoreSingleGraphErroris set
(line 2427); under the current implementation there is no retry and no
health signal, so a replica that hits this path continues serving while
remaining diverged, and every probe still passes.
Why readiness probes cannot mask this
While testing distributed deployments on Kubernetes, the natural first
instinct is "gate the Service on readiness". It cannot work here:
- Readiness is evaluated per pod against a startup-time contract. Graphs
are created at runtime, after every replica is already Ready, so no probe
definition can reference them. - Making readiness mean "this replica has bound every graph in PD meta"
would remove every replica from the Service on each creation, trading a
scoped 400 for a cluster-wide outage, and would still not cover the
creating replica's own gap between the 200 and its asynchronous
injectGraph. - The failure is per graph, not per process. A replica that missed one
graph (step 4 above) is healthy for every other graph; readiness is the
wrong granularity to express that.
This is a data-plane consistency question, and only the creation protocol
itself can answer it.
Proposal, in phases
Phase 1, quick win (merged in #3138, 2026-08-12): make the creating replica consistent at 200. Change
line 1356 of core/GraphManager.java from this.eventHub.notify(...) to
this.notifyAndWaitEvent(Events.GRAPH_CREATE, graph), restoring parity
with the local path at line 1206. After this, the Server that answered the
create can always serve the graph immediately, which makes sticky routing a
complete client-side workaround instead of a probabilistic one. Two details
worth deciding during review: notifyAndWaitEvent currently swallows
listener failures with a warning (line 1774), so a failed injection should
probably fail the create instead; and the wait occupies a REST worker for
the duration of the binding, which is the same cost the local path already
pays.
Phase 2: let clients await cluster-wide availability. Give each Server a
per-graph status it writes to PD meta after its own injectGraph
completes, and expose the aggregate through the REST API, for example
GET /graphspaces/{space}/graphs/{name} gaining a status with
CREATING / READY(n of m) / FAILED(replica) semantics, or a dedicated
status endpoint. Optionally add a blocking flag to the create call that
returns only when all currently registered Servers report READY. This also
turns the silent divergence of step 4 into something operators can see and
alert on.
Phase 3, the architectural direction: PD owns graph creation. The create
API (on any Server, or on PD directly) becomes a request to PD; PD
validates, persists the config, and records the desired state; Servers
reduce to pure converging replicas: watch, open, bind, report status.
The creating Server no longer has a privileged code path at all, which
removes the asymmetry that produced this race. This is the same direction
already taken for initialization: #3118 argued, and #3119 implements
(under review), that Server-side InitStore is unnecessary in PD/HStore
mode because storage-side metadata is owned by PD, gated by a dedicated
init_store.enabled option. Graph creation is the remaining place where a
single Server locally mutates cluster-level state and then hopes the rest
of the cluster catches up. Moving it behind PD finishes that thought.
Interactions and risks
- Ordering: the add notification is a plain KV put of the latest graph
name onto one shared key (GraphMetaManager.javaline 77). The phase 2
status keys should be per graph, not piggybacked on that channel, so
rapid consecutive creations cannot coalesce. - Rollback: today
addGraphConfiglands in PD before schema template
preparation runs (prepareSchema, line 1365), so a late failure on the
creating Server leaves a config other replicas will happily converge on,
with the client seeing an error. Phase 3 needs an explicit creation
state machine in PD (PENDING until commit, cleanup on failure) rather
than inheriting this publish-then-finish order. - Mixed-version clusters: phase 1 is purely local to one Server and safe
to roll. Phase 2 must treat replicas that never write status keys (old
Servers) as UNKNOWN and document that clients fall back to retry. Phase
3 changes ownership and needs either a PD-side compatibility mode that
still accepts Server-initiated creation, or a cluster version gate. - Failure visibility: any phase that makes creation synchronous or
status-reporting must decide what a partial failure returns; silently
logging and returning 200, as the event path does today, is the current
behavior being fixed, not a fallback to keep.
Context
While testing distributed deployments on Kubernetes I hit this window
directly. For reference, the deployment tooling is the Helm chart
contributed in PR #3132 (issue #3131); it fronts the Server replicas
with one load-balanced Service, which is what makes the window user
visible: Hubble's ordinary create-then-index flow trips it on the first
try. The chart README documents the remaining cross-replica window as a
known limitation with retry, sticky routing, and per-replica graph-list
polling as mitigations. Phase 1 is addressed by #3138; Phases 2–3 remain
here. A concrete API sketch for phase 2 is in #3139.
Visual summary
- Ngôn ngữ chính
- Java
- Star
- 3.2k
- Fork
- 640
- Merge trung bình
- 1 ngày 7 giờ
- Pull request đã merge (30 ngày)
- 88
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của apache/hugegraph
-
[Bug] PD task REST: balanceLeaders throws a bare HTTP 500 during the balance-shard window, and task responses cannot be told apart from a no-op or a follower answerCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
apache/hugegraph#3231 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Task] Establish systematic performance baselines and optimization trackingCó thể đã có người làm @contrueCT đã nhận 1 ngày trước. Đang mởhelp wanted improvement rust
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 12/100
apache/hugegraph#3346 · 2 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug] Cypher endpoint rejects the standard date() function as unknownCó thể đã có người làm @Mihir-Rabari đã nhận hôm nay. Đang mởbug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 52/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug] CI accepts obsolete PR targets after retargeting or force-pushCó thể làm lại được Pull request cho issue này đã bị đóng mà không được merge. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 25/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của apache/hugegraph
Issue tương tự
-
BC remittance upload: a claim MSP refused (S03) is saved with a blank status instead of refused (F)Đang mởtype: bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
carlos-emr/carlos#4933 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
apache/rocketmq-dashboard#6925 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Vakore/ZappierGames#147 ·
-
Post 1.11.2 to SpigotMCĐang mởspigotmc
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 92/100
Dans-Plugins/Wild-Pets#356 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Post 1.16.2 to SpigotMCĐang mởspigotmc
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 85/100
Dans-Plugins/Medieval-Roleplay-Engine#371 ·
Maintainer thường phản hồi trong vòng 1 ngày