Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[Proposal] RFC: evaluating a first Rust proof of concept in the Server

未关闭
#3,238 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
30/100
Issue 类型
功能
描述清晰度
基本清楚
活跃度
活跃
技术栈
java, rust

调研方向

Review the evidence repository dpol1/hugegraph-rust-rfc, especially the README and rfc/ directories. Understand the benchmark harness in hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/benchmark/rfc. The work involves evaluating JNI performance against Java alternatives, not writing code. The outcome is a recommendation based on latency measurements.

由索引模型根据 Issue 内容生成。

描述

Status: draft for review. Parent: apache/hugegraph#3110. Groundwork: #3145, #3146. Snapshot: apache/hugegraph 83ef9f3fa.

  • Evidence repository: dpol1/hugegraph-rust-rfc at commit b094369. Its README explains how to reproduce the runs. Paths below in its rfc, spike and results directories link to it.
  • Benchmarks and harness: benchmark/rfc on the branch prototype/rfc-spike of dpol1/hugegraph (commit 1c62d1fe7), not proposed for merge.
  • Paths that start with a HugeGraph module name are in apache/hugegraph at 83ef9f3fa.

1. Objectives, scope, non-goals

Question: which Server-core candidate offers a verifiable benefit large enough to justify the interoperability, compatibility, and maintenance cost of a Rust implementation?

Latency criterion, committed before any measurement (rfc/thresholds.md): T_native / T_Java <= 0.5. T_native covers the crossing, conversions, copies, the operation, and building the result.

Scope: the candidates of the #3145 inventory (LongEncoding, NumericUtil, both BytesBuffer copies, IntSet/IntMap, IntMapByDynamicHash) and one non-codec candidate, the Jieba analyzer. Server core is the perimeter this RFC proposes; the maintainers have not chosen it.

Non-goals:

  • Rust code in apache/hugegraph
  • storage baselines (#3223 covers them)
  • toolchain client work
  • a proof of concept on BytesBuffer before #3090 is decided

2. Candidates and decision

All numbers are JMH average times per call on one Linux x86_64 laptop (section 6). The table uses these terms:

  • Java cost (stage A): time per call of the current Java operation.
  • Calibration (stage B): time per call of the Java-to-Rust crossings that a native version of the operation would need.
  • Declared path: how those crossings are made, JNI through the jni-rs with_env wrapper around raw JNI calls on stack buffers (section 3).
  • Loads: small draws values from 0 to 999, mixed from the full long range, and large from the 1000 values below Long.MAX_VALUE. For validB64Char the character is early in the alphabet, late in it, or absent.
  • Stop and explore: for one operation and load, the declared path stops when the lower bound of the calibration exceeds half the upper bound of the Java cost; both bounds come from 99.9% confidence intervals (rfc/thresholds.md). Otherwise the verdict is explore, which means only that the rule does not allow a stop. A stop applies to the declared path and that load, not to every possible native implementation.
  • Stage C: measurement of a complete native implementation for an operation that reaches explore. It was not run.
  • Not distinguishable from the harness: the Java cost does not separate from the harness baseline, a benchmark method that reads the same inputs without running the operation. The rule is not applied to such a case.
  • Outcomes: evidence contrary when the declared path stops; evidence insufficient when the available evidence leaves the question open; not evaluated when nothing was measured, for the reason given in the row.
Candidate Outcome Evidence
LongEncoding encoders (encodeNumber, encodeSortable, encodeSignedB64) Evidence contrary for the declared path and loads Java 38 to 182 ns. Creating the result string across the boundary alone costs 166 to 171 ns (rfc/stage-c-gate.md)
LongEncoding.decodeSignedB64 Against the current Java: stop on small and large; evidence insufficient on mixed (the gate says explore; stage C was not run). Against an improved Java decoder (same behaviour on the verified cases, section 4): evidence contrary for the declared path on all loads Current Java 25 / 280 / 263 ns; improved Java 9.5 / 21.7 / 23.2 ns (small / mixed / large, same campaign); native read calibration 128 to 143 ns (rfc/stage-c-gate-d1.md)
LongEncoding.validB64Char Evidence contrary for the declared path Java 5.7 to 25.4 ns; calibration 61 ns (rfc/stage-c-gate.md)
NumericUtil (numberToSortableLong, numberToSortableBytes, sortableBytesToNumber) Evidence contrary for the declared path; one case not distinguishable from the harness Java 3.8 to 11.5 ns; calibrations 59 to 158 ns (rfc/stage-c-gate.md)
BytesBuffer (core and struct) Not evaluated Blocked by #3090. The two copies already meet on one live path: the store ScanPartition service decodes server-written rows with the struct copy and passes no schema, while the core property format relies on schema metadata. rfc/matrix.md gives the call chain. Vermeer loads graphs through this service. apache/hugegraph-computer#361 reports Can't construct Cardinality from code 0 in the struct readProperty there, an error consistent with the mismatch; it was not reproduced here, and the report lists Store and PD images tagged latest
IntSet / IntMap Evidence insufficient for Rust The motivation is maintenance (sun.misc.Unsafe) and memory, both outside the latency criterion. JEP 471 (https://openjdk.org/jeps/471) maps every member they use to VarHandle; that Java path was not measured here
IntMapByDynamicHash Not a candidate today No production instantiation at 83ef9f3fa (rfc/matrix.md)
Jieba analyzer Evidence insufficient Tokens are persisted as search-index row keys. The jieba-rs README says nothing about compatibility with jieba-analysis 1.0.2, and a token-equality run on real text is missing (rfc/matrix.md)

Recommendation: on the measured loads, the Java optimization makes it unjustified to continue the studied JNI path with respect to the 2x objective. Every mapped operation and load stops on this path, with two exceptions:

  • numberToSortableLong(Long) on the small load, whose cost is not distinguishable from the harness.
  • The current decodeSignedB64 on the mixed load, which ends as evidence insufficient (the gate says explore; stage C was not run). Against the improved Java decoder (21.7 ns instead of 280 ns on that load), the declared path stops on every load.

The spike on decodeSignedB64 therefore ends here: no stage C, and the wrapper question stays deferred. The other candidates keep the outcomes in the table.

The conclusion is unfavourable to the measured JNI path, on the measured loads, with respect to the 2x objective; it does not extend to every fine-grained call or to every native implementation. Nor does it show that Rust is without value for codecs in general.

The conclusion is compatible with the boundary principle of the refactor/rust-rewrite-design branch: explicit RPC or a C ABI, avoiding fine-grained cross-language calls (principle 5 of its design document, linked in the next section). Coarse-grained or batched boundaries were not measured here; a later candidate of that kind would need its own evidence, against the #3223 baselines.

Relation to refactor/rust-rewrite-design

The upstream branch refactor/rust-rewrite-design (apache/hugegraph, head a92da16a3 of 2026-09-13) is earlier work on the same roadmap. It covers PD and storage; this RFC does not.

  • What it proposes (review drafts, in Chinese):
    • a phased rewrite in which Rust services coexist with Java behind gRPC (docs/rust-rewrite-design.md, sections 3 and 5)
    • the boundary principle "explicit RPC or C ABI, avoiding fine-grained cross-language calls" (principle 5 in section 2, line 21)
    • a phase 0 of baselines and contracts, with golden samples generated by the Java version that a Rust version must pass (section 8.1)
    • data semantics compared through normalized comparators rather than internal bytes (section 8.2)
    • an engineering standard for Rust code (docs/rust-engineering-standard.md)
  • What it contains:

Where the two meet: on the measured path and loads, this RFC's measurements are compatible with principle 5. The byte-exact corpus of #3146 and the branch's normalized comparison answer different questions: persisted encoded keys need exact bytes; external semantics can be compared after normalization.

3. Boundary

Each candidate faced three options:

  • Improved Java. Argued first; measured when it decides the recommendation, as it did for decodeSignedB64 (section 2). For IntSet/IntMap it is argued only (JEP 471).
  • In-process JNI. The declared path: the jni-rs 0.22.4 with_env wrapper around raw JNI calls on stack buffers (rfc/thresholds.md). The wrapper catches Rust panics that unwind and turns them into Java exceptions. spike/rust/src/lib.rs states the JNI obligations of each call; section 4 lists the checks. FFM is final only from JDK 22 (JEP 454), so it needs an explicit baseline decision; it was not measured.
  • Out-of-process. Excluded from the initial perimeter of scalar operations as a scope choice, not by measurement.

A crossing through the declared path with no JNI call inside the closure (passLong) costs 59 ns (57.9 to 60.1), against a harness baseline of 3.6 ns (results/20260923T134724Z-stage-b). That cost belongs to the measured path; it is not attributed to JNI or to the panic capture. It sets the verdicts for the scalar operations, whose budgets (half the Java cost) are about 2 to 13 ns.

Removing with_env would not force panic = "abort": a directly written boundary could keep catch_unwind with panic = "unwind". Its exception handling, references, and error paths would then need their own design and checks, and nothing here shows it would be faster or as robust.

If a future candidate is recommended, its shape would be:

  • The existing Java class keeps its interface.
  • A Java implementation and a native one sit behind an internal seam chosen at class initialisation.
  • The Java implementation is the fallback for availability and compatibility, not for recovery from a fatal native failure.
  • The native library loads where HugeSecurityManager allows it; it refuses loads from Gremlin workers (hugegraph-server/hugegraph-core/.../security/HugeSecurityManager.java:144-149). Loading at startup is one option.

Unpaired surrogates: on the declared read path (GetStringRegion), UTF-16 units arrive unchanged (results/20260923T134724Z-stage-b/check.txt).

4. Validation

Verified by the spike (spike/VALIDATION.md, results/):

  • Corpus: the #3146 corpus is an oracle of recorded outcomes; it records values and exception classes, not messages. On its 740 decodeSignedB64 cases the improved decoder gives the recorded values and exception classes.
  • Differential test: a separate comparison with the original decoder on 20,020 generated cases, including invalid input, overflow, null, and surrogates. Values, exception classes, and messages are the same, except the NullPointerException message, which the check does not compare. Both checks cover the verified cases, not every possible input.
  • Measurement guards: each run refuses uncommitted changes, rebuilds from the recorded revision, checks the discovered benchmarks against the mapping and the loaded code against the recorded build, and records its commits (rfc/appendix-method.md).
  • Boundary checks: deterministic results, null references, a panic, and an out-of-range array each give the expected Java outcome, with and without -Xcheck:jni, which reported nothing. The out-of-memory branches (a NULL result from NewStringUTF or NewByteArray) were not exercised.

Criteria for a future proof of concept, not verified here:

  • concurrency and resource release for stateful candidates
  • fallback when the native library is absent or of the wrong version
  • observability of which implementation runs
  • upgrade and rollback where a persisted format is involved

5. CI, packaging, security

No candidate is recommended, so this section sizes nothing. Constraints for any later candidate:

The engineering standard on refactor/rust-rewrite-design (docs/rust-engineering-standard.md) already lists toolchain pinning, clippy -D warnings, cargo audit, cargo deny, an unsafe policy, and no panic across FFI.

6. Risks, limits, open questions

Limits of the evidence:

  • One laptop (Intel Core Ultra 7 155H, powersave governor, no CPU pinning), one JDK (Temurin 11.0.30), one Rust toolchain (1.85.0); aarch64 not measured.
  • Every measurement ran on JDK 11. If #3117 moves the baseline to Java 17, the Java costs, and with them the budgets, need measuring again; FFM would still need JDK 22.
  • Stage A and stage B ran in different campaigns, and other work ran on the machine during stage A (rfc/appendix-method.md).
  • The borderline decodeSignedB64 large case changed verdict between two campaigns: explore in the first, stop in the second.

How the loads relate to production: on the index read path, decodeSignedB64 decodes the index label id, which is a schema id. User schema ids need at most 6 characters in this encoding. The other ofStoredString(..., LONG) callers are reached only through TableSerializer.readId, and no concrete TableSerializer exists at 83ef9f3fa. Section 6 of rfc/RFC.md gives the full chain. This bounds the length, not the distribution of lengths in production. The mixed load covers the full long range, the large load the 1000 values below Long.MAX_VALUE, and the small load values from 0 to 999 (1 to 2 characters). On the small load the improved Java costs 9.5 ns instead of 25.2 ns; that saving is measured on that load and not established per index row in production.

Java-side findings, not Rust questions (rfc/java-findings.md):

  • Secondary-index prefixes convert n(n+1)/2 elements per update.
  • CollectionFactory always returns the concurrent segmented IntSet/IntMap.
  • Server and store declare different analyzer versions, with no server path in the repository that triggers a store-side index build.

Open questions for the maintainers:

  1. Is Server core still the preferred first area, given the storage-side exploration on refactor/rust-rewrite-design and the toolchain priority in apache/hugegraph-toolchain#748?
  2. Mentor and Rust reviewers: to be defined. This RFC names no one as responsible.
  3. Is the improved decodeSignedB64 wanted as a small optimization? A checked prototype exists: ImprovedLongEncoding, a class in hugegraph-test with a copy of the alphabet. This RFC claims no application-level benefit. The prefix conversion has a different motivation and would be evaluated separately.
  4. Is a Jieba token-equality run worth doing?
  5. #3117 timing: when the Java baseline moves, should the Java measurements be repeated before the maintainers decide? A raw extern "system" no-op control without with_env would also show whether the validB64Char verdicts on the late and absent loads depend on the wrapper. That control is deferred (section 3).
  6. How do the maintainers want to bring this proposal to discussion and decision on the public mailing list?

Guardrails from #3110

Guardrail Where
Scope, owner, mentor, non-goals Sections 1 and 6 (mentor open)
API, protocol, storage-format, operational compatibility Sections 2 and 3; persisted formats (BytesBuffer rows, LongEncoding/NumericUtil outputs inside index keys, analyzer tokens) not changed
Differential, concurrency, fault-injection, upgrade/rollback tests Section 4; concurrency, fault injection, and upgrade are criteria for a future PoC, out of scope here
Latency, throughput, memory, recovery benchmarks Latency measured; throughput, memory, and recovery out of scope for a first selection (#3223 covers storage)
Security/dependency checks and platforms Section 5; only x86_64 measured
Observability, deployment, coexistence, migration, rollback Section 3 (fallback shape) and section 4 (criteria); no deployment proposed
Acceptance criteria and documentation rfc/thresholds.md; this document and rfc/appendix-method.md

Changes since publication

  • 2026-09-24: section 2 defines its terms before the table, and the text is reorganised into shorter sentences and lists. Verification chains now link to the evidence repository instead of being repeated here. The evidence links point to commit b094369. Since publication, the evidence repository marks rfc/RFC.md as the text first published here, corrects the load-average note in the appendix, states the stage A window in UTC, and removes repeated tables from rfc/matrix.md. No number, source or conclusion changed.
主要语言
Java
星标
3.2k
派生
637
平均合并
3 天 17 小时
30 天内合并 PR
22

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

apache/hugegraph 的其他 Issue

查看 apache/hugegraph 的全部 Issue

相似的 Issue

更多 Java Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。