github ArcadeData/arcadedb 26.10.2

5 hours ago

ArcadeDB 26.10.2

Overview

682 issues and pull requests closed under the 26.10.2 milestone - 485 issues and 197 pull requests - out of 355 pull requests merged and about 1,360 commits since 26.10.1. 377 of the closed issues are bug fixes, 20 of them rated critical.

The headline work is in six places:

  • Query control and speed - a JVM-wide query admission gate, list and terminate running queries over HTTP and every wire protocol, SQL and openCypher statements that differ only in their literals now share one parsed statement and one plan (a point lookup with the value in the text is 3 to 7 times faster), a parallel GROUP BY that evaluates each key once, and Customizable Contraction Hierarchies for shortest paths.
  • High availability (Raft) - schema and database-setting changes made outside a DDL statement now reach every node, vectors inserted on a follower no longer replace other vectors, an in-place Ratis restart no longer double-applies a commit, and a node removed from the Raft configuration is fenced. A follower's refusal of a write is a redirect on Bolt, MongoDB, Redis and gRPC, naming the leader.
  • Time series and Prometheus - retention and compaction no longer corrupt or resurrect data in the sealed store, writes that used to be stored silently wrong are refused or reported, and PromQL follows Prometheus on a dozen edge cases.
  • openCypher - temporal values (time(), localtime(), zoned datetime(), duration()) are stored natively, MERGE matches exactly what MATCH matches, GQL constructs the engine used to ignore are refused, and the path.* procedures answer as APOC does.
  • Security hardening - open wire-protocol and /ws sessions re-check the user on every request, SQL classifies a statement by everything nested inside it, lightweight edge types enforce create and delete grants, a type's EXTERNAL bucket follows the type's permissions, and Studio no longer runs HTML from an AI answer.
  • Silent wrong data - lost keys during a concurrent commit, a schema reload that issued the same RID twice, a schema change lost at close, LSM compaction resetting the null strategy, and WAL recovery re-issuing transaction ids.

Upgrading is recommended for every deployment, and in particular for every HA cluster and every time series workload. Behavior changes are collected in Upgrade notes.

Contents: New features · Highlights · Security · Major fixes and improvements · Upgrade notes · Dependency updates


New features

Server and operations

  • Query admission gate (#9518). A FIFO queue in front of every remote protocol (HTTP, Postgres, Bolt, Redis, MongoDB, gRPC, MCP): when the server is busy, requests wait and start in arrival order instead of all starting at once and exhausting the heap. On by default (arcadedb.queryMaxConcurrent, 2 x cores and at least 4, 0 disables it), bounded by arcadedb.queryQueueTimeout and arcadedb.queryQueueMaxSize, and heap-aware through arcadedb.queryAdmissionHeapWatermark. A refused request is retryable on every protocol (HTTP 503, Postgres 40001, Redis TRYAGAIN).
  • List and terminate running queries (#9680, #9689). list queries [database <db>] [tag <t>], terminate query <id>, terminate queries tag <t>, list transactions and terminate transaction <sessionId> over POST /api/v1/server, with an X-ArcadeDB-Query-Tag header to find your own statements and an acknowledgement that says whether the statement stopped. Bolt, gRPC (streaming loads included), MCP, the Postgres wire and /ws insert sessions register their work too, and every entry names its client (#9774, #9838, #9875, #9962).
  • Statement and plan cache by query shape (#8307). SQL and openCypher texts that differ only in their literals are parsed and planned once. Measured on a unique-index point lookup, Cypher drops from 42 to 6.1 microseconds per query and SQL from 21.5 to 6.6; a query that already uses parameters costs what it did.
  • Docker images run on Java 25 (eclipse-temurin:25-alpine), so compact object headers are finally enabled in the image (#9602). The native image now ships Raft HA, metrics and tracing (#9407), runs the e2e battery in CI (#9495), and uses scalar vector math instead of the uncompiled Vector API, which was 6 to 27 times slower (#10072).
  • Studio is served with a Content-Security-Policy that refuses inline script (#9681), and the saved-query and history sidebar, the Reference panel, the sample-database cards, the API endpoint list and the Profiler rows are keyboard accessible (#8740, #9375, #9508).
  • HA operations: an operator override to lift a quarantine or the stale-snapshot read floor on a sole voter (POST /api/v1/cluster/accept-diverged/{database}) (#9449, #9498); GET /api/v1/cluster exposes the follower's replication-path and unreachable-leader state and in-place restart counters, with matching alerts (#9399, #9429); an alert when every voter has quarantined the same database (#9553); the remaining Raft executors published as pool metrics (#8856); and a configurable DEFLATE level for snapshot ship (arcadedb.ha.snapshotCompressionLevel) (#9295).
  • Backups: SQL BACKUP DATABASE applies the retention policy (#9844), and a full backup archives a vector index's graph consistently so a restore reuses it (#9917).

Query languages and engine

  • Native openCypher temporals: four new property types, OFFSET_TIME, LOCAL_TIME, ZONED_DATETIME and DURATION, indexable by LSM_TREE and HASH; a String is never reinterpreted as a time again (#8572, #9462, #9636).
  • Customizable Contraction Hierarchies: a Graph Analytical View keeps a CCH per weight property (CREATE GRAPH ANALYTICAL VIEW ... CCH (distance)), so weighted point-to-point shortest paths no longer depend on the distance (cchShortestPath(), algo.cch.shortestPath), and the hierarchy keeps up with weight updates in milliseconds (#9437, #9915).
  • path.* procedures answer as apoc.path.*: APOC's filter syntax, nine uniqueness modes, endNodes, terminatorNodes, allowlistNodes, denylistNodes, optional, a lazy walk that a limit really stops, and a heap-charged breadth-first walk (#10025, #10048, #10049, #10017).
  • openCypher label expressions on variable-length relationships ([:!R*], [:%*], shortestPath((a)-[:!R*]-(b))) now run (#9117).
  • SQL: CREATE INDEX ... METADATA {"buildMode": "SORTED"} selects the sorted build (#9291); CREATE GRAPH ANALYTICAL VIEW v PROPERTIES () builds a view with no vertex properties (#9831); algo.labelpropagation takes a tieBreakProperty (#9285); CHECK DATABASE reports unreadable stripe directories (#9266); Gremlin hasLabel sees types created earlier in the same traversal (#9335).
  • PromQL: the bool comparison modifier (a > bool 1) (#9644).

Remote clients

  • Typed "already exists" exceptions: the Java remote clients raise RemoteAlreadyExistsException for a taken user, token or database name, and the server answers 409 for them over HTTP (#10073, #10074, #10075).
  • RemoteSchema.alterMaterializedView() is implemented (#8726), a streamed error line rebuilds its typed exception (#8874).

Highlights

High availability: schema, settings and recovery reach every node

  • Changes that were saved on the leader only now replicate. ALTER PROPERTY constraints and the Property setters, schema extensions, the ALTER DATABASE date formats and setZoneId, ALTER TYPE ... WITH lightweight / unique, the time series downsampling policy, ALTER TYPE ... NAME (type, bucket, index and edge files), ALTER DATABASE / set database setting, a JSONL import's triggers, views, continuous aggregates and functions, and db.migrateLegacyTemporals(). A constraint went unenforced on a node elected after a failover and date settings answered differently per node (#9747, #9762, #9763, #9764, #9712). During a rolling upgrade a rename or a setting change is refused with 409 until every node understands it.
  • Vectors inserted on a follower replaced other vectors. A replicated vector index page did not move the follower's id allocator, so the next insert reused an id the leader had allocated: countEntries() stayed flat and the superseded vectors dropped out of search (#9428). A follower's vector graph also picks up vectors replicated after its first search (#9857, fixed by @blaus23 in #9859), and a snapshot install or a full backup no longer reuses a vector graph without the manifest that vouches for it (#9758, #9888).
  • An in-place Ratis restart double-applied a commit, and several replicated commit paths never fired after-commit callbacks (which also lost Redis transactions that only changed in-memory values) (#8785, #8784).
  • No leader after a leader loss in a 3-node cluster. Once a stopped node was gone its host name could resolve to the node asking for it, so every Raft message meant for the dead node was delivered back to the sender and answered as if it were that node: the new leader received its own heartbeat and stepped down, and every election then counted its own vote twice. A node now refuses any Raft message addressed to another node and reconnects to a peer when it notices it has messaged itself (#10016, reported on a real cluster). A closed state machine refuses commits at once instead of waiting the quorum timeout (#9411), the Ratis close is bounded on stop() and on an in-place restart (#9561), and a log close can no longer deadlock with an in-flight cache eviction (#9556).
  • Two committed schema changes gave the same file id to different compacted index files, quarantining the database on every node (#9547). A restarted node ran out of heap catching up about 61,000 Raft entries on a 1 GB heap; the entry cache is now bound to the heap (#9549).
  • A node removed from the Raft configuration no longer accepts writes that stall for the quorum timeout, and refuses consistent reads, drops and installs (#9510, #9590, #9589).
  • The proposer of a refused schema entry is quarantined and resynced instead of staying diverged, and a bootstrap fingerprint entry applied during shutdown is left for replay (#9555, #9558, #9568). A sole voter no longer quarantines a database on an unexpected apply error (#9308).
  • A schema change no longer breaks time series writes on followers: every schema reload closed the files of each time series type that held data, so the next write failed and the follower resynced (#9886).
  • A follower's refusal of leader-only work is answered at once (Database.transaction() used to retry it through every txRetries attempt) and always names the leader, never the refusing node (#9995, #10066, #10060).
  • Cluster credential on every peer dial is built in one place (PeerRequest), so the token and forwarded user always travel together (#9999, #10068).

Lost updates and silent data loss

  • Index lookups lost a key while a commit published: a point lookup, Database.lookupByKey, pid IN :ids in SQL and in openCypher missed (11 to 23 percent of reads in the reproduction) or served another key's record when a transaction deleted and re-created a key (#9369, #9397).
  • A schema reload issued the same new page and RID to two transactions, because the reloaded bucket started with an empty page-reservation state (#9835).
  • A schema change pending at close was lost whenever the schema held an index: the save ran after the database reported itself closed, failed at SEVERE and left the type or index gone after a reopen (#10059).
  • LSM index compaction reset the null strategy to SKIP for ERROR and INDEX indexes, and saved it to schema.json (#9717).
  • WAL recovery set the next transaction id from the last replayed transaction, not the highest, so after a crash with two concurrent commits the database re-issued transaction ids (#9795).
  • include() / exclude() returned a dirty record stamped with the source RID, so saving a projection truncated that record and orphaned its edges (#9840). INSERT ... FROM SELECT *, !secret copied the excluded column (#9965).
  • A MongoDB insert made while the caller held a transaction committed its own and rolled the caller's back (#9977).
  • A refused DELETE_RECORD had already removed the record's index entries, EXTERNAL values and edges; the check now runs first (#9305). An edge iterator's remove() unlinked an edge the user may not delete (#9779).
  • A multi-page record could be read torn under REPEATABLE_READ after a concurrent rewrite (#9070), a flush suspension released on the wrong thread left every later backup, freeze and close timing out (#9832, #9927), and an async batch on a one-bucket type lost 1,000 records in 24 of 6,000 runs until its replay waited a jittered backoff (#9529, #9694).
  • The Python driver rolls back after an interrupt in begin(), and run_in_transaction retries a LockTimeoutException (193 failures escaped in the reproduction) (#9321, #9629, #9970).

Time series and Prometheus: the sealed store stops losing and resurrecting data

  • Retention appended its rewritten blocks after whatever an abandoned rewrite left in .ts.sealed.tmp, so blocks it had just dropped came back on the next open and shipped to every follower (#9770). COMPACT TIMESERIES TYPE racing a retention pass corrupted the live store's in-memory directory and silently undid the retention at the next open (#9771). Acknowledged rows were missing after a kill -9 during a concurrent compaction (#9452).
  • PRECISION SECOND lost every sample at the first retention pass, and a NANOSECOND type never expired and answered nothing in PromQL, because no timestamp code read the precision (#9800).
  • Sealed store I/O: a thread interrupt closed the shared channel for every reader until the next compaction (#9621), and a short read dropped every later block of the directory (#9640).
  • Writes are strict. An empty tag value, an undeclared tag or field key, a quoted datetime in a typed column and a value the column cannot store are refused or reported as a partial write instead of stored wrong (#8647, #8646, #9809, #9643). arcadedb.timeSeriesUndeclaredKeys restores the lenient behavior for schema-less producers.
  • Reads: an unbounded SELECT FROM <timeseries type> and the gRPC stream no longer load the whole range (a 50M-point type ran out of heap) (#9420), scanRangeDescending no longer stops early on overlapping blocks (#9982), ts.rank ranks by value and ts.movingAvg keeps one slot per row (#9956, #9958), an ungrouped min()/max() of a LONG past 2^53 is exact (#9710), and count(*) is answered from block statistics (#9612).
  • Prometheus: remote read keeps non-equality matchers and no longer merges series whose labels contain | (#9803, #9623), /series applies match[] (#9386), remote write converts labels into typed tag columns and answers 415 to a 2.0 body (#9972, #9624), and PromQL refuses what Prometheus refuses (#9723, #9743, #9788, #9883, #9922, #9924).

Equality and typing agree everywhere

  • MERGE matches exactly what MATCH matches: a stored FLOAT, a temporal against a STRING property, and -0.0 / 0.0 no longer create duplicates or raise a duplicated-key error (#9693, #9616, #9617, #9618).
  • A pattern finds what it stored in a declared scalar property. MERGE (n:S {v: 1}) stores '1' in a STRING property and used to create a duplicate every time; the same holds for text written into INTEGER, LONG, FLOAT, DOUBLE, BOOLEAN, DATE and DATETIME (#9695, #9701, #9794, #9935).
  • A COLLATE ci index no longer changes the answer of = / IN (#9403), k = [1, 2] is no longer read as IN (#9370), SQL sum() of an empty group is NULL (#9351), and an equality on a ZONED_DATETIME property agrees with and without an index (#9648).
  • Weighted shortest-path finders share one edge-weight rule: a missing weight is 1, a negative, NaN or infinite weight is not walked, in dijkstra(), astar(), duanSSSP() and every algo.* finder (#9443).
  • A statement is classified by everything nested inside it, so a write hidden in a LET, a projection or a sub-query is no longer run through the read-only paths (#9628).

Faster queries

  • Statement and plan sharing by query shape (#8307, #9818, #9714) and admission control (#9518) - see New features.
  • GROUP BY: each key is aggregated once in a parallel scan, aggregates are evaluated off bound columns, and the in-heap budget no longer scales with the number of cores (a query that ran on 1 or 2 cores failed on 4 or more) (#9496, #9402).
  • Variable-length hops that need only distinct end vertices walk a visited set instead of every path: MATCH (a)-[:LINK*1..D]->(b) RETURN DISTINCT b over 200,000 vertices and 1.8 million edges went from not finishing at depth 9 in 120 s to about half a second at depth 12 (#10062, contributed by @tae898).
  • LSM index scans no longer go quadratic after a hot key (#9461), bucket scan read-ahead is bounded by bytes (#9404, #9401), a commit skips the hole proof for pages written only by hole-free writes (#9483), and a scan stops paying a schema lookup per record (#9997, #10013).
  • Count push-downs through a Graph Analytical View (LSQB Q2, Q3, Q7, Q8, Q9) take property predicates, Cartesian products and pass-through WITH, and are exact on parallel, reciprocal, self-loop and light edges (#9298, #9350, #9354, #9484, #9485, #9486, #9595, #9596, #9597, #9608, #9282).
  • An index lookup that overlaps a commit no longer re-reads the whole key up to 9 times (#9902).
  • Bolt coalesces RECORD messages into a few large socket writes (#9353) and no longer accumulates a whole PULL on the heap (#9372); a vector index rebuild is no longer deferred forever on a large index (#7260); the sparse top-K splits from 50,000 postings (#9482); and a time series grouped aggregation keeps its bucket cursor across rows (#10003).

Security

Authorization, redaction and input-bounds fixes found by audit and by reporters.

  • Long-lived wire connections re-check the user (#9279). PostgreSQL, Bolt, MongoDB and Redis sessions re-resolve their user against the live security state on every request, as HTTP does: a deleted user, a changed password or a revoked grant now applies to connections that are already open. /ws insert and SUBSCRIBE frames do the same (#9311, #9312, #9340, #9424).
  • SQL classification looks inside nested statements (#9628). LET $x = (INSERT ...), SELECT FROM (CREATE TYPE ...) and similar were reported as reads and ran through GET/POST /api/v1/query and read-only MCP identities. PROFILE follows the statement it wraps (#9313), and GraphQL type definitions need UPDATE_SCHEMA (#9280).
  • Lightweight edge types enforce createRecord and deleteRecord (#9619, #9779); a vertex type's super-node stripe buckets and a type's paired EXTERNAL bucket follow the type's permissions and lock (#9780, #9637).
  • DELETE_RECORD is checked first, before any index or graph cleanup (#9305).
  • LOAD CSV settings are security-sensitive (#9371): remote URLs, local files, the import directory and the blocked IP ranges need updateSecurity to change through ALTER DATABASE.
  • Support diagnostics and logs redact arcadedb.server.defaultDatabases, including the JVM arguments uploaded to the portal and the two log lines that spelled the setting with a space (#9625, #9773). Rotate any password configured this way on a server that already uploaded diagnostics. Studio's Support page no longer disables a column's auto-mask for good after one unmask (#9797).
  • Studio AI answers can no longer run HTML (#9626): the answer and the profiler analysis use an escape-first markdown renderer, marked is no longer shipped, and Studio is served with a Content-Security-Policy (#9681). The AI Assistant caps every read of the portal and gateway answers (#9627).
  • gRPC: arcadedb.grpc.tls.enabled and the other arcadedb.grpc.* settings are read from server-configuration.json and the environment (asking for TLS in the file used to leave a plaintext endpoint listening), and arcadedb.grpc.host is honored (#9316, #9318).
  • Production mode conceals error text on the Gremlin Server for every channelizer, on the /ws insert session, Bolt and Redis (#9317, #9639, #9749, #8749). The Python server's loopback-by-default hardening now covers the Postgres, Redis and Bolt listeners (#9632).
  • HA: security entries submitted from a node removed from the Raft configuration are refused (#9589).

Major fixes and improvements

Storage, WAL and integrity

  • A schema reload keeps the page reservations, an interrupted flush resume no longer abandons batches, dropping a database with suspended flushes releases its WAL acknowledgements, and closing the last database inside suspendFlushAndExecute no longer fails with a NullPointerException (#9835, #9832, #9881, #9927).
  • A subtype that inherits an EXTERNAL property stores its values in its own _ext bucket (#9683). A compacted LSM bucket index keeps its name across a restart and an HA schema reload (#9213, #9434).
  • CHECK DATABASE reads each record of a type with MANDATORY / NOTNULL properties once (#7981), and a failed read of a closed component file throws IllegalArgumentException instead of NullPointerException (#9306).
  • Interrupted snapshot reads reopen the channel instead of invalidating open windows or aborting a backup (#9306); a full backup skips index-compaction temporaries (#8848); orphan compaction temporaries are swept at open (#8849).
  • An impossible REGEXP, and a MANDATORY or default-valued property on a LIGHTWEIGHT edge type, are refused at declaration time (#9853, #9846, #9990, #9975).

SQL and indexes

  • LIKE '%' matches an empty string (#10005), values() on a sub-query row lists exactly the values of keys() (#9843), nested projection prefix wildcards @this:{s*} work (#9841), and a bare ORDER BY @rid on a MATCH that returns columns is refused instead of silently ignored (#9911).
  • A MATCH node with rid: honors its type: and bucket:, and a node filtered by SEARCH_INDEX() is streamed instead of loaded whole (#9912, #9908). SEARCH_INDEX pushes @rid ranges and merges score-ordered OR branches before loading records (#9895, #9909, #10022).
  • UPDATE ... UPSERT requires an equality on every property of a UNIQUE index (#9339, #9342); an async SELECT with parameters binds them, not the executor's configuration (#9941); concurrent CREATE INDEX IF NOT EXISTS no longer fails with a NullPointerException (#9741); Schema.copyType no longer rolls back the caller's transaction (#9851).
  • The BM25 corpus counters of a FULL_TEXT index move when the transaction commits on every node and no longer race writers; the repair is kept and the counters are inspectable (#10018, #10019, #9337). SEARCH_FIELDS no longer matches terms in fields it was not given (#9910).
  • vector.sum, vector.avg, vector.min and vector.max aggregate when written with the dotted name, in SQL and openCypher (#9768); sum() and avg() work over BYTE values, and agg.* functions accept Float[] and Short[] properties (#9281, #9777).
  • A COLLATE ci index is skipped by native select() range cursors and Gremlin range has(), and select() with a LIMIT returns the rows the unindexed query does (#9302, #9613).

openCypher

  • GQL constructs the parser accepted and the engine ignored are refused (ANY SHORTEST, REPEATABLE ELEMENTS, IS DIRECTED, a nested quantified group), a path variable over a quantified group binds one PATH, LET, IS NORMALIZED and the conditional query WHEN ... THEN run, and RETURN ... ORDER BY/SKIP/LIMIT is applied after the sort as one page (#9940).
  • A variable-length MATCH honors a previously bound relationship list (#9828, #9854, contributed by @mvanhorn), WHERE (booleanVariable) is no longer parsed as a pattern (#9542), and a variable cannot be both a node and a relationship in a pattern predicate (#9292).
  • DROP CONSTRAINT <name> drops what the constraint created, SHOW CONSTRAINTS / SHOW INDEXES list what exists, and IS :: <type> keeps the property's other constraints and refuses attributes the new type cannot use (#9646, #9706, #9304, #9645).
  • Write conflicts are retried by Database.transaction(..., retries) like SQL's (#9487); temporal values compare under every dateImplementation and render with Locale.ROOT (#9647, #9649); durations are exact, ISO text is read as Neo4j reads it, and arithmetic raises on overflow (#9338, #10055).
  • algo.bridges treats edges as undirected and algo.kShortestPaths takes a direction (#9766, #9914), algo.* keeps tolerance and direction on the accelerated path (#9829), and the count push-downs of a Graph Analytical View stay exact (see above).

Graph engine and analytical views

  • A view over only some of the vertex types no longer accelerates count push-downs, traversal, shortest paths or algo.* (#9301); a lightweight edge created or removed after the build reaches the view (#9572); a vertex whose edge list has an empty segment between non-empty ones no longer loses edges in the view or in CHECK DATABASE (#9715); parallel lightweight edges are distinct relationships in =, DISTINCT and DELETE (#10008).
  • A view restored from its persisted CSR after a reopen gives the speed-up it reports (#9587), a fused 2-hop count(*) no longer holds every path on the heap (#9738), and a plain OPTIONAL MATCH ... WHERE h IS NULL plans as an anti-join (#9598).
  • GraphBatch.withLightEdges(true) needs a LIGHTWEIGHT edge type (#9378).

Vector and sparse vector

  • vectorNeighbors(), vector.neighbors, db.index.vector.queryNodes, vector.sparseNeighbors and vector.recommend (grouped or not) top up candidates that a concurrent commit deleted instead of answering short (#9368, #9858, #9697, #10002, #9692, #9396); the db.index.vector.queryNodes report (#9858) is from @ofir-blaus.
  • BINARY quantization reranks past deleted candidates and in grouped searches (#9633), vector.approxDistance INT8 keeps its per-vector scale (#9776), a sparse-vector update answers on its new dimensions (#9343), and FP16 and INT8 sparse searches no longer miss a document that belongs in the top-K (#9834).
  • A location load that stops partway no longer lets new vectors reuse ids held by other records (#9892).

Wire protocols

  • Postgres: SHOW, DISCARD, DEALLOCATE, CLOSE and search_path are handled; START TRANSACTION, ABORT, BEGIN with isolation modes and AND NO CHAIN are accepted; statement_timeout is enforced; the extended protocol forwards a follower's DDL and writes to the leader; and a write inside an explicit transaction on a follower stays inside it (#9326, #9327, #9328, #9329, #9631, #9954, #10040).
  • Bolt: an index compaction left half-replicated no longer breaks every lookup with ArrayIndexOutOfBoundsException on a follower (#10046, reported by @rlaveycal).
  • MongoDB: a MAP property with a non-String key no longer fails the whole find() (#9973), and a follower's refusal is NotWritablePrimary (#9845).
  • Redis: a MULTI/EXEC block retried by the HTTP auto-commit wrapper no longer applies INCR twice, HDEL reports the records it removed and a multi-key HDEL / HSET is one unit of work (#9322, #9837).
  • gRPC: ExecuteCommand honors grpcQueryMaxResultRows (#9861), arcadedb.grpc.compression.enabled is applied (#9782), a lost answer on a write is an unknown outcome, not a retryable error (#8822), and a ZONED_DATETIME, OFFSET_TIME, LOCAL_TIME and DURATION keep their type on the wire (#9969, #10034).
  • GraphQL: selections that share a response key are merged, and a sub-selection on a LINK property is no longer dropped (#8744, #9467).
  • Gremlin: elementMap() rows have one JSON shape, a nested map is named like the row (@rid, @type), the plugin fails its start on a port bind error, and the index step is guarded against a concurrent CREATE INDEX (#9576, #9781, #9319, #9651).
  • HTTP: a statement longer than arcadedb.network.socketTimeout is no longer killed with no response (#9953), the write watchdog survives an XNIO selector wakeup race (#9439), and the streamed query error line carries the classified label and retryAfter (#8899).
  • Remote client: SELECT @rid FROM Person and Gremlin elementMap() keep their metadata columns (#9804), and a malformed DuplicatedKeyException no longer crashes the driver (#9473).

Importer, GraphBatch and async

  • A 200M-vertex / 2B-edge GraphImporter load no longer stalls in pass 1 (#9575); an async executor batch waits a jittered backoff before replay (#9529, #9694).
  • The native image resolves reflection-free SQL AST copies, Lucene and the missing function metadata, which fixed UPDATE/DELETE over HTTP answering 500 with NoSuchMethodException (#9495, reported by @zek0), and registers the gRPC messages and Netty's ticker (#10070, #9564).

Python bindings and tooling

  • The Python bindings are updated for 26.10.1: results that release the engine, exact Decimal and bytes parameters, and tripwires for open engine bugs (#9294, contributed by @tae898). An interrupt during begin() pops only the level it pushed (#9970).

Upgrade notes

Each item links to the issue with the full migration detail; the complete write-up is in docs/release-26.10.2.md.

  • Query admission gate is on by default (#9518). Past arcadedb.queryQueueTimeout or arcadedb.queryQueueMaxSize a request is refused as retryable (HTTP 503, Postgres 40001, Redis TRYAGAIN). Set arcadedb.queryMaxConcurrent=0 to disable it.
  • Docker images run on Java 25, and Studio is served with a Content-Security-Policy that refuses inline script (#9602, #9681).
  • HA: the snapshot ship compresses at DEFLATE level 1 by default, so a resync over a WAN sends more bytes (arcadedb.ha.snapshotCompressionLevel=6 restores the previous level) (#9295). A follower restarted in place is no longer reformatted automatically (#8901). During a rolling upgrade a type rename, ALTER DATABASE and set database setting are refused with 409 until every node runs this build, and a rename cannot be downgraded past this release on its retained log (#9762, #9763). An index that already lost vectors to #9428 stays short and has to be rebuilt.
  • A follower's refusal is a redirect: Bolt Neo.ClientError.Cluster.NotALeader, MongoDB 10107 NotWritablePrimary, Redis READONLY, /ws and gRPC NOT_LEADER with the leader's address. A client that matched the old retryable codes must handle the new ones (#9845, #9868, #9930).
  • Queries that answered differently now answer consistently:
    • a plain = / IN on a COLLATE ci index is case sensitive (#9403);
    • k = <list> no longer expands (#9370), SQL sum() of an empty or all-NULL group is NULL (#9351), and UPSERT needs a UNIQUE index (#9339);
    • dijkstra(), astar() and the algo.* finders count a missing weight as 1 and skip negative weights (#9443);
    • MERGE and MATCH agree, and a declared scalar property matches the text it stored (#9693, #9695, #9794);
    • path.expandconfig and path.expand return every trail by default, path.subgraphall returns every relationship between its nodes (#10025, #10048, #10049);
    • include() and exclude() return a map (#9840), values() on a Result follows keys() (#9843), a bare ORDER BY @rid on a MATCH with columns is refused (#9911), and a path variable over a quantified group binds one PATH (#9940).
  • openCypher temporals are stored natively. A database that holds Cypher temporals in undeclared properties reads them back as Strings until the property is declared with the new type and the values are copied through a parked property (see the migration recipe in docs/release-26.10.2.md) (#8572, #9636).
  • Stricter refusals instead of silent acceptance: time series writes with an empty tag value or an undeclared key (arcadedb.timeSeriesUndeclaredKeys=ignore restores the old behavior) (#8646, #8647), an ISO duration with no component or a misplaced fraction (#10055), an impossible REGEXP (#9853), a MANDATORY or default-valued property on a LIGHTWEIGHT edge type (#9846), a statement that hides a write inside a read (#9628), GraphBatch.withLightEdges(true) into a type that is not LIGHTWEIGHT (#9378), and the GQL constructs listed above (#9940).
  • PromQL refuses what Prometheus refuses: a comparison between two scalars without bool, a set operator with a scalar, a range vector or a string as an operand, a scalar argument to abs, ceil, floor and round, and a range query that returns a range vector or a string (#9723, #9743, #9788, #9883, #9922, #9924).
  • Existing names answer 409: creating a user, a token or a database under a taken name no longer answers 403 or 400 (#10073, #10074, #10075).
  • Vector graphs: a snapshot install and a full backup now ship the .vecgraph empty, so the first search after a restore builds and verifies its own graph; run REBUILD INDEX on the vector indexes of archives taken before this release (#9758, #9888).
  • Subtype EXTERNAL values written before the upgrade stay inline until REBUILD TYPE <Subtype> moves them (#9683).
  • Gremlin Server in production mode refuses to start with a channelizer that cannot hide error details (#9639); BACKUP DATABASE now prunes the archive directory by the retention policy (#9844); arcadedb.grpc.compression.enabled=false is honored (#9782).
  • Embedded API: SQLFunctionHeuristicPathFinderAbstract.MIN is gone (use com.arcadedb.graph.EdgeWeight), Schema.alterMaterializedView() validates its arguments, and schema setters called directly on an HA follower throw the "schema changes need the leader" error (#9443, #8726, #9747).

Dependency updates

About 30 dependency bumps landed in this cycle, almost all through Dependabot. The notable ones:

  • Server and engine: Netty 4.2.19, Undertow 2.4.4, Lucene 10.5.2, Jedis 8.0.2, JLine 4.4.7, snappy-java 1.1.10.11 and the PostgreSQL JDBC driver 42.7.14. The Docker base image moves to Eclipse Temurin 25.
  • Studio: ApexCharts 7.8.0, swagger-ui-dist 5.33.1 and datatables.net 3.1.3; marked is no longer shipped (#9626).
  • Build and test: Cucumber 8.0.4, Playwright 1.64, black 26.10.0 and the Go module bumps of the e2e suite.
  • CI: GitHub Actions and pre-commit hook bumps.

To everyone who reported, reproduced, reviewed, tested and fixed - in particular @ruispereira, @tae898, @manus-pi, @ofir-blaus, @blaus23, @mvanhorn, @willemijn-zeno, @shulei5831sl, @YaeSakuraQ, @Das-Rabindra, @HarinathMaddela, @saeedtabrizi, @zpaulo80, @zek0, @YGY-001, @ruslan-butyk-fntext, @rlaveycal, @preshewar, @philiptran-tech

Don't miss a new arcadedb release

NewReleases is sending notifications on new releases.