ArcadeDB 26.10.1
Overview
1,615 issues and pull requests closed under the 26.10.1 milestone - 1,244 issues and 371 pull requests - 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.
The headline work is in six places:
- High availability (Raft) - snapshot install, follower catch-up, leadership hand-off, readiness gating and restart recovery were hardened end to end. Followers stuck at a stale term, divergence after a graceful restart and resyncs that reopened stale files are closed.
- Lost updates and silent wrong data - concurrent read-modify-write on growing and multi-page records, hash-index key changes applied twice,
MERGE ... ON MATCH SET n = n + 1losing an increment, and stale-read writes are now refused or merged correctly. - Query speed - parallel filtered scans, parallel
DISTINCTand Cypher aggregation,ORDER BY ... LIMITanswered from index order,LIKE 'prefix%'andmin()/max()served by the index, a striped database lock so concurrent RID lookups scale, and a LET subquery cache. - Equality and comparison now agree everywhere - index, SQL scan and Cypher gave different answers for
FLOAT/DOUBLE,DECIMAL,DATETIME,BOOLEAN, null and signed zero. They give the same answer now. - Wire protocols - the Postgres, MongoDB, Redis, Bolt, gRPC and Gremlin front ends got more than a hundred protocol-fidelity fixes, plus TLS for the Postgres wire protocol.
- Vector search - rebuild loops, compaction races, grouped search, empty filters and sparse-vector ranking.
Upgrading is recommended for every deployment, and in particular for every HA cluster. Behaviour changes are collected in Upgrade notes.
Contents: New features · Highlights · Security advisories · Major fixes and improvements · Upgrade notes · Dependency updates
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.namein tracing (#7727, #7294, #7295). - Persistent instance id (
adb-<uuid>) shown in Studio, thearcadedb.support.idsetting, 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.shwhen the JVM accepts them (#4537). HEADon/readyand/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). - HA: 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 in Studio (#6990, #7219, #7523, #7515, #7538).
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 STDOUTover the Postgres wire, in text and binary format (#7188), and NaN-transparent TimeSeriesSUM/AVG(#7089).algo.personalizedPageRanktakes a personalization vector (#8715); Cypherpoint()returns Neo4j SRIDs (#3993).- TimeSeries: bucket origin/offset on
ts.timeBucket, per-column codec syntax inCREATE TIMESERIES TYPE, andAggregationMetricsover HTTP (#8798, #7690, #7717). - Vector:
vector.neighborsaccepts 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). - Graph Importer imports JSON array properties (vector embeddings) from JSONL (#7185).
apoc.merge.node/apoc.merge.relationshiponMatchPropsanddb.index.fulltext.query*options (#8117, #8103).
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).
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; 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) (#8270); 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.
MajorityCommittedAllFailedExceptionandReplicationDispatchedTimeoutExceptionwereNeedRetryExceptions, so a committed write could be replayed (#8481); the remote client now sendsX-Request-Idso 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); the embedded
addPeerAPI 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.
Lost updates and silent data loss
SET n = n + 1lost an increment underREAD_COMMITTEDwhen 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
txPageSlotMergeon (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_HASHlet anUPDATEtake a key another record held, andNOTUNIQUE_HASHkept a stale entry (#8983). UPDATE ... SETon 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 TYPEleft the type's aliases in the schema, so the dropped type came back alive after a restart (#8169);TRUNCATE TYPEnever 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 noWHEREand 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); a WAL housekeeping timer could delete recovery input (#8600).
- The schema is written once per DDL statement, never mid-transaction, and
schema.jsonis 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
nullreturned the null-key records again, and aDELETEcould remove the wrong rows (#9274, #9238).UPDATE/DELETEwith positional parameters read theWHEREfrom an earlier cached statement (#9245). - Precision. A SQL decimal literal keeps digits beyond double precision, JSONL import keeps
DECIMALprecision, a suffix-less floating point literal is aDouble, and numeric comparison past 2^53 is exact (#8872, #8871, #7609, #7628). - Datetimes. An unparseable datetime is refused instead of stored as
NULL, andDATETIME_MICROSliterals with a space separator stop being truncated (#8090); sub-millisecond precision and temporal round trips were fixed in Cypher and over the wire. - Aggregates.
LONGsum overflow,COUNT(DISTINCT expr)(a syntax error until now), aggregates inORDER BY, andmin()/max()over an index range (#8972, #8889, #8973, #8812).
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 /queryno longer opens an auto-commit transaction that blocked it, and a satisfiedLIMITreleases 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 ... LIMITfrom index order,LIKE 'prefix%',STARTS WITH, Cyphermin()/max(), CypherORindex 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);
checkDatabaseIsOpenno longer writeslastUsedOnon every call (#8523). - Plan caching.
UPDATE,DELETEandINSERTno longer re-plan on every execution (#9207); a correlated LET subquery is memoized per outer binding (#8400, #8441). - Graph algorithms.
algo.wccuses a parallel union-find (Afforest) (#9133), andalgo.*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).
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 and are patched in 26.10.1. All four 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 DATABASESlisted 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/uniqueschema flags viaALTER TYPE, bypassingUPDATE_SCHEMA.
Also hardened in this release
Authorization and input-bounds fixes found by audit and by other reporters.
- Bolt applies the per-user database and type access to database selection and schema listings (#8415).
- Schema DDL gates:
ALTER TYPEedge-type settings andREBUILD TYPErequireUPDATE_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/wsframes 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_settingand the MCP request log mask hidden settings, andresetAll()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, 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 (#8660, #8401, #9233).
- LIGHTWEIGHT edge types: reads answered 0,
TRUNCATEandTRAVERSEwere wrong (#7477, #7480, #7481). CHECK DATABASEfinds records that violate their own type's existence constraints (#7952); a failedREBUILD INDEXrestores the old index (#9254).- A
LIMITquery could leave the database unable to close because cancelling its parallel scan interrupted a page read (#8944). - Windows backup archive nesting and path separators (#7586); the ext4
lost+founddirectory is no longer registered as a database (#8805). - Backups cover the configuration files in the snapshot (#6114); 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 (#8817, #6970).
CREATE INDEXon a populated type no longer truncates datetimes to milliseconds, andCREATE INDEX IF NOT EXISTSworks 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; unique prefix lookup and
IS NULLon unique indexes (#9253, #8806). - A unique
BINARYkey skipped the uniqueness check because array keys were compared shallowly (#7881).
SQL
- Parser re-render,
copy(),LIMITcounting, positionalLIMIT ? SKIP ?binding, comments inORDER BY/GROUP BY, hex literals, nesting guards and planner exceptions (#7800, #8434, #9122, #9154). - A scalar left operand of
CONTAINSANYsilently matched nothing, andexpand()of a native array yields one row per element (#8475, #7910). - The
UPDATEHalloween problem,GROUP BYprojection alias rebuilt per record,ORDER BYover a null scan, andFULL_TEXTnever answering exact lookups (#8814, #8260, #8664, #8442). SQLscripts replay blocks correctly andEXPLAINparity across sqlscript operation types (#8633, #7576).
openCypher
- Relationship uniqueness scoped to the clause,
MERGEbinds the path it merged, and an eager read/write barrier so a query never reads back what it just created (#7165, #7169, #7171). MERGE ... ON MATCH SETno longer loses a concurrent increment, nodes are written once forMERGE ... SETandCREATE ... SET, andLIMITafter a write no longer cuts the write short (#8538, #8735, #8826, #8827).- An empty
FOREACHchangedOPTIONAL MATCHresults,FOREACH/REMOVEscope checks, existence constraints checked at end of statement, andCALL (*)no longer inherits the outer clauses' variables (#8733, #8105, #9205). - Unidirectional
in()/both()with a view, queries on the incoming side of unidirectional edge types,DELETEof a node with several relationships, and code-point string handling (#8939, #8625). - Temporal lookups and round trips, signed zero, and a Cypher
DDLguard.
Graph engine and analytical views
- Every transaction that committed while a Graph Analytical View was building its CSR was lost from it permanently (#8378); a vertex reusing a deleted RID was hidden from Cypher counts (#8948).
- Relationship uniqueness, off-monitor rebuild and per-database
useWhen(#8394, #8403); one-hop aggregations (#8335). astar/dijkstraoption parsing, theDIAGONALheuristic and tie-breaking (#8767); parallel-edge shortest paths andpath.subgraphAll(#7982).
Vector and sparse vector
- The graph rebuild loop stops when two records share a vector id, and
CHECK DATABASE FIXrepairs it (#8946). - Aborted transactions no longer leak tombstones or uncommitted vectors into
LSM_VECTOR/LSM_SPARSE_VECTORindexes, 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
filterthat matches no records is 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;
BINARYquantization, zero and non-finite vectors, and a window-based MaxScore for SPLADE top-K (#9132, #9252, #9200). - The HNSW build cache is sized from the heap ceiling, so a served database matches an embedded one (#7146).
TimeSeries
- A
TIMESTAMPcolumn not declared first round-tripped through JSONL with its values in the wrong columns (#7899); aggregation push-down mixed row and schema column indexes between the mutable and sealed halves (#8140). - One answer per query whichever layer holds the data, sealed-store identity and repair, and an empty
TAGkey is refused instead of dropped silently (#7733, #8738, #8563). - PromQL conformance gaps and
ts.timeBucket()exposed asLocalDateTime(#8926, #7610).
Wire protocols
- Postgres:
SETis session-scoped, transactional and read back bySHOW;COMMIT/ROLLBACKinside an implicit transaction block were silently ignored; a trailing semicolon bypassed the transaction-block state machine;ROLLBACK TO SAVEPOINTis refused instead of accepted as a no-op; portals are transaction-scoped;$Nparameters on indexes; Arrow ADBC driver bootstrap (pg_type,regclass); and binary arrays (#8028, #8245, #8217, #7178). - MongoDB: lossless BSON types (Binary, regex, timestamp, MinKey, MaxKey, JavaScript were silently dropped), exact filter semantics, nested
$set, projection, unique_idand replace (#9062, #9137). - Redis: a newline batch was classified by its first command only, so a
GETfollowed by anHDELwas declared read-only;INCR/DECRare atomic on the query engine too;MULTI/EXECno longer double-applies; binary-safe values;INCRBYFLOAT(#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;RemoteServerdrop/createUser/dropUser go throughhttpCommand(#8525, #8062, #7796). - Gremlin: label loss, profile double execution, idempotent
drop, fractional index bounds, and the advertised Gremlin port (#8258, #7408, #9147).
Importer, GraphBatch and async
- Importer transaction ownership, RDF and CSV edge cases, row-error handling and post-import validation (#7943, #7948).
GraphBatchwrote 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);
insert_manyandGraphBatch.create_vertexroll back on every failure path (#7882).
Upgrade notes
- 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).
arcadedb.server.apiTokenRequireSecureTransport: check the first-run Docker flow onhttp://localhost:2480, where Studio's API-token mint is refused once it is on (#8765, #7804).- Stricter refusals instead of silent acceptance: an unparseable datetime, a map value the declared type cannot take,
Type.convertof an impossible value,ROLLBACK TO SAVEPOINTover the Postgres wire, anUPDATEwhose value was computed from a stale read, andTRUNCATE TYPEon a non-empty vertex or edge type (#8090, #9110, #8610, #8042). - Queries that returned a plan-dependent answer now return the equality-consistent one (null keys,
FLOAT/DECIMAL/DATETIME/BOOLEAN, signed zero). A result set that changes after the upgrade is the fix, not a regression. - Index default page size changed (#9250); existing indexes keep their size.
- Schema DDL gates:
ALTER TYPEedge-type settings andREBUILD TYPEneedUPDATE_SCHEMA(#8398); Gremlin scripting andio()are restricted (#8227). - 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).
LSM_SPARSE_VECTORrescoring oversamples by default (rescoreOversample2) and ranks by exact score (#8576).
Dependency updates
About 140 dependency bumps landed in this cycle, almost all through Dependabot. The notable ones:
- Server and engine: 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, jboss-threads 3.10.1, Swagger 2.2.55 and swagger-parser 2.1.48, Neo4j Java driver 6.3.0, Jedis 8.0.1, and gRPC protos 2.78.0.
- Build and test: maven-compiler 3.16.0, Surefire and Failsafe 3.6.0, maven-dependency-plugin 3.11.0, maven-install-plugin 3.2.0, native-maven-plugin 1.1.14, Mockito 5.24.0, Cucumber 8.0.3 and Maven 3.10.0.
- Studio and end-to-end tests: ApexCharts 7.6.1, marked 18.0.14, swagger-ui-dist 5.33.0, JSZip 3.10.2, plus the usual build-tool, Jest, undici and
@grpc/grpc-jsbumps. - CI: GitHub Actions, pre-commit hooks, Go modules and Docker base image bumps.
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, @wsalembi