Back to Blog

ArcadeDB 26.10.1: HA Hardening, Lost-Update Fixes, Consistent Equality, and 4 Security Advisories

ArcadeDB 26.10.1 Release

ArcadeDB 26.10.1 closes 1,615 issues and pull requests under the 26.10.1 milestone: 1,244 issues and 371 PRs, out of 846 pull requests merged and about 3,100 commits since 26.9.1. 984 of the closed issues are bug fixes, 75 of them rated critical.

At a Glance

Area What changed The number
High availability (Raft) Snapshot install, follower catch-up, leadership hand-off, readiness, restart recovery ~300 issues with the ha label
Silent data loss Lost updates on growing records, hash-index key changes applied twice, stale-read writes most concurrent-growth runs lost updates before
Equality Index, SQL scan and Cypher disagreed for FLOAT, DECIMAL, DATETIME, BOOLEAN, null, signed zero one answer everywhere
Query speed Parallel scans, DISTINCT and aggregation, index-ordered LIMIT, striped database lock see below
Wire protocols Postgres, MongoDB, Redis, Bolt, gRPC, Gremlin 100+ fixes, plus Postgres TLS
Security 4 advisories, one critical all affecting 26.9.1 and earlier

Upgrading is recommended for every deployment, and in particular for every HA cluster. Behaviour changes are collected in the upgrade checklist below.

New Features

Server and Operations

  • TLS/SSL for the Postgres wire protocol (#8840).
  • Opt-in OTLP log export, an OTLP/HTTP metrics endpoint fix, and service.name in tracing (#7727, #7294, #7295).
  • Persistent instance id (adb-<uuid>) shown in Studio, the arcadedb.support.id setting, and Studio support requests that run a read-only query support asked for and send the result back (#8685, #8682, #8861).
  • A configurable config directory (#7415) and compact object headers enabled by server.sh when the JVM accepts them (#4537).
  • HEAD on /ready and /health (#8133); the HTTP server binds every local address the configured host resolves to (#8692).
  • Pool metrics for the per-server security executors (#7856).
  • Read-your-writes bookmark (X-ArcadeDB-Commit-Index) on streamed HTTP responses (#7351), and streaming ingestion with a temporary-id mapping returned chunk by chunk (#7353).
  • Startup restore: publishes progress and waits out a conflicting operation (#7440, #7652).

Studio

  • Index search and time series move into Query and Database, with a new Chart tab (#8788).
  • A selected saved query is no longer run automatically (#7049), and the cluster page shows peer capabilities and rolling-upgrade readiness.

Query Languages and Engine

  • Property rename as an in-place primitive, in the Java API and SQL (#7589).
  • COPY (<query>) TO STDOUT over the Postgres wire, in text and binary format (#7188), and NaN-transparent TimeSeries SUM/AVG (#7089).
  • algo.personalizedPageRank takes a personalization vector (#8715); Cypher point() returns Neo4j SRIDs (#3993).
  • TimeSeries: bucket origin/offset on ts.timeBucket, per-column codec syntax in CREATE TIMESERIES TYPE, and AggregationMetrics over HTTP (#8798, #7690, #7717).
  • Graph Importer imports JSON array properties (vector embeddings) from JSONL (#7185).
  • apoc.merge.node/apoc.merge.relationship onMatchProps and db.index.fulltext.query* options (#8117, #8103). Teams moving from Neo4j can see how much of the surface is covered on the Neo4j alternative page.

Remote Clients and gRPC

A spec-driven client generation epic (#4894) closed the gaps between the HTTP, gRPC and Java remote APIs: a gRPC control plane (database lifecycle, backup, security, profiler, restore and import, groups and API tokens), a gRPC time-series API, an HTTP streaming query and vector search, an HTTP//ws insert session with a Java client, and RemoteDatabase access to /ts/* (#7304, #7305, #7306, #7307, #7403, #7406).

Major Highlights

High Availability: a Hardening Pass on Every Path a Cluster Takes to Recover

Around 300 issues carry the ha label this cycle. The ones that mattered most in practice:

  • A follower stuck at a stale term after a restart or rolling restart, costing quorum on the next failure, is fixed at the root: the leader looped on install-snapshot notifications for ever after a follower installed at leaderStart-1 (#8289, #8341, #8360, #8449).
  • Stale entries after a snapshot install. A follower that re-applied its local log while a leader snapshot arrived applied stale entries onto the fresh copy and halted. Every install path now carries a boundary index, and a leader snapshot that is behind what the follower already applied is refused (#8577, #8579, #8454, #8651).
  • A resync reopened pre-resync files. A snapshot resync skipped a database that was closed on the follower, a node could serve a snapshot of a database it had quarantined, and a node elected leader could reopen an unverified closed copy as the cluster’s copy (#8464, #8468, #8605, #8589). An audited override now exists for a peer that can never answer (#8641).
  • Divergence after a graceful restart under load (extra rows and a corrupt unique LSM index) is closed (#8270), and so is a leader DDL that published its pages before its schema entry, so a concurrent replica transaction on the same pages merged against the old schema (#7438, #8686).
  • Leadership hand-off. The automatic hand-offs only screened the target for lag, so a peer that was down was a valid target and the leader then refused every write. Targets are proven reachable now, the budget is sliced per candidate, and a node whose database was dropped for a resync can no longer be elected (#8556, #8491, #8533, #8480).
  • A write that may have committed was retried. MajorityCommittedAllFailedException and ReplicationDispatchedTimeoutException were NeedRetryExceptions, so a committed write could be replayed (#8481). The remote client now sends X-Request-Id, so a write whose response was lost is retried safely (#8526, #8136).
  • Readiness is honest. The security-convergence gate is armed on a runtime join, per join, and on by default; a node re-added with a retained config volume no longer passes it on stale fingerprints (#7819, #8317, #8329, #8414). Followers are gated on the leader’s commit index (#7619).
  • Two concurrent add-peer requests could commit two Raft peer ids for one address (#7802), and the embedded addPeer API seeded no security documents at all (#7820).
  • A long-running seeded chaos test now exercises compound failure and recovery scenarios (#8279), and the HA integration tests run nightly instead of on every push.

New on the HA side: a bulk schema scope so a DDL script replicates as one Raft entry, peer-capability negotiation, peer priority on POST /api/v1/cluster/peer, joining a running node to a cluster, and per-peer capabilities and rolling-upgrade readiness in Studio (#6990, #7219, #7523, #7515, #7538). If you run ArcadeDB in client-server mode with a Raft cluster, this is the release to be on.

Lost Updates and Silent Data Loss

  • SET n = n + 1 lost an increment under READ_COMMITTED when two transactions committed concurrently (#8538). A write computed from a read that went stale is now refused (#8610).
  • Concurrent updates of records that grow past a page lost committed updates in most runs with txPageSlotMerge on (the default), and a document larger than one page was silently overwritten (#8985, #8982, #8988, #8989).
  • HASH indexes applied a key change twice at commit: UNIQUE_HASH let an UPDATE take a key another record held, and NOTUNIQUE_HASH kept a stale entry (#8983).
  • UPDATE ... SET on a nested map key or list index was silently lost, because the owner was never marked dirty (#8027); UPDATE ... REMOVE prop[0] on a Set deleted every element (#8032).
  • One interrupted commit permanently killed a WAL pool slot, so every later transaction on it blocked 30 s and failed (#7768).
  • A UNIDIRECTIONAL edge created while its target was deleted committed as a ghost edge (#8986, #8676).
  • DROP TYPE left the type’s aliases in the schema, so the dropped type came back alive after a restart (#8169); TRUNCATE TYPE never refused a non-empty vertex or edge type (#8042).
  • Console data loss. The console split a command at the first closing brace followed by a newline, so a multi-line UPDATE ... SET x = {json} ran with no WHERE and overwrote every row (#8246).
  • Clean close and WAL. A clean close now fsyncs only the files written since their last sync, and recovery fsyncs before it drops the WAL; a second embedded instance’s clean close could delete another live instance’s files (#8626, #7479, #7502); and a WAL housekeeping timer could delete recovery input (#8600).
  • The schema is written once per DDL statement, never mid-transaction, and schema.json is replaced atomically (#8635, #7279).

The Same Value Now Compares Equal Everywhere

The index, a SQL scan and Cypher disagreed on what “equal” means for several types, so the same WHERE returned different rows depending on the plan. They agree now for FLOAT/DOUBLE, DECIMAL, DATETIME, BOOLEAN, BigInteger, signed zero and null (#8884, #8885, #8886, #8887, #9113, #9114, #9160, #9274).

  • Null equality is decided at execution, not at planning. A statement first run with a value kept its index plan, so a later run with null returned the null-key records again, and a DELETE could remove the wrong rows (#9274, #9238). UPDATE/DELETE with positional parameters read the WHERE from an earlier cached statement (#9245).
  • Precision. A SQL decimal literal keeps digits beyond double precision, JSONL import keeps DECIMAL precision, a suffix-less floating point literal is a Double, and numeric comparison past 2^53 is exact (#8872, #8871, #7609, #7628).
  • Datetimes. An unparseable datetime is refused instead of stored as NULL, and DATETIME_MICROS literals with a space separator stop being truncated (#8090); sub-millisecond precision and temporal round trips were fixed in Cypher and over the wire.
  • Aggregates. LONG sum overflow, COUNT(DISTINCT expr) (a syntax error until now), aggregates in ORDER BY, and min()/max() over an index range (#8972, #8889, #8973, #8812).

A result set that changes after the upgrade is the fix, not a regression.

Faster Queries

  • Parallel scans, aggregation and DISTINCT. Filtered scans and aggregation run on the dedicated scan workers, a transaction that has written nothing still scans in parallel, POST /query no longer opens an auto-commit transaction that blocked it, and a satisfied LIMIT releases its upstream (#8523, #8775, #8594, #8799, #8797). Scans also stopped re-resolving each record and build records from the page in hand (#8265, #8266, #8312).
  • The index serves more shapes: Cypher ORDER BY ... LIMIT from index order, LIKE 'prefix%', STARTS WITH, Cypher min()/max(), Cypher OR index seeks, case-insensitive index ranges, and an adaptive range fetch that parallelises large ranges (#8422, #8666, #8723, #8766, #8333).
  • Concurrent RID lookups scale. The database read/write lock is striped (#8838), and checkDatabaseIsOpen no longer writes lastUsedOn on every call (#8523).
  • Plan caching. UPDATE, DELETE and INSERT no longer re-plan on every execution (#9207), and a correlated LET subquery is memoized per outer binding (#8400, #8441).
  • Graph algorithms. algo.wcc uses a parallel union-find (Afforest) (#9133), and algo.* waits for the restored Graph Analytical View on the first call after a reopen (#9220).
  • Cypher joins on disconnected patterns use a value hash join with compact, bounded buffers instead of buffering the full record of every right-hand row (#8583, #8584, #8585).
  • A JVM-wide heap budget for query buffers keeps concurrent SQL and OpenCypher queries from exhausting the heap together (#8591).
  • TimeSeries: tag-skipping mutable scans, merging of small sealed blocks, shared tag strings and bucket origin/offset for weekly buckets (#8574, #8798, #8794, #8793). For the numbers behind ArcadeDB’s engine speed, see the benchmarks.

Teams building retrieval pipelines on the GraphRAG stack get a round of correctness fixes in LSM_VECTOR and LSM_SPARSE_VECTOR:

  • The graph rebuild loop stops when two records share a vector id, and CHECK DATABASE FIX repairs it (#8946).
  • Aborted transactions no longer leak tombstones or uncommitted vectors into vector indexes, a rebuild reads committed state only, and a delete reuses the persisted graph (#7931, #7974, #7842).
  • Searches during an async rebuild returned poor neighbors; the first search after a reopen loads the graph via the ordinal map; a query that rebuilds a graph honours the JVM-wide rebuild limit (#8862, #8852, #7814).
  • A filter that matches no records was treated as no filter and returned records outside it (#8959); grouped search fills every winning group (#8002).
  • Compaction lost commits and a search during compaction was wrong; BINARY quantization, zero and non-finite vectors, and a window-based MaxScore for SPLADE top-K were fixed (#9132, #9252, #9200).
  • The HNSW build cache is sized from the heap ceiling, so a served database matches an embedded one (#7146).

New: vector.neighbors accepts a subquery result as its filter, dense vector search sees the open transaction’s rows, and sparse-vector indexes can be built over existing rows and rank by exact score (#7125, #7378, #8536, #8576).

Wire Protocols: TLS for Postgres and 100+ Fidelity Fixes

  • TLS/SSL for the Postgres wire protocol (#8840), plus COPY (<query>) TO STDOUT in text and binary format (#7188).
  • Postgres: SET is session-scoped, transactional and read back by SHOW; COMMIT/ROLLBACK inside an implicit transaction block were silently ignored; a trailing semicolon bypassed the transaction-block state machine; ROLLBACK TO SAVEPOINT is refused instead of accepted as a no-op; portals are transaction-scoped; $N parameters work on indexes; the Arrow ADBC driver bootstraps (pg_type, regclass); and binary arrays work (#8028, #8245, #8217, #7178).
  • MongoDB: lossless BSON types (Binary, regex, timestamp, MinKey, MaxKey and JavaScript were silently dropped), exact filter semantics, nested $set, projection, unique _id and replace (#9062, #9137).
  • Redis: a newline batch was classified by its first command only, so a GET followed by an HDEL was declared read-only; INCR/DECR are atomic on the query engine too; MULTI/EXEC no longer double-applies; values are binary-safe; INCRBYFLOAT is supported (#8247, #8248).
  • Bolt: node ids match id(n), system procedures are served only when the statement is that call, and failures are named.
  • gRPC and remote clients: a lost response on a self-committing write is an unknown outcome, not a retryable error; RemoteDatabase.transaction() has the partial-commit guard; RemoteServer drop/createUser/dropUser go through httpCommand (#8525, #8062, #7796).
  • Gremlin: label loss, profile double execution, idempotent drop, fractional index bounds and the advertised Gremlin port were fixed (#8258, #7408, #9147).

Security Advisories

This release closes four security advisories, published in full (impact, affected versions and credit) as GitHub Security Advisories on the repository. All four affect 26.9.1 and earlier, are patched in 26.10.1, and were reported by @manus-pi.

  • GHSA-h2j4-28h8-cj5v (critical): the Gremlin Groovy engine lacked a scripting authorization check, enabling remote code execution by an authenticated user.
  • GHSA-7fcg-pg8x-wf7w (medium): Bolt SHOW DATABASES listed every database on the server without per-user authorization filtering.
  • GHSA-hw9x-xx38-rg28 (medium): Bolt lacked per-database authorization, so an authenticated user could read another database’s schema metadata.
  • GHSA-9c7g-grf7-j2r5 (medium): read-only users could change edge type lightweight/unique schema flags via ALTER TYPE, bypassing UPDATE_SCHEMA.

Also Hardened in This Release

  • Bolt applies the per-user database and type access to database selection and schema listings (#8415).
  • Schema DDL gates: ALTER TYPE edge-type settings and REBUILD TYPE require UPDATE_SCHEMA (#8398).
  • Gremlin scripting is restricted further, including io() and lambdas (#8227, #8338).
  • Request bounds: a chunked request body no longer bypasses httpBodyContentMaxSize, gzip and Snappy ingest are bounded after decompression, and /ws frames are bounded (#7772, #8084, #8065). MongoDB wire regexes in aggregation are time-bounded (#9164), and a TimeSeries aggregation checks its caller’s ceiling before allocating buckets (#7476).
  • Secrets: set_server_setting and the MCP request log mask hidden settings, and resetAll() no longer dumps the whole configuration (#8038, #8965).
  • AI chat store: the legacy-directory migration no longer awards a collided chat store to the wrong user, and accounts that differ only in case no longer share one (#7620, #8154).
  • HA: five TLS and security issues were fixed, security mutations are refused off the leader, and the Raft gRPC peer allow-list is no longer frozen at startup (#7854, #8407, #7162).
  • HTTP Basic credentials are split on the first colon only (#7783).

Major Fixes and Improvements

Storage, WAL and Integrity

  • A write to a record the transaction already deleted is dropped, not reported as a deadlock (#7149).
  • Bucket space reuse, a free tail measured directly on a rebased delete, and edge delete page merges were fixed (#8660, #8401, #9233).
  • LIGHTWEIGHT edge types: reads answered 0, and TRUNCATE and TRAVERSE were wrong (#7477, #7480, #7481).
  • CHECK DATABASE finds records that violate their own type’s existence constraints (#7952), and a failed REBUILD INDEX restores the old index (#9254).
  • A LIMIT query could leave the database unable to close because cancelling its parallel scan interrupted a page read (#8944).
  • Windows backup archive nesting and path separators were fixed (#7586), and the ext4 lost+found directory is no longer registered as a database (#8805).
  • Backups cover the configuration files in the snapshot (#6114), and the HA snapshot ZIP skips index-compaction temporaries (#8019).

Indexes

  • A range seek landed in the middle of a same-key run (#7611); transaction index ranges after compaction and the tx-overlay filtering rules shared across read paths were fixed (#8817, #6970).
  • CREATE INDEX on a populated type no longer truncates datetimes to milliseconds, and CREATE INDEX IF NOT EXISTS works across super types (#7164, #7228).
  • SQL queries no longer fail while an index is created or dropped on the same type (#8855).
  • Hash index overflow walk cost, prefix seek and lookup at page end were fixed, along with unique prefix lookup and IS NULL on unique indexes (#9253, #8806).
  • A unique BINARY key skipped the uniqueness check because array keys were compared shallowly (#7881).

SQL

  • Parser re-render, copy(), LIMIT counting, positional LIMIT ? SKIP ? binding, comments in ORDER BY/GROUP BY, hex literals, nesting guards and planner exceptions were fixed (#7800, #8434, #9122, #9154).
  • A scalar left operand of CONTAINSANY silently matched nothing, and expand() of a native array yields one row per element (#8475, #7910).
  • The UPDATE Halloween problem, GROUP BY projection alias rebuilt per record, ORDER BY over a null scan, and FULL_TEXT never answering exact lookups were fixed (#8814, #8260, #8664, #8442).
  • SQL scripts replay blocks correctly, and EXPLAIN has parity across sqlscript operation types (#8633, #7576).

openCypher

  • Relationship uniqueness is scoped to the clause, MERGE binds the path it merged, and an eager read/write barrier means a query never reads back what it just created (#7165, #7169, #7171).
  • MERGE ... ON MATCH SET no longer loses a concurrent increment, nodes are written once for MERGE ... SET and CREATE ... SET, and LIMIT after a write no longer cuts the write short (#8538, #8735, #8826, #8827).
  • An empty FOREACH changed OPTIONAL MATCH results; FOREACH/REMOVE scope checks, existence constraints checked at end of statement, and CALL (*) no longer inheriting the outer clauses’ variables were fixed (#8733, #8105, #9205).
  • Unidirectional in()/both() with a view, queries on the incoming side of unidirectional edge types, DELETE of a node with several relationships, and code-point string handling were fixed (#8939, #8625).

TimeSeries

  • A TIMESTAMP column not declared first round-tripped through JSONL with its values in the wrong columns (#7899), and aggregation push-down mixed row and schema column indexes between the mutable and sealed halves (#8140).
  • A query now gives one answer whichever layer holds the data; sealed-store identity and repair were fixed, and an empty TAG key is refused instead of dropped silently (#7733, #8738, #8563).
  • PromQL conformance gaps were closed, and ts.timeBucket() is exposed as LocalDateTime (#8926, #7610).

Importer, GraphBatch and Async

  • Importer transaction ownership, RDF and CSV edge cases, row-error handling and post-import validation were fixed (#7943, #7948).
  • GraphBatch wrote the header end offset one byte short on property-less edges, wrote a null for a declared edge property as a type tag with no value, and now honours the caller’s transaction (#7448, #9018, #9242).
  • A periodic batch-commit conflict is retried instead of silently discarded (#7615), and insert_many and GraphBatch.create_vertex roll back on every failure path (#7882).

Upgrade Checklist

Behaviour changes are collected in the release notes. The ones worth checking before you upgrade:

  1. Security-convergence readiness gate is on by default on HA clusters, armed per join. A joining node is not reported ready until its security state matches the leader’s (#7819, #8414).
  2. arcadedb.server.apiTokenRequireSecureTransport: check the first-run Docker flow on http://localhost:2480, where Studio’s API-token mint is refused once it is on (#8765, #7804).
  3. Stricter refusals instead of silent acceptance: an unparseable datetime, a map value the declared type cannot take, Type.convert of an impossible value, ROLLBACK TO SAVEPOINT over the Postgres wire, an UPDATE whose value was computed from a stale read, and TRUNCATE TYPE on a non-empty vertex or edge type (#8090, #9110, #8610, #8042).
  4. Queries that returned a plan-dependent answer now return the equality-consistent one (null keys, FLOAT/DECIMAL/DATETIME/BOOLEAN, signed zero).
  5. Index default page size changed (#9250); existing indexes keep their size.
  6. Schema DDL gates: ALTER TYPE edge-type settings and REBUILD TYPE need UPDATE_SCHEMA (#8398); Gremlin scripting and io() are restricted (#8227).
  7. Rolling upgrade of an HA cluster: peers negotiate capabilities, and group or API-token changes wait until every peer supports them; Studio’s cluster page shows the state (#7219, #7538).
  8. LSM_SPARSE_VECTOR rescoring oversamples by default (rescoreOversample 2) and ranks by exact score (#8576).

Dependency Updates

About 140 dependency bumps landed in this cycle, almost all through Dependabot. The notable ones: Netty 4.2.18, Apache Ratis 3.3.1, Gremlin (TinkerPop) 3.8.2, GraalVM 25.x, JVector 4.0.1, Jackson 2.22.3, Protobuf 4.36.2, SLF4J 2.0.20, Logback 1.6.5, OpenTelemetry 1.66.0, SnakeYAML 2.7, lz4-java 1.12.0, commons-lang3 3.21.0, JLine 4.4.6, Neo4j Java driver 6.3.0, Jedis 8.0.1 and gRPC protos 2.78.0. On the Studio side: ApexCharts 7.6.1, marked 18.0.14 and swagger-ui-dist 5.33.0.

Getting Started with 26.10.1

Docker

docker pull arcadedata/arcadedb:26.10.1

Visit our Docker Hub repository for more information.

Maven

<dependency>
    <groupId>com.arcadedb</groupId>
    <artifactId>arcadedb-engine</artifactId>
    <version>26.10.1</version>
</dependency>

All artifacts are available on Maven Central.

Documentation

For details on features and usage, see the documentation.


Download ArcadeDB 26.10.1 now: GitHub Releases

Thanks to everyone who reported, reproduced, reviewed, tested and fixed, in particular @ruispereira, @tae898, @rspereiratech, @YGY-001, @jjj-n, @gramian, @Das-Rabindra, @singhpratech, @rlaveycal, @wkpark, @rbonestell, @justinblethrow-cloud, @philiptran-tech, @GYWang1983, @astarso, @mdre, @ivan-velikanov, @willemijn-zeno, @shlomiassaf, @nx-mama, @maurovit, @LetMeSleep8h, @suhanrain, @manus-pi, @BingEdward, @anatshad, @borutjures, @ExtReMLapin, @jawahar4k, @JMQuill-SF, @M-Tesla, @odysseaspenta, @sainiteesh36, @shulei5831sl, @singh-hovr and @wsalembi.

Luca Garulli ArcadeDB Founder