<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://arcadedb.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://arcadedb.com/" rel="alternate" type="text/html" /><updated>2026-08-01T04:09:37+00:00</updated><id>https://arcadedb.com/blog/feed.xml</id><title type="html">ArcadeDB</title><subtitle>The Next Generation Multi-Model Database</subtitle><entry><title type="html">Building Memory for LLM Agents with ArcadeDB</title><link href="https://arcadedb.com/blog/building-memory-for-llm-agents-with-arcadedb/" rel="alternate" type="text/html" title="Building Memory for LLM Agents with ArcadeDB" /><published>2026-07-30T00:00:00+00:00</published><updated>2026-07-30T00:00:00+00:00</updated><id>https://arcadedb.com/blog/building-memory-for-llm-agents-with-arcadedb</id><content type="html" xml:base="https://arcadedb.com/blog/building-memory-for-llm-agents-with-arcadedb/"><![CDATA[<p><strong>An agent that cannot remember is a chatbot with extra steps.</strong> This post shows how to build persistent memory for an LLM agent on a knowledge graph: storing episodes and the entities they mention, keeping facts current without losing their history, and recalling context with vector similarity and graph traversal in one query.</p>

<p>Everything here runs in-process with <code class="language-plaintext highlighter-rouge">pip install arcadedb-embedded</code>. No server, no separate vector database.</p>

<hr />

<h2 id="what-agent-memory-actually-is">What Agent Memory Actually Is</h2>

<p>“Memory” gets used loosely. In practice an agent needs three different things, and only two of them belong in a database.</p>

<p><strong>Working memory</strong> is the context window: the current conversation, tool outputs, and scratchpad. It lives in the prompt and disappears when the request ends.</p>

<p><strong>Episodic memory</strong> is what happened. Turn by turn, session by session: the user asked X, the agent called tool Y, the result was Z. It is append-only and inherently temporal.</p>

<p><strong>Semantic memory</strong> is what the agent has learned. Durable facts about entities: this user prefers Postgres, this project depends on that service, this customer is on the enterprise plan. Facts change, and the changes matter.</p>

<p>The common mistake is to implement all three as a single vector index over conversation transcripts. That works for the first demo and degrades quickly.</p>

<h2 id="why-a-vector-store-alone-is-not-enough">Why a Vector Store Alone Is Not Enough</h2>

<p>Embedding every turn and retrieving the top-k nearest is a reasonable baseline. It fails in four specific ways, and all four are relationship problems.</p>

<p><strong>Superseded facts.</strong> The user said they work at Acme in March and at Globex in June. Both turns are in the index, both are semantically similar to “where does the user work”, and the retriever has no notion that one replaced the other. The agent confidently returns the wrong employer. A vector index has no concept of validity.</p>

<p><strong>Entity identity.</strong> “Ana”, “Ana Ruiz”, and “a.ruiz@example.com” are the same person. Cosine similarity does not know that. Without entity resolution, memory fragments across aliases and recall silently degrades as the number of sessions grows.</p>

<p><strong>Structural questions.</strong> “What has this user asked about the billing service?” is a filter over a relationship, not a similarity search. A vector index can only approximate it, and approximation is exactly the wrong tool when the answer is deterministic.</p>

<p><strong>Multi-hop recall.</strong> “Which of the user’s projects depends on the library that just broke?” requires traversing project to dependency to incident. No single stored turn contains that chain.</p>

<p>A knowledge graph answers all four directly, because it stores the relationships instead of hoping similarity implies them. What it is not good at on its own is fuzzy recall, which is why the useful design keeps both, in one engine.</p>

<h2 id="the-schema">The Schema</h2>

<p>Four vertex types and three edge types are enough to start.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">arcadedb_embedded</span> <span class="k">as</span> <span class="n">arcadedb</span>

<span class="n">DIM</span> <span class="o">=</span> <span class="mi">1536</span>  <span class="c1"># match your embedding model
</span>
<span class="n">db_ctx</span> <span class="o">=</span> <span class="n">arcadedb</span><span class="p">.</span><span class="nf">create_database</span><span class="p">(</span><span class="sh">"</span><span class="s">./agent-memory</span><span class="sh">"</span><span class="p">)</span>

<span class="k">with</span> <span class="n">db_ctx</span> <span class="k">as</span> <span class="n">db</span><span class="p">:</span>
    <span class="k">with</span> <span class="n">db</span><span class="p">.</span><span class="nf">transaction</span><span class="p">():</span>
        <span class="c1"># An episode is one thing that happened
</span>        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE VERTEX TYPE Episode</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Episode.content STRING</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Episode.session STRING</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Episode.ts DATETIME</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Episode.embedding LIST OF FLOAT</span><span class="sh">"</span><span class="p">)</span>

        <span class="c1"># Entities the agent has learned about
</span>        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE VERTEX TYPE Entity</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Entity.name STRING</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Entity.kind STRING</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE INDEX ON Entity (name) UNIQUE</span><span class="sh">"</span><span class="p">)</span>

        <span class="c1"># A durable fact, valid over an interval
</span>        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE VERTEX TYPE Fact</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Fact.predicate STRING</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Fact.value STRING</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Fact.valid_from DATETIME</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE PROPERTY Fact.valid_to DATETIME</span><span class="sh">"</span><span class="p">)</span>

        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE EDGE TYPE MENTIONS</span><span class="sh">"</span><span class="p">)</span>   <span class="c1"># Episode -&gt; Entity
</span>        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE EDGE TYPE ASSERTS</span><span class="sh">"</span><span class="p">)</span>    <span class="c1"># Episode -&gt; Fact
</span>        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">CREATE EDGE TYPE ABOUT</span><span class="sh">"</span><span class="p">)</span>      <span class="c1"># Fact    -&gt; Entity
</span>
        <span class="c1"># HNSW index for semantic recall over episodes
</span>        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
            <span class="sa">f</span><span class="sh">"</span><span class="s">CREATE INDEX ON Episode (embedding) LSM_VECTOR </span><span class="sh">"</span>
            <span class="sa">f</span><span class="sh">"</span><span class="s">METADATA {{ dimensions: </span><span class="si">{</span><span class="n">DIM</span><span class="si">}</span><span class="s">, similarity: </span><span class="sh">'</span><span class="s">COSINE</span><span class="sh">'</span><span class="s"> }}</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>The separation matters: an <code class="language-plaintext highlighter-rouge">Episode</code> is immutable and says what was observed, a <code class="language-plaintext highlighter-rouge">Fact</code> is the interpretation, and the <code class="language-plaintext highlighter-rouge">ASSERTS</code> edge records which episode produced it. When the agent later gets something wrong, you can trace the belief back to the turn that caused it.</p>

<h2 id="writing-a-turn-to-memory">Writing a Turn to Memory</h2>

<p>Each turn does three things: store the episode with its embedding, resolve the entities it mentions, and assert or update any facts.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timezone</span>

<span class="k">def</span> <span class="nf">remember</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">entities</span><span class="p">,</span> <span class="n">facts</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Persist one turn: episode, entities, and any facts it asserts.</span><span class="sh">"""</span>
    <span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="n">timezone</span><span class="p">.</span><span class="n">utc</span><span class="p">).</span><span class="nf">isoformat</span><span class="p">()</span>

    <span class="k">with</span> <span class="n">db</span><span class="p">.</span><span class="nf">transaction</span><span class="p">():</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">CREATE VERTEX Episode SET content = ?, session = ?, </span><span class="sh">"</span>
            <span class="sh">"</span><span class="s">ts = ?, embedding = ?</span><span class="sh">"</span><span class="p">,</span>
            <span class="n">text</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">now</span><span class="p">,</span> <span class="nf">embed</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>

        <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">kind</span> <span class="ow">in</span> <span class="n">entities</span><span class="p">:</span>
            <span class="c1"># UPSERT gives one vertex per distinct entity name
</span>            <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">UPDATE Entity SET name = ?, kind = ? UPSERT WHERE name = ?</span><span class="sh">"</span><span class="p">,</span>
                <span class="n">name</span><span class="p">,</span> <span class="n">kind</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
            <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">CREATE EDGE MENTIONS </span><span class="sh">"</span>
                <span class="sh">"</span><span class="s">FROM (SELECT FROM Episode WHERE content = ? AND session = ?) </span><span class="sh">"</span>
                <span class="sh">"</span><span class="s">TO (SELECT FROM Entity WHERE name = ?)</span><span class="sh">"</span><span class="p">,</span>
                <span class="n">text</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>

        <span class="k">for</span> <span class="n">subject</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">facts</span><span class="p">:</span>
            <span class="nf">assert_fact</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">subject</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">now</span><span class="p">,</span> <span class="n">source</span><span class="o">=</span><span class="n">text</span><span class="p">,</span>
                        <span class="n">session</span><span class="o">=</span><span class="n">session</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="keeping-facts-current-without-losing-history">Keeping Facts Current Without Losing History</h2>

<p>This is the part a vector store cannot do at all. When a new value arrives for a predicate that already has one, do not overwrite it: close the old fact and open a new one.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">assert_fact</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">subject</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">now</span><span class="p">,</span> <span class="n">source</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Close any currently-valid fact for this predicate, then open a new one.</span><span class="sh">"""</span>
    <span class="c1"># 1. Invalidate the previous value, if any
</span>    <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">UPDATE Fact SET valid_to = ? </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">WHERE predicate = ? AND valid_to IS NULL </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">AND @rid IN (SELECT in(</span><span class="sh">'</span><span class="s">ABOUT</span><span class="sh">'</span><span class="s">).@rid FROM Entity WHERE name = ?)</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">now</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">subject</span><span class="p">)</span>

    <span class="c1"># 2. Record the new value
</span>    <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">CREATE VERTEX Fact SET predicate = ?, value = ?, valid_from = ?</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">predicate</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
    <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">CREATE EDGE ABOUT </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">FROM (SELECT FROM Fact WHERE predicate = ? AND value = ? </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">      AND valid_to IS NULL) </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">TO (SELECT FROM Entity WHERE name = ?)</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">predicate</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">subject</span><span class="p">)</span>
    <span class="n">db</span><span class="p">.</span><span class="nf">command</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">CREATE EDGE ASSERTS </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">FROM (SELECT FROM Episode WHERE content = ? AND session = ?) </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">TO (SELECT FROM Fact WHERE predicate = ? AND value = ? </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">    AND valid_to IS NULL)</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">source</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</code></pre></div></div>

<!-- UNVERIFIED: the correlated subquery in step 1 ("@rid IN (SELECT in('ABOUT').@rid ...)") uses documented SQL primitives but this exact composition has not been executed. Verify before publishing. -->

<p>Now “where does the user work?” has one right answer, and “where did the user work in April?” is still answerable. Nothing was destroyed.</p>

<h2 id="recall-similarity-then-structure">Recall: Similarity, Then Structure</h2>

<p>Retrieval runs in two layers. Semantic recall finds episodes that resemble the question; structural recall pulls the facts that are currently true.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">recall</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">question</span><span class="p">,</span> <span class="n">k</span><span class="o">=</span><span class="mi">5</span><span class="p">):</span>
    <span class="n">q</span> <span class="o">=</span> <span class="nf">embed</span><span class="p">(</span><span class="n">question</span><span class="p">)</span>

    <span class="c1"># 1. Episodes that resemble the question, plus what they mention
</span>    <span class="n">episodes</span> <span class="o">=</span> <span class="n">db</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">SELECT content, ts, distance, out(</span><span class="sh">'</span><span class="s">MENTIONS</span><span class="sh">'</span><span class="s">).name AS entities </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">FROM ( SELECT expand(vector.neighbors(</span><span class="sh">'</span><span class="s">Episode[embedding]</span><span class="sh">'</span><span class="s">, ?, ?)) )</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">q</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>

    <span class="c1"># 2. Facts that are currently true about those entities
</span>    <span class="n">facts</span> <span class="o">=</span> <span class="n">db</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="sh">"</span><span class="s">sql</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">SELECT predicate, value, out(</span><span class="sh">'</span><span class="s">ABOUT</span><span class="sh">'</span><span class="s">).name AS subject </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">FROM Fact WHERE valid_to IS NULL </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">AND out(</span><span class="sh">'</span><span class="s">ABOUT</span><span class="sh">'</span><span class="s">).name IN ?</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">[</span><span class="n">e</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">episodes</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">entities</span><span class="sh">"</span><span class="p">)</span> <span class="ow">or</span> <span class="p">[])])</span>

    <span class="k">return</span> <span class="n">episodes</span><span class="p">,</span> <span class="n">facts</span>
</code></pre></div></div>

<!-- UNVERIFIED: composing vector.neighbors() with an out('MENTIONS') traversal in the outer SELECT uses documented primitives but this exact composition is not shown in the docs and has not been executed. Verify before publishing. -->

<p>The second query is the one that makes the agent reliable. It returns only facts whose <code class="language-plaintext highlighter-rouge">valid_to</code> is null, so superseded beliefs cannot leak back into the prompt no matter how semantically similar the old episode was.</p>

<h2 id="wiring-it-into-a-framework">Wiring It Into a Framework</h2>

<p>If you already drive your agent through a framework, you do not need to hand-roll the retrieval layer.</p>

<p><strong>LangChain.</strong> The official <a href="https://github.com/ArcadeData/langchain-arcadedb"><code class="language-plaintext highlighter-rouge">langchain-arcadedb</code></a> package provides <code class="language-plaintext highlighter-rouge">ArcadeDBGraph</code>, which implements LangChain’s <code class="language-plaintext highlighter-rouge">GraphStore</code> protocol and connects over ArcadeDB’s Bolt protocol.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">langchain_arcadedb</span> <span class="kn">import</span> <span class="n">ArcadeDBGraph</span>

<span class="n">graph</span> <span class="o">=</span> <span class="nc">ArcadeDBGraph</span><span class="p">(</span>
    <span class="n">url</span><span class="o">=</span><span class="sh">"</span><span class="s">bolt://localhost:7687</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">username</span><span class="o">=</span><span class="sh">"</span><span class="s">root</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">password</span><span class="o">=</span><span class="sh">"</span><span class="s">playwithdata</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">database</span><span class="o">=</span><span class="sh">"</span><span class="s">agent-memory</span><span class="sh">"</span><span class="p">,</span>
<span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">graph</span><span class="p">.</span><span class="n">get_schema</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>LlamaIndex.</strong> <a href="https://pypi.org/project/llama-index-graph-stores-arcadedb/"><code class="language-plaintext highlighter-rouge">llama-index-graph-stores-arcadedb</code></a> implements the LlamaIndex <code class="language-plaintext highlighter-rouge">PropertyGraphStore</code> interface.</p>

<p><strong>MCP.</strong> ArcadeDB ships an MCP server, so an LLM can query the memory graph directly as a tool. See <a href="/blog/arcadedb-mcp-server-connect-your-llm-to-your-database/">connecting your LLM to your database with MCP</a>.</p>

<p>Note that the LangChain path needs a running server with the Bolt plugin enabled, whereas the embedded path above needs no server at all. Both talk to the same storage format.</p>

<h2 id="why-this-runs-well">Why This Runs Well</h2>

<p>Recall sits in the agent’s request path, so traversal speed is not an academic concern. On the LDBC Graphalytics suite, run on identical hardware against seven other graph engines, ArcadeDB completes weakly connected components in 0.08s and PageRank in 0.10s, winning 5 of the 6 standard algorithms. On the LSQB pattern-matching benchmark, Q6, a two-hop traversal counting 1.67 billion rows, finishes in 110ms. Full methodology and the reproducible harness are on the <a href="https://arcadedb.com/benchmarks.html">benchmarks page</a>.</p>

<p>Embedded mode removes the network entirely, which for a single-process agent is usually the largest remaining latency term.</p>

<h2 id="honest-comparison">Honest Comparison</h2>

<p>ArcadeDB is not the only option that stores graph and vectors together. <a href="https://memgraph.com">Memgraph</a> and <a href="https://falkordb.com">FalkorDB</a> both index embeddings in the same store as the graph and both target agent memory explicitly, and either is a reasonable choice.</p>

<p>The differences worth weighing are licensing and breadth. Memgraph Community is BSL 1.1 and FalkorDB is SSPLv1, both source-available rather than OSI open source; ArcadeDB is Apache 2.0. ArcadeDB also stores documents and time series alongside the graph, and runs embedded in your Python or JVM process rather than only as a server. The full side-by-side is in <a href="/blog/open-source-knowledge-graph-graphrag-databases-compared/">open source knowledge graph and GraphRAG databases compared</a>.</p>

<hr />

<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>

<h3 id="what-is-memory-for-an-llm-agent">What is memory for an LLM agent?</h3>

<p>Agent memory is the state an agent keeps between turns and between sessions. It usually splits into episodic memory (what happened, turn by turn), semantic memory (durable facts learned about entities), and working memory (the current context window). Only the first two need a database.</p>

<h3 id="why-is-a-vector-store-alone-not-enough-for-agent-memory">Why is a vector store alone not enough for agent memory?</h3>

<p>A vector store retrieves passages that resemble the query, but it cannot express that a fact was superseded, that two names refer to one person, or that a preference belongs to a project rather than a user. Those are relationships and validity intervals, which is what a graph stores.</p>

<h3 id="how-do-you-stop-an-agent-from-recalling-outdated-facts">How do you stop an agent from recalling outdated facts?</h3>

<p>Do not overwrite facts; invalidate them. Give each fact edge a valid_from and valid_to timestamp, and when a new value arrives, close the old edge instead of deleting it. Retrieval then filters to edges where valid_to is null, and the history stays auditable.</p>

<h3 id="can-arcadedb-store-agent-memory-and-the-embeddings-together">Can ArcadeDB store agent memory and the embeddings together?</h3>

<p>Yes. Embeddings live in a property on the same records that carry the graph, indexed with HNSW. A single transaction writes an episode, its vector, and its relationships, so semantic recall and relationship traversal run against one consistent copy of the data.</p>

<h3 id="does-arcadedb-work-with-langchain-for-agent-memory">Does ArcadeDB work with LangChain for agent memory?</h3>

<p>Yes. The official langchain-arcadedb package provides ArcadeDBGraph, which implements the LangChain GraphStore protocol and connects over ArcadeDB’s Bolt protocol. There is also a LlamaIndex PropertyGraphStore integration, and an MCP server that lets an LLM query the database directly.</p>

<h3 id="can-agent-memory-run-without-a-database-server">Can agent memory run without a database server?</h3>

<p>Yes. The arcadedb-embedded Python package runs the full engine inside your agent process, with a bundled Java runtime. For a single-process agent or a local assistant, that removes the server, the network hop, and the deployment entirely while keeping graph and vector recall.</p>

<hr />

<h2 id="getting-started">Getting Started</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>arcadedb-embedded
</code></pre></div></div>

<p>That is the whole install: the wheel bundles a Java runtime, so there is no JDK to set up. From there, the schema above is a working starting point you can adapt.</p>

<p>For the retrieval patterns behind this, see <a href="https://arcadedb.com/graph-rag.html">GraphRAG on ArcadeDB</a>. For entity extraction and knowledge graph construction, see <a href="https://arcadedb.com/knowledge-graphs.html">knowledge graphs</a>. ArcadeDB is Apache 2.0 and free in production, with no node limits and no enterprise edition.</p>]]></content><author><name>Luca Garulli</name></author><category term="LLM Agents" /><category term="Agent Memory" /><category term="Knowledge Graph" /><category term="GraphRAG" /><category term="Vector Search" /><category term="AI" /><category term="Python" /><category term="LangChain" /><summary type="html"><![CDATA[How to build persistent memory for LLM agents using a knowledge graph: episodic and semantic memory, temporal fact invalidation, and hybrid vector plus graph recall in ArcadeDB.]]></summary></entry><entry><title type="html">Open Source Knowledge Graph &amp;amp; GraphRAG Databases Compared (2026)</title><link href="https://arcadedb.com/blog/open-source-knowledge-graph-graphrag-databases-compared/" rel="alternate" type="text/html" title="Open Source Knowledge Graph &amp;amp; GraphRAG Databases Compared (2026)" /><published>2026-07-30T00:00:00+00:00</published><updated>2026-07-30T00:00:00+00:00</updated><id>https://arcadedb.com/blog/open-source-knowledge-graph-graphrag-databases-compared</id><content type="html" xml:base="https://arcadedb.com/blog/open-source-knowledge-graph-graphrag-databases-compared/"><![CDATA[<p><strong>The open source knowledge graph and GraphRAG databases worth evaluating in 2026 are <a href="https://arcadedb.com">ArcadeDB</a>, <a href="https://neo4j.com">Neo4j Community Edition</a>, <a href="https://memgraph.com">Memgraph</a>, <a href="https://falkordb.com">FalkorDB</a>, <a href="https://janusgraph.org">JanusGraph</a>, <a href="https://terminusdb.com">TerminusDB</a>, <a href="https://cayley.io">Cayley</a>, and <a href="https://github.com/kuzudb/kuzu">Kuzu</a>.</strong> Below we compare each on licensing, maintenance status, query languages, and whether it can serve GraphRAG retrieval, meaning graph traversal plus vector similarity, without a second database bolted alongside it.</p>

<p><strong>The short version:</strong> three of the eight index vectors in the same store as the graph, and can therefore serve GraphRAG on their own: ArcadeDB, Memgraph, and FalkorDB. They differ mainly on licence. ArcadeDB is Apache 2.0, Memgraph Community is BSL 1.1, FalkorDB is SSPLv1. Only ArcadeDB is OSI open source, and only ArcadeDB also stores documents and time series and runs embedded.</p>

<p>A word on where this is published. This is the ArcadeDB blog, we build ArcadeDB, and we think it is the best fit for most knowledge graph projects in 2026. You should discount our opinion accordingly. What we can do is be precise about the things that are checkable: which licence each project uses, when it last shipped a release, and what it does not do. Every status claim in this article was verified against the project’s own repository on 30 July 2026, and we say plainly where an alternative is the better choice.</p>

<p>Two of the eight are not really live options any more, and we say so rather than padding the list.</p>

<hr />

<h2 id="what-makes-a-knowledge-graph-database">What Makes a Knowledge Graph Database?</h2>

<p>A knowledge graph is not just a graph. It is a graph where the nodes represent real entities (people, documents, products, concepts), the edges carry typed meaning (authored-by, cites, depends-on), and the whole thing is used to answer questions that require following those relationships rather than filtering a table.</p>

<p>That imposes requirements beyond “can store nodes and edges”:</p>

<ul>
  <li><strong>Multi-hop traversal at usable speed.</strong> The value of a knowledge graph is in the questions that cross three or four relationships. If those queries take minutes, nobody asks them.</li>
  <li><strong>Semantic retrieval.</strong> Users do not know your terminology. Someone searching for “how to handle errors in the payment service” needs to find a document titled “Exception Management in Billing Module”. That requires vector embeddings, not keyword matching.</li>
  <li><strong>Exact retrieval too.</strong> Sometimes the query is a specific error code or clause number, where semantic similarity is exactly wrong and you need literal full-text matching.</li>
  <li><strong>A licence you can actually build on.</strong> If you plan to embed the database in a product you distribute, a copyleft licence is a business decision, not a footnote.</li>
  <li><strong>Someone still maintaining it.</strong> A knowledge graph is long-lived infrastructure. Adopting an unmaintained engine means you have adopted its unfixed bugs permanently.</li>
</ul>

<p>That last criterion eliminates more candidates than people expect.</p>

<hr />

<h2 id="1-arcadedb">1. ArcadeDB</h2>

<p><strong>Licence:</strong> Apache 2.0 · <strong>Latest release:</strong> 26.7.3 (July 2026) · <strong>Status:</strong> actively developed</p>

<p>ArcadeDB is a multi-model database that stores graph, document, key-value, full-text search, vector, and time-series data in one engine, under a single ACID transaction boundary.</p>

<h3 id="why-it-stands-out">Why It Stands Out</h3>

<p>For knowledge graph work specifically, the argument is that a knowledge graph needs three retrieval modes and most stacks make you run three databases to get them.</p>

<p><strong>Graph, vectors, and full-text in one query.</strong> A realistic knowledge graph query does several things at once: find the entities semantically related to a question, filter to those matching an exact identifier, then traverse out to everything connected. On ArcadeDB that is one statement against one copy of the data. On most alternatives it is a graph query, a separate vector search, and an application layer merging the results, with the consistency between the two stores becoming your problem.</p>

<p><strong>Native vector search.</strong> ArcadeDB indexes embeddings with JVector, using DiskANN and HNSW with SIMD acceleration, over the same records that hold the graph. There is no plugin to install and no external service to keep in sync.</p>

<p><strong>Five query languages.</strong> SQL, OpenCypher 25, Apache TinkerPop Gremlin, GraphQL, and the MongoDB query language all run against the same data. The Cypher engine is native rather than a translation layer and passes 97.8% of the official TCK, which matters if you are moving existing queries across.</p>

<p><strong>Apache 2.0, with no edition split.</strong> Clustering with Raft consensus, replication, embedded mode, and vector search are all in the free build. There is no Enterprise edition holding back the features you need in production, and no node or core limit.</p>

<p><strong>Embedded or server.</strong> ArcadeDB runs inside your JVM, or in Python in-process via <code class="language-plaintext highlighter-rouge">pip install arcadedb-embedded</code> with a bundled JRE. For a knowledge graph feeding a local RAG pipeline, that removes the server entirely.</p>

<h3 id="where-its-not-the-best-fit">Where It’s Not the Best Fit</h3>

<p>Honesty requires a real list here, not a token one.</p>

<p><strong>It is the smallest community of the six.</strong> ArcadeDB has roughly 1,055 GitHub stars against Neo4j’s enormous ecosystem, Cayley’s 15,000, and JanusGraph’s 5,800. That translates into fewer Stack Overflow answers, fewer blog posts when you hit an edge case, and fewer engineers who already know it. If hiring people who have used your database before is a hard requirement, this is a genuine argument against us.</p>

<p><strong>It is not a triplestore.</strong> If your knowledge graph is RDF, your data is in Turtle or N-Triples, and your team writes SPARQL, ArcadeDB is the wrong shape. It is a property graph engine. Use a dedicated RDF store.</p>

<p><strong>It does not shard a single graph across machines.</strong> ArcadeDB replicates for availability rather than partitioning one graph across a cluster. If your graph genuinely exceeds what one machine can hold, JanusGraph over Cassandra is the more honest answer.</p>

<p><strong>It runs on the JVM.</strong> The bundled-JRE Python package hides this well, but the engine is Java. If your operational policy excludes the JVM, that is decisive.</p>

<hr />

<h2 id="2-neo4j-community-edition">2. Neo4j Community Edition</h2>

<p><strong>Licence:</strong> GPLv3 · <strong>Status:</strong> actively developed</p>

<p>Neo4j is the reference point for property graphs, and Community Edition is a genuinely capable database. It is also the option most often adopted without reading the licence.</p>

<h3 id="the-good">The Good</h3>

<p><strong>The ecosystem is unmatched.</strong> More documentation, more tutorials, more courses, more consultants, and more people who already know Cypher than every other option on this list combined. For a team learning graph modelling from scratch, that is worth a great deal.</p>

<p><strong>Cypher is excellent.</strong> Neo4j designed the language that the rest of the industry now implements. It is genuinely pleasant for pattern matching.</p>

<p><strong>Full ACID and a mature engine.</strong> Community Edition is not a crippled demo. For single-instance workloads it is a solid production database.</p>

<h3 id="the-problems">The Problems</h3>

<p><strong>GPLv3 is a business decision.</strong> Community Edition is copyleft. If you distribute software that includes or links to it, the licence reaches your application. For internal deployments this is usually irrelevant; for anything you ship to customers it is often a blocker, and the escape hatch is a commercial Enterprise licence.</p>

<p><strong>No clustering.</strong> Community Edition does not support clustering, so it is limited to single-instance deployments. High availability and read scaling are Enterprise features. For a knowledge graph that becomes load-bearing infrastructure, that ceiling arrives eventually.</p>

<p><strong>Graph only.</strong> Neo4j stores graphs. Documents, time series, and the rest live in other systems, which for knowledge graph work usually means running a separate store for the source documents alongside the graph of extracted entities.</p>

<h3 id="when-to-choose-it-anyway">When to Choose It Anyway</h3>

<p>If your team already knows Cypher, your deployment is internal and single-instance, and you value ecosystem depth over licence flexibility, Neo4j Community Edition is a reasonable and low-risk choice. Plenty of successful knowledge graphs run on it.</p>

<hr />

<h2 id="3-memgraph">3. Memgraph</h2>

<p><strong>Licence:</strong> BSL 1.1 (Community) · <strong>Latest release:</strong> v3.12.0 (July 2026) · <strong>Status:</strong> actively developed</p>

<!-- SOURCE licence: https://github.com/memgraph/memgraph/blob/master/licenses/BSL.txt — Community edition is BSL 1.1; Enterprise uses the separate MEL licence. -->
<!-- SOURCE release/stars: https://api.github.com/repos/memgraph/memgraph — v3.12.0 published 2026-07-15, 4,293 stars, not archived. Verified 30 July 2026. -->
<!-- SOURCE positioning + vector index: https://github.com/memgraph/memgraph repository description, "High-performance open-source in-memory graph database for GraphRAG, AI memory, agentic AI, and real-time graph analytics. Cypher-compatible, built in C++." -->

<p>Memgraph is an in-memory, Cypher-compatible graph database written in C++, and it has aimed squarely at GraphRAG and agent memory. If you have asked an AI assistant about GraphRAG recently, there is a good chance it cited Memgraph.</p>

<h3 id="the-good-1">The Good</h3>

<p><strong>Built for this workload.</strong> Memgraph ships text and vector indexes in the same store as the graph, so a retrieval pipeline can run graph traversal and similarity search as one database operation rather than two systems stitched together. This is the same architectural argument we make for ArcadeDB, and it is a fair one when they make it.</p>

<p><strong>In-memory speed.</strong> Holding the working set in memory gives Memgraph excellent latency on traversals, which matters when retrieval sits in the request path of an agent loop.</p>

<p><strong>Cypher compatible.</strong> Existing Cypher and Neo4j-shaped tooling largely carries over.</p>

<h3 id="the-problems-1">The Problems</h3>

<p><strong>BSL is not open source.</strong> Memgraph Community is licensed under the Business Source License 1.1, which the Open Source Initiative does not recognise as an open-source licence. It restricts commercial use in ways a permissive licence does not, and Enterprise sits behind a separate proprietary licence. If your reason for avoiding Neo4j was licensing, read the BSL carefully before treating Memgraph as the escape.</p>

<p><strong>In-memory is a cost model.</strong> RAM is the constraint. For a knowledge graph that is large and mostly cold, keeping it resident is a different budget from a disk-based engine.</p>

<p><strong>Graph plus vectors, not multi-model.</strong> Documents and time series live elsewhere.</p>

<h3 id="when-to-choose-it-anyway-1">When to Choose It Anyway</h3>

<p>If latency is your dominant constraint, your graph fits comfortably in memory, and BSL is acceptable to your legal team, Memgraph is genuinely strong at exactly this workload. It is the most direct competitor to ArcadeDB on GraphRAG and we would rather say so than pretend otherwise.</p>

<hr />

<h2 id="4-falkordb">4. FalkorDB</h2>

<p><strong>Licence:</strong> SSPLv1 · <strong>Latest release:</strong> v4.20.1 (July 2026) · <strong>Status:</strong> actively developed</p>

<!-- SOURCE licence: https://docs.falkordb.com/References/license.html — Server Side Public License v1 (SSPLv1). -->
<!-- SOURCE release/stars: https://api.github.com/repos/FalkorDB/FalkorDB — v4.20.1 published 2026-07-15, 4,853 stars, not archived. Verified 30 July 2026. -->
<!-- SOURCE GraphBLAS + positioning: https://github.com/FalkorDB/FalkorDB repository description, "uses GraphBLAS under the hood for its sparse adjacency matrix graph representation. Our goal is to provide the best Knowledge Graph for LLM (GraphRAG)." -->
<!-- SOURCE RedisGraph lineage: covered in our earlier post /blog/neo4j-alternatives-in-2026-a-fair-look-at-the-open-source-options/ -->

<p>FalkorDB is the continuation of RedisGraph after Redis discontinued it, rebuilt around GraphBLAS sparse adjacency matrices and aimed explicitly at knowledge graphs for LLMs.</p>

<h3 id="the-good-2">The Good</h3>

<p><strong>GraphBLAS is a genuinely good fit.</strong> Representing the graph as sparse matrices turns multi-hop traversal into linear algebra, and it makes FalkorDB fast on the pattern-matching workloads GraphRAG generates.</p>

<p><strong>HNSW vector index in the same store.</strong> Like Memgraph, FalkorDB indexes embeddings alongside the graph, so hybrid retrieval does not require a second database.</p>

<p><strong>Cypher, with a low-friction operational model.</strong> It runs as a Redis module, which is familiar territory for a lot of teams.</p>

<h3 id="the-problems-2">The Problems</h3>

<p><strong>SSPL is source-available, not open source.</strong> The Server Side Public License restricts how you may offer the software as a service and is not an OSI-approved open-source licence. For most self-hosted users this is not a practical constraint, but it is not the same freedom Apache 2.0 gives you, and it should not be described as open source.</p>

<p><strong>Graph and vectors only.</strong> Same limitation as Memgraph: no document or time-series model.</p>

<p><strong>Redis-module operational shape.</strong> Convenient if you already run Redis, an additional dependency if you do not.</p>

<h3 id="when-to-choose-it-anyway-2">When to Choose It Anyway</h3>

<p>If you want fast GraphRAG retrieval, you already operate Redis, and SSPL is acceptable, FalkorDB is a well-built option with a clear focus on exactly this use case.</p>

<hr />

<h2 id="5-janusgraph">5. JanusGraph</h2>

<p><strong>Licence:</strong> Apache 2.0 · <strong>Latest release:</strong> 1.1.0 (November 2024) · <strong>Status:</strong> maintained, slow release cadence</p>

<p>JanusGraph is a distributed graph database under the Linux Foundation, and the direct descendant of Titan. It is the option built for graphs that genuinely do not fit on one machine.</p>

<h3 id="the-good-3">The Good</h3>

<p><strong>Real horizontal scale.</strong> JanusGraph is a graph layer over a distributed storage backend, shipping support for Apache Cassandra, Apache HBase, and Oracle Berkeley DB Java Edition. If you already run Cassandra at scale, JanusGraph inherits that operational maturity and partitions a graph across it in a way most single-node engines cannot.</p>

<p><strong>Genuinely open, genuinely neutral.</strong> Apache 2.0, under the Linux Foundation, with no vendor holding an Enterprise edition back. There is no commercial upsell waiting.</p>

<p><strong>TinkerPop native.</strong> Gremlin is the query language, and JanusGraph is a first-class TinkerPop implementation, so the wider Gremlin tooling ecosystem works.</p>

<h3 id="the-problems-3">The Problems</h3>

<p><strong>You are operating at least two distributed systems.</strong> JanusGraph is not a complete database. Production means running and tuning Cassandra or HBase underneath it, plus usually Elasticsearch or Solr for indexing. The operational burden is substantially higher than any single-binary option here, and that is the dominant cost for most teams.</p>

<p><strong>Slow release cadence.</strong> Version 1.0.0 arrived in October 2023 and 1.1.0 in November 2024. The repository is active, but a project shipping roughly one release a year is a different maintenance proposition from one shipping monthly.</p>

<p><strong>Gremlin only, and no native vectors.</strong> There is no Cypher and no SQL. For semantic search you will pair it with a separate vector database, which for knowledge graph work means the multi-store architecture again.</p>

<h3 id="when-to-choose-it-anyway-3">When to Choose It Anyway</h3>

<p>If your graph is genuinely too large for one machine and you already operate Cassandra or HBase, JanusGraph is the right answer and we would tell you so. That is a real scenario and no amount of single-node performance changes it.</p>

<hr />

<h2 id="6-terminusdb">6. TerminusDB</h2>

<p><strong>Licence:</strong> Apache 2.0 · <strong>Latest release:</strong> 12.0.6 (June 2026) · <strong>Status:</strong> actively developed</p>

<p>TerminusDB is a document graph database whose distinguishing idea is git-style version control for data: branching, merging, diffing, and time travel over the database itself.</p>

<h3 id="the-good-4">The Good</h3>

<p><strong>Versioning is a first-class feature, not a pattern you implement.</strong> For a knowledge graph curated by humans, being able to branch the graph, make changes, review a diff, and merge is genuinely valuable and nothing else on this list offers it natively. Regulatory and scientific use cases where you must prove what the data said last quarter are a natural fit.</p>

<p><strong>Apache 2.0 and actively shipping.</strong> Releases are current, the repository saw commits the week this was written, and the licence is permissive.</p>

<p><strong>Document plus graph model.</strong> Data is modelled as documents with a schema, connected as a graph, which fits knowledge graph work better than a pure triplestore for most application developers.</p>

<h3 id="the-problems-4">The Problems</h3>

<p><strong>Smaller ecosystem than its age suggests.</strong> Roughly 3,370 GitHub stars and a modest contributor base. Documentation and community answers are thinner than Neo4j or JanusGraph.</p>

<p><strong>No native vector search.</strong> TerminusDB does not index embeddings alongside the graph, so semantic retrieval means a separate vector store. <!-- UNVERIFIED: TerminusDB may have added vector capability in a recent release; verify against current docs before publishing. --></p>

<p><strong>WOQL is a learning curve.</strong> The native query language is its own thing rather than Cypher, SQL, or Gremlin, so existing team knowledge does not transfer.</p>

<h3 id="when-to-choose-it-anyway-4">When to Choose It Anyway</h3>

<p>If versioning and provenance are the primary requirement rather than a nice-to-have, TerminusDB is the strongest option here and the comparison is not close.</p>

<hr />

<h2 id="7-cayley">7. Cayley</h2>

<p><strong>Licence:</strong> Apache 2.0 · <strong>Latest tagged release:</strong> v0.7.7 (October 2019) · <strong>Status:</strong> effectively dormant</p>

<p>Cayley is a linked-data graph database written in Go, inspired by the graph infrastructure behind Google’s Knowledge Graph. It has 15,045 GitHub stars, which is more than anything else on this list, and it is the clearest illustration of why star counts are a poor proxy for project health.</p>

<h3 id="the-good-5">The Good</h3>

<p><strong>The design is genuinely nice.</strong> A Go binary with pluggable backends, a clean HTTP API, and a small footprint. For a read-mostly linked-data store it is pleasant to work with.</p>

<p><strong>Permissively licensed with no vendor.</strong> Apache 2.0, no commercial edition, no strings.</p>

<h3 id="the-problems-5">The Problems</h3>

<p><strong>No tagged release since October 2019.</strong> That is the single fact that matters. The repository is not archived and there is occasional commit activity, but a database that has not cut a release in nearly seven years is not something to build a knowledge graph on in 2026. Bug fixes, security patches, and dependency updates are not arriving on any schedule you can rely on.</p>

<p><strong>No vector search, no modern AI integration.</strong> Cayley predates the entire embedding era and has no answer for semantic retrieval.</p>

<p><strong>Query languages are its own.</strong> Gizmo, a JavaScript-based query API, alongside GraphQL and MQL variants. <!-- UNVERIFIED: exact set of query languages in current Cayley; verify against project docs before publishing. --></p>

<h3 id="verdict">Verdict</h3>

<p>We would not start a new knowledge graph on Cayley in 2026, and we would say the same if it were our own project. Included here because its star count keeps it near the top of search results, and readers deserve to know that the number reflects 2015 enthusiasm rather than 2026 maintenance.</p>

<hr />

<h2 id="8-kuzu">8. Kuzu</h2>

<p><strong>Licence:</strong> MIT · <strong>Latest release:</strong> v0.11.3 (October 2025) · <strong>Status:</strong> archived</p>

<p>Kuzu was an embedded analytical graph database with a columnar storage engine and a Cypher interface, and it was genuinely good at what it did.</p>

<h3 id="what-happened">What Happened</h3>

<p>The GitHub repository was archived in October 2025 following the team’s acquisition by Apple. Development stopped. The code remains available under MIT, and community forks exist, but there is no funded maintainer and no release schedule.</p>

<h3 id="strengths-while-it-lasted">Strengths, While It Lasted</h3>

<p><strong>Excellent analytical performance.</strong> Kuzu’s columnar engine was fast on multi-hop analytical queries, and it still appears in benchmark comparisons, including <a href="https://arcadedb.com/benchmarks.html">our own LDBC results</a>, where it is marginally faster than ArcadeDB on LSQB Q2. We are not going to pretend otherwise.</p>

<p><strong>Embedded and Python-first.</strong> It brought embedded graph workloads to Python properly, which is exactly the niche <code class="language-plaintext highlighter-rouge">arcadedb-embedded</code> now occupies.</p>

<h3 id="limitations">Limitations</h3>

<p><strong>It is archived.</strong> Everything else is secondary. An archived database is not a foundation for new infrastructure, however good the engine was.</p>

<p><strong>MIT means the code survives.</strong> If you already run Kuzu, you are not stranded, and forking is legally straightforward. But you now own a database engine, which is a larger commitment than most teams intend to make.</p>

<hr />

<h2 id="the-comparison-at-a-glance">The Comparison at a Glance</h2>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>ArcadeDB</th>
      <th>Neo4j CE</th>
      <th>Memgraph</th>
      <th>FalkorDB</th>
      <th>JanusGraph</th>
      <th>TerminusDB</th>
      <th>Cayley</th>
      <th>Kuzu</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Licence</strong></td>
      <td>Apache 2.0</td>
      <td>GPLv3</td>
      <td>BSL 1.1</td>
      <td>SSPLv1</td>
      <td>Apache 2.0</td>
      <td>Apache 2.0</td>
      <td>Apache 2.0</td>
      <td>MIT</td>
    </tr>
    <tr>
      <td><strong>OSI open source</strong></td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Status (Jul 2026)</strong></td>
      <td>Active</td>
      <td>Active</td>
      <td>Active</td>
      <td>Active</td>
      <td>Maintained</td>
      <td>Active</td>
      <td>Dormant</td>
      <td>Archived</td>
    </tr>
    <tr>
      <td><strong>Latest release</strong></td>
      <td>26.7.3</td>
      <td>Current</td>
      <td>v3.12.0</td>
      <td>v4.20.1</td>
      <td>1.1.0 (2024)</td>
      <td>12.0.6</td>
      <td>v0.7.7 (2019)</td>
      <td>v0.11.3 (2025)</td>
    </tr>
    <tr>
      <td><strong>Vectors in-store</strong></td>
      <td>Yes</td>
      <td>Partial</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Serves GraphRAG alone</strong></td>
      <td>Yes</td>
      <td>Partial</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Beyond graph</strong></td>
      <td>Doc, KV, TS, FTS</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>Doc</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Clustering free</strong></td>
      <td>Yes</td>
      <td>No</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>Yes</td>
      <td>n/a</td>
      <td>n/a</td>
    </tr>
    <tr>
      <td><strong>Query languages</strong></td>
      <td>5</td>
      <td>Cypher</td>
      <td>Cypher</td>
      <td>Cypher</td>
      <td>Gremlin</td>
      <td>WOQL</td>
      <td>Gizmo</td>
      <td>Cypher</td>
    </tr>
    <tr>
      <td><strong>Embedded mode</strong></td>
      <td>Yes</td>
      <td>No (Enterprise)</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Separate storage backend</strong></td>
      <td>No</td>
      <td>No</td>
      <td>No</td>
      <td>Redis module</td>
      <td>Required</td>
      <td>No</td>
      <td>Optional</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>GitHub stars</strong></td>
      <td>1,055</td>
      <td>Very large</td>
      <td>4,293</td>
      <td>4,853</td>
      <td>5,816</td>
      <td>3,370</td>
      <td>15,045</td>
      <td>4,028</td>
    </tr>
  </tbody>
</table>

<p>Star counts and release data verified against each project’s GitHub repository on 30 July 2026.</p>

<hr />

<h2 id="why-licensing-matters-for-knowledge-graphs">Why Licensing Matters for Knowledge Graphs</h2>

<p>Knowledge graphs have a licensing problem that other databases do not, because they tend to end up embedded in products rather than sitting behind a service boundary.</p>

<p>A knowledge graph that powers an internal search tool is a service you deploy, and GPLv3 is largely irrelevant. A knowledge graph that ships inside a desktop application, an on-premise product, or a customer-installed agent is distributed software, and a copyleft licence reaches your code. Teams frequently discover this after the architecture is settled.</p>

<p>Apache 2.0, used by ArcadeDB, JanusGraph, TerminusDB, and Cayley, permits embedding in proprietary software with no source obligation. MIT, used by Kuzu, is similarly permissive. GPLv3, used by Neo4j Community Edition, does not, and the commercial escape hatch is priced accordingly.</p>

<p>The second licensing question is what the free edition withholds. Neo4j reserves clustering and embedded mode for Enterprise. ArcadeDB, JanusGraph, and TerminusDB do not have a paid edition holding features back at all.</p>

<hr />

<h2 id="so-which-one-should-you-choose">So Which One Should You Choose?</h2>

<p><strong>Choose ArcadeDB</strong> if you want graph traversal, vector search, and full-text retrieval in one engine under a permissive licence, and you would rather not operate three databases to build one knowledge graph. Accept that you are picking the smallest community here.</p>

<p><strong>Choose Neo4j Community Edition</strong> if ecosystem depth matters more than licence flexibility, your deployment is internal and single-instance, and your team already writes Cypher.</p>

<p><strong>Choose Memgraph</strong> if retrieval latency is the dominant constraint, your graph fits in memory, and your legal team is comfortable with BSL 1.1.</p>

<p><strong>Choose FalkorDB</strong> if you want fast GraphRAG retrieval, already operate Redis, and SSPL is acceptable.</p>

<p><strong>Choose JanusGraph</strong> if your graph genuinely does not fit on one machine and you already operate Cassandra or HBase. Do not choose it to avoid that operational burden, because it adds to it.</p>

<p><strong>Choose TerminusDB</strong> if data versioning, branching, and provenance are core requirements rather than conveniences.</p>

<p><strong>Do not start on Cayley</strong> in 2026, despite the star count, unless you are prepared to maintain it.</p>

<p><strong>Do not start on Kuzu</strong>, because it is archived, unless you are deliberately adopting a fork and accepting ownership.</p>

<hr />

<h2 id="key-takeaways">Key Takeaways</h2>

<ul>
  <li>Six of these eight are actively maintained. Cayley has not cut a release since 2019, and Kuzu was archived in October 2025.</li>
  <li>GitHub stars measure historical enthusiasm, not current health. Cayley has the most stars and the least maintenance.</li>
  <li>Licensing determines whether you can embed the database in a distributed product. Apache 2.0 and MIT permit it; GPLv3 constrains it.</li>
  <li>GraphRAG needs graph traversal and vector similarity together. Three of these eight do both in one store: ArcadeDB, Memgraph, and FalkorDB. The other five expect a separate vector database.</li>
  <li>The GraphRAG shortlist comes down to licence: ArcadeDB is Apache 2.0, Memgraph is BSL, FalkorDB is SSPL. Only ArcadeDB is OSI open source, and only ArcadeDB is multi-model and embeddable.</li>
  <li>ArcadeDB’s genuine weakness is community size: it has the fewest GitHub stars of the eight.</li>
</ul>

<hr />

<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>

<h3 id="what-is-the-best-open-source-knowledge-graph-database-in-2026">What is the best open source knowledge graph database in 2026?</h3>

<p>It depends on what you already run. ArcadeDB, Memgraph, and FalkorDB all index vectors alongside the graph and can serve GraphRAG alone; they differ on licence, with only ArcadeDB being OSI open source. JanusGraph is better at scale on Cassandra or HBase, and TerminusDB is better when versioning matters most.</p>

<h3 id="which-open-source-database-is-best-for-graphrag">Which open source database is best for GraphRAG?</h3>

<p>GraphRAG needs graph traversal and vector similarity in the same query. ArcadeDB, Memgraph, and FalkorDB all index embeddings in the same store as the graph, so any of the three can serve GraphRAG without a separate vector database. ArcadeDB is the only one of the three under an OSI-approved open source licence.</p>

<h3 id="is-neo4j-community-edition-suitable-for-a-production-knowledge-graph">Is Neo4j Community Edition suitable for a production knowledge graph?</h3>

<p>It can be, with two caveats. Community Edition is GPLv3, which is a copyleft licence that affects how you can distribute software built on it, and it does not support clustering, so it is limited to single-instance deployments. Both are Enterprise Edition features.</p>

<h3 id="which-open-source-knowledge-graph-databases-are-still-actively-maintained">Which open source knowledge graph databases are still actively maintained?</h3>

<p>As of July 2026, ArcadeDB, Memgraph, FalkorDB, TerminusDB, JanusGraph, and Neo4j all ship releases. Kuzu was archived on GitHub in October 2025 after its team was acquired by Apple. Cayley’s repository is not archived but its last tagged release, v0.7.7, dates from October 2019.</p>

<h3 id="do-i-need-a-separate-vector-database-for-a-knowledge-graph">Do I need a separate vector database for a knowledge graph?</h3>

<p>Not necessarily. A knowledge graph that also serves semantic search needs both relationships and embeddings. ArcadeDB indexes vectors natively with JVector alongside the graph, so a single query can rank by similarity and traverse edges. Most alternatives require pairing the graph with a separate vector store.</p>

<h3 id="what-is-the-difference-between-a-knowledge-graph-and-an-rdf-triplestore">What is the difference between a knowledge graph and an RDF triplestore?</h3>

<p>A triplestore models data strictly as subject-predicate-object triples and typically queries with SPARQL. A property graph attaches arbitrary properties to nodes and edges and queries with Cypher, Gremlin, or SQL. Both can express a knowledge graph; property graphs are usually easier for application developers.</p>

<h3 id="does-janusgraph-need-cassandra-or-hbase-to-run">Does JanusGraph need Cassandra or HBase to run?</h3>

<p>JanusGraph is a graph layer rather than a complete storage engine, so it runs on top of a separate backend. It ships support for Apache Cassandra, Apache HBase, and Oracle Berkeley DB Java Edition. Berkeley DB suits local development; Cassandra or HBase is expected in production.</p>

<hr />

<h2 id="getting-started-with-arcadedb">Getting Started with ArcadeDB</h2>

<p>If the single-engine argument is the one that lands, the fastest way to test it is to build a small knowledge graph on your own data and see whether one query really can do all three retrieval modes.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-p</span> 2480:2480 <span class="nt">-p</span> 2424:2424 <span class="se">\</span>
  <span class="nt">-e</span> <span class="nv">JAVA_OPTS</span><span class="o">=</span><span class="s2">"-Darcadedb.server.rootPassword=playwithdata"</span> <span class="se">\</span>
  arcadedata/arcadedb:latest
</code></pre></div></div>

<p>Then open ArcadeDB Studio at <code class="language-plaintext highlighter-rouge">http://localhost:2480</code> and start modelling. For the concepts behind entity extraction, semantic search, and temporal knowledge, see <a href="https://arcadedb.com/knowledge-graphs.html">knowledge graphs on ArcadeDB</a>. If you are building retrieval for an LLM on top of it, <a href="https://arcadedb.com/graph-rag.html">GraphRAG</a> covers the hybrid retrieval patterns. If you are migrating an existing graph, the <a href="https://arcadedb.com/neo4j.html">Neo4j migration guide</a> covers Cypher and Bolt compatibility.</p>

<p>ArcadeDB is Apache 2.0, free in production, with no node limits and no Enterprise edition. Benchmark it against whatever you run today and keep whichever wins.</p>]]></content><author><name>Luca Garulli</name></author><category term="Knowledge Graph" /><category term="GraphRAG" /><category term="Graph Database" /><category term="Open Source" /><category term="Comparison" /><category term="Multi-Model" /><category term="Memgraph" /><category term="FalkorDB" /><category term="JanusGraph" /><category term="TerminusDB" /><category term="Cayley" /><category term="Kuzu" /><category term="Neo4j" /><category term="Knowledge Graph Database" /><summary type="html"><![CDATA[Open source knowledge graph and GraphRAG databases compared in 2026: ArcadeDB, Neo4j CE, Memgraph, FalkorDB, JanusGraph, TerminusDB, Cayley, and Kuzu, on licensing, vector search, and maintenance.]]></summary></entry><entry><title type="html">ArcadeDB 26.7.3: Three Security Advisories and a Super-Node Edge-Merge Data-Loss Fix</title><link href="https://arcadedb.com/blog/arcadedb-26-7-3/" rel="alternate" type="text/html" title="ArcadeDB 26.7.3: Three Security Advisories and a Super-Node Edge-Merge Data-Loss Fix" /><published>2026-07-17T00:00:00+00:00</published><updated>2026-07-17T00:00:00+00:00</updated><id>https://arcadedb.com/blog/arcadedb-26-7-3</id><content type="html" xml:base="https://arcadedb.com/blog/arcadedb-26-7-3/"><![CDATA[<p><strong>ArcadeDB 26.7.3</strong> is a hotfix on top of <a href="https://arcadedb.com/blog/arcadedb-26-7-2/">26.7.2</a>. It closes three security advisories, two in the MCP server transport and one in the JavaScript/Java trigger authorization gate, and repairs a data-loss regression in the commutative super-node edge-append merge that 26.7.2 introduced.</p>

<p>Upgrade if you can. It matters most if you expose the MCP server, run in server or multi-tenant mode, or store high-degree (super-node) graphs. There are no breaking changes and no schema migration in this release.</p>

<h2 id="major-highlights">Major Highlights</h2>

<h3 id="security-advisories">Security Advisories</h3>

<p>All three come from the same internal audit that produced the 26.7.2 fixes, covering MCP-transport and trigger surfaces that round did not reach.</p>

<ul>
  <li><strong>MCP command transport disabled all engine permission checks</strong> (<a href="https://github.com/ArcadeData/arcadedb/security/advisories/GHSA-6x73-v3rc-f57c">GHSA-6x73-v3rc-f57c</a>). The MCP transport never bound the authenticated principal onto the request thread’s <code class="language-plaintext highlighter-rouge">DatabaseContext</code>, so the engine permission gates (which are deliberate no-ops when no user is bound) silently passed for every MCP caller. A non-root, MCP-allowed reader could perform arbitrary writes, DDL, and schema or security mutation; the <code class="language-plaintext highlighter-rouge">query</code> + <code class="language-plaintext highlighter-rouge">js</code> sub-case could execute arbitrary in-JVM JavaScript. The principal is now bound at the single DB-resolution chokepoint and cleared on the pooled worker thread, so the engine per-user gates enforce for MCP exactly as they do for the HTTP, Bolt, PostgreSQL, and gRPC transports.</li>
  <li><strong>MCP <code class="language-plaintext highlighter-rouge">get_server_settings</code> leaked the HA clusterToken in cleartext</strong> (<a href="https://github.com/ArcadeData/arcadedb/security/advisories/GHSA-p9wc-4fhr-78wm">GHSA-p9wc-4fhr-78wm</a>). The MCP <code class="language-plaintext highlighter-rouge">get_server_settings</code> tool masked only settings whose key contained <code class="language-plaintext highlighter-rouge">"password"</code>, so <code class="language-plaintext highlighter-rouge">arcadedb.ha.clusterToken</code> was returned raw. That token is the trust anchor for cluster-forwarded authentication, so leaking it enables full root impersonation. This is the MCP sibling of the 26.7.2 fix (<a href="https://github.com/ArcadeData/arcadedb/security/advisories/GHSA-46hj-24h4-j8gf">GHSA-46hj-24h4-j8gf</a>); the tool now redacts both value and default via <code class="language-plaintext highlighter-rouge">GlobalConfiguration.isHidden()</code>, matching <code class="language-plaintext highlighter-rouge">GetServerHandler</code>.</li>
  <li><strong>JavaScript / Java triggers could escalate a schema admin to server-wide admin</strong> (<a href="https://github.com/ArcadeData/arcadedb/security/advisories/GHSA-38pf-6hp2-pxww">GHSA-38pf-6hp2-pxww</a>). A <code class="language-plaintext highlighter-rouge">JAVASCRIPT</code> trigger binds the real database object into a GraalVM context, so its script could call <code class="language-plaintext highlighter-rouge">database.getSecurity().createUser(...)</code> and escalate an <code class="language-plaintext highlighter-rouge">UPDATE_SCHEMA</code> (schema-admin) user to a server-wide admin; a <code class="language-plaintext highlighter-rouge">JAVA</code> trigger runs an arbitrary loaded class. Creating either host-code trigger type now requires <code class="language-plaintext highlighter-rouge">UPDATE_SECURITY</code> at the <code class="language-plaintext highlighter-rouge">LocalSchema.createTrigger</code> chokepoint, mirroring the <code class="language-plaintext highlighter-rouge">DEFINE FUNCTION ... LANGUAGE js</code> gate (<a href="https://github.com/ArcadeData/arcadedb/security/advisories/GHSA-vwjc-v7x7-cm6g">GHSA-vwjc-v7x7-cm6g</a>). Declarative SQL triggers keep <code class="language-plaintext highlighter-rouge">UPDATE_SCHEMA</code>, and schema reload of existing triggers is unaffected.</li>
</ul>

<h2 id="major-fixes">Major Fixes</h2>

<h3 id="graph-engine">Graph Engine</h3>

<ul>
  <li>
    <p><strong>Edge-append merge no longer reverts concurrent writes on multi-page edge chunks</strong> (<a href="https://github.com/ArcadeData/arcadedb/issues/5302">#5302</a>). The commutative edge-append merge (<code class="language-plaintext highlighter-rouge">GRAPH_EDGE_APPEND_MERGE</code>, introduced in 26.7.2) resolved a commit-time page conflict by re-deriving the conflicted page and replaying the transaction’s tracked appends. For an edge chunk stored as a <strong>multi-page record</strong> (a chunk that straddles a page boundary) this re-derivation was unsound: the rebase re-read the chunk through the transaction’s stale in-transaction page copy and committed it, silently reverting concurrently committed appends on the continuation page. The observable symptoms were zeroed chunk tails, shifted or aliased pairs, lost edges, and <code class="language-plaintext highlighter-rouge">BufferUnderflowException</code> on later traversals of the vertex.</p>

    <p>Only records living entirely in place on the conflicted page are now re-derived. Multi-page and indirected (placeholder) chunk records fall back to the standard full-transaction retry. Single-page chunks, which are the vast majority and include all super-node stripe chunks, keep the merge.</p>
  </li>
</ul>

<h3 id="mcp-server">MCP Server</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">get_schema</code> now builds its schema through a dedicated <code class="language-plaintext highlighter-rouge">buildSchema</code> path.</li>
  <li>The MCP dispatcher handles a missing resource with a proper <code class="language-plaintext highlighter-rouge">MCPResourceNotFoundException</code> instead of a generic failure.</li>
</ul>

<h2 id="getting-started-with-2673">Getting Started with 26.7.3</h2>

<h3 id="docker">Docker</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull arcadedata/arcadedb:26.7.3
</code></pre></div></div>

<p>Visit our <a href="https://hub.docker.com/r/arcadedata/arcadedb">Docker Hub repository</a> for more information.</p>

<h3 id="maven">Maven</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.arcadedb<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>arcadedb-engine<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>26.7.3<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>All artifacts are available on <a href="https://repo.maven.apache.org/maven2/com/arcadedb/">Maven Central</a>.</p>

<h3 id="documentation">Documentation</h3>

<p>For details on features and usage, see the <a href="https://docs.arcadedb.com/">documentation</a>.</p>

<h2 id="compatibility-note">Compatibility Note</h2>

<p>This release contains no breaking changes and no schema migration. It maintains 100% compatibility with previous database formats, meaning no export/import is required when upgrading. As always, we recommend creating a database backup before upgrading.</p>

<hr />

<p><strong>Download ArcadeDB 26.7.3 now</strong>: <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.7.3">GitHub Releases</a></p>

<p>Thanks to everyone in the community who reported issues, opened PRs, and helped shape this release.</p>

<p>Luca Garulli
ArcadeDB Founder</p>]]></content><author><name>Luca Garulli</name></author><category term="Multi-Model" /><category term="Security" /><category term="MCP" /><category term="Graph Database" /><category term="Release" /><summary type="html"><![CDATA[ArcadeDB 26.7.3 is a focused hotfix on top of 26.7.2: it closes three security advisories (two in the MCP server transport, one in the JavaScript/Java trigger authorization gate) and repairs a data-loss regression in the commutative super-node edge-append merge.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/release-v26.7.3.jpg" /><media:content medium="image" url="https://arcadedb.com/assets/images/release-v26.7.3.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Certified, Not Claimed: ArcadeDB’s Bolt Compatibility With Every Official Neo4j Driver</title><link href="https://arcadedb.com/blog/bolt-driver-compatibility-certification/" rel="alternate" type="text/html" title="Certified, Not Claimed: ArcadeDB’s Bolt Compatibility With Every Official Neo4j Driver" /><published>2026-07-14T00:00:00+00:00</published><updated>2026-07-14T00:00:00+00:00</updated><id>https://arcadedb.com/blog/bolt-driver-compatibility-certification</id><content type="html" xml:base="https://arcadedb.com/blog/bolt-driver-compatibility-certification/"><![CDATA[<p>Plenty of databases say they “support the Bolt protocol.” Almost none of them tell you what that sentence leaves out.</p>

<p>ArcadeDB has spoken Bolt for a while, and the honest version of our claim was narrow: the official Neo4j drivers connect, and simple queries work. Useful, but not the same thing as compatibility. The gap between the two is where users get hurt. The driver connects on Monday; on Thursday someone reads a <code class="language-plaintext highlighter-rouge">datetime</code> back out of a query and gets a string.</p>

<p><a href="/blog/arcadedb-26-7-2/">ArcadeDB 26.7.2</a> closes that gap. It ships a shared conformance spec, all five official drivers under test, fixes for the protocol bugs that testing exposed, and a compatibility matrix that regenerates itself every night and is published where anyone can read it.</p>

<h2 id="the-audit">The audit</h2>

<p>We started by auditing what we actually had (<a href="https://github.com/ArcadeData/arcadedb/issues/4882">epic #4882</a>). Some of it was reassuring. The full Bolt 3.0/4.0/4.4 message set was implemented, <code class="language-plaintext highlighter-rouge">ROUTE</code> included, along with a hand-written PackStream encoder, correct structure tags for Node, Relationship, and Path, TLS via <code class="language-plaintext highlighter-rouge">bolt+s</code>, and Neo4j-style structured error codes.</p>

<p>The rest was not:</p>

<ul>
  <li><strong>Bolt 5.x was never advertised.</strong> The server negotiated 3.0, 4.0, and 4.4. Modern 5.x drivers worked only by silently downgrading, which nobody had documented or tested as a deliberate stance.</li>
  <li><strong>Temporal values went out as strings.</strong> <code class="language-plaintext highlighter-rouge">Date</code>, <code class="language-plaintext highlighter-rouge">Time</code>, <code class="language-plaintext highlighter-rouge">LocalDateTime</code>, <code class="language-plaintext highlighter-rouge">DateTime</code> and friends were serialized as ISO-8601 text instead of native Bolt structures. <code class="language-plaintext highlighter-rouge">Duration</code> and spatial <code class="language-plaintext highlighter-rouge">Point</code> had no handling anywhere. The driver dutifully handed your application a <code class="language-plaintext highlighter-rouge">String</code>.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Neo.TransientError.*</code> did not exist.</strong> Only a handful of <code class="language-plaintext highlighter-rouge">ClientError</code> and <code class="language-plaintext highlighter-rouge">DatabaseError</code> codes were defined, so the drivers’ managed-transaction retry logic could never fire the way it does against Neo4j.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ROUTE</code> was single-node only.</strong> It returned this node’s own address as writer, reader, and router, so <code class="language-plaintext highlighter-rouge">neo4j://</code> routing against a real HA cluster was unproven.</li>
  <li><strong>Three of the five official drivers had zero Bolt coverage.</strong> The Python and C# e2e suites tested the Postgres wire protocol and HTTP. There was no Go module at all.</li>
  <li><strong>Java and JavaScript coverage was shallow.</strong> Connect, run a query, one regression test. No transactions, no error paths, no type round-trips.</li>
</ul>

<p>A compatibility claim you cannot fail is not a compatibility claim. That list is why the epic existed.</p>

<h2 id="the-rules-we-set">The rules we set</h2>

<p><strong>Certify depth, not presence.</strong> “The driver connects” is not certification. Every driver runs the full feature matrix, and every unsupported cell becomes a documented limitation instead of a silent omission.</p>

<p><strong>Only the real drivers.</strong> <code class="language-plaintext highlighter-rouge">neo4j-java-driver</code>, <code class="language-plaintext highlighter-rouge">neo4j-driver</code>, <code class="language-plaintext highlighter-rouge">neo4j</code>, <code class="language-plaintext highlighter-rouge">Neo4j.Driver</code>, <code class="language-plaintext highlighter-rouge">neo4j-go-driver</code>. No mocks, no bespoke socket clients. If the driver your application imports cannot do it, we do not get to claim it.</p>

<p><strong>One spec, five idiomatic suites.</strong> We deliberately did not build a YAML-driven test runner in five languages. The <a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/conformance/spec.yaml">conformance spec</a> is a reference document. Each scenario is hand-written into that language’s native framework (JUnit, jest, pytest, xUnit, <code class="language-plaintext highlighter-rouge">go test</code>) and tagged with the scenario ID. The spec owns <em>what</em> gets tested; the code stays polyglot and readable.</p>

<p><strong>“Not supported” is an acceptable answer, as long as it is written down.</strong> Byte-for-byte parity with Neo4j server behavior was never the goal. Knowing exactly where we differ was worth more.</p>

<h2 id="the-spec-39-scenarios-9-areas">The spec: 39 scenarios, 9 areas</h2>

<p>The matrix is defined once, in <code class="language-plaintext highlighter-rouge">bolt/conformance/spec.yaml</code>, across nine areas taken verbatim from the epic’s feature table:</p>

<table>
  <thead>
    <tr>
      <th>Area</th>
      <th>What it pins down</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">connection</code></td>
      <td><code class="language-plaintext highlighter-rouge">bolt://</code>, <code class="language-plaintext highlighter-rouge">bolt+s://</code> with TLS required and optional, <code class="language-plaintext highlighter-rouge">neo4j://</code> routing discovery</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">auth</code></td>
      <td>Basic auth success and failure; the <code class="language-plaintext highlighter-rouge">none</code> scheme being rejected (intentional, now certified as such)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">transactions</code></td>
      <td>Autocommit, explicit BEGIN/COMMIT/ROLLBACK, managed transaction functions, retry on transient errors</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">causal-consistency</code></td>
      <td>Bookmarks enforcing read-after-write across sessions</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">multi-database</code></td>
      <td>Session database selection and isolation between databases on one driver</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">result-handling</code></td>
      <td>Streaming <code class="language-plaintext highlighter-rouge">PULL</code>, <code class="language-plaintext highlighter-rouge">PULL n</code> resumption, <code class="language-plaintext highlighter-rouge">DISCARD</code>, <code class="language-plaintext highlighter-rouge">ResultSummary</code> counters</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">type-roundtrip</code></td>
      <td>Node, Relationship, Path, ByteArray, nested collections, nulls, all five temporal types, Duration, Point</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">errors</code></td>
      <td><code class="language-plaintext highlighter-rouge">Neo.ClientError.*</code> and <code class="language-plaintext highlighter-rouge">Neo.TransientError.*</code> so driver retry behavior matches Neo4j</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">protocol</code></td>
      <td>3.0/4.0/4.4 negotiation, 5.x negotiation, <code class="language-plaintext highlighter-rouge">RESET</code> mid-stream</td>
    </tr>
  </tbody>
</table>

<p>Each scenario carries a stable ID (<code class="language-plaintext highlighter-rouge">TYPE-011</code>, <code class="language-plaintext highlighter-rouge">PROTO-002</code>), a fixture, given/when/then steps, and a status. Every test in every language embeds its scenario ID in the test name, so checking coverage is a grep.</p>

<h2 id="the-drivers-14-pinned-versions">The drivers: 14 pinned versions</h2>

<p>Testing against <code class="language-plaintext highlighter-rouge">latest</code> tells you about today and nothing about tomorrow. Every language is pinned to a band set, resolved to concrete versions in <a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/conformance/driver-versions.md"><code class="language-plaintext highlighter-rouge">driver-versions.md</code></a>:</p>

<table>
  <thead>
    <tr>
      <th>Driver</th>
      <th>Versions under test</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Java (<code class="language-plaintext highlighter-rouge">neo4j-java-driver</code>)</td>
      <td>4.4.20, 5.28.5, 6.2.0</td>
    </tr>
    <tr>
      <td>JavaScript (<code class="language-plaintext highlighter-rouge">neo4j-driver</code>)</td>
      <td>5.28.3, 6.2.0</td>
    </tr>
    <tr>
      <td>Python (<code class="language-plaintext highlighter-rouge">neo4j</code>)</td>
      <td>5.28.4, 6.1.0, 6.2.0</td>
    </tr>
    <tr>
      <td>.NET (<code class="language-plaintext highlighter-rouge">Neo4j.Driver</code>)</td>
      <td>5.26.2, 5.28.4, 6.2.1</td>
    </tr>
    <tr>
      <td>Go (<code class="language-plaintext highlighter-rouge">neo4j-go-driver</code>)</td>
      <td>5.27.0, 5.28.0, 5.28.4</td>
    </tr>
  </tbody>
</table>

<p>The <code class="language-plaintext highlighter-rouge">latest</code> band deliberately tracks the newest release, so a driver-side release that breaks compatibility trips our nightly run within a day of shipping.</p>

<p>Two of these bands carry compromises. The C# floor is 5.26.2 instead of a 4.x line, because <code class="language-plaintext highlighter-rouge">Neo4j.Driver</code> made breaking API changes in 5.0 that the shared suite cannot compile against. JavaScript has no 4.x band for the same reason. The 4.4 <em>wire protocol</em> is still covered through the Java 4.4.20 legacy driver. Both compromises are written down in the file, with the reasoning, so nobody has to reverse-engineer why a column is missing.</p>

<h2 id="what-the-tests-broke">What the tests broke</h2>

<p>The first full run lit up red, which was more or less the point. Everything from the audit list is now fixed in 26.7.2:</p>

<ul>
  <li><strong>Native temporal structures.</strong> <code class="language-plaintext highlighter-rouge">Date</code>, <code class="language-plaintext highlighter-rouge">Time</code>, <code class="language-plaintext highlighter-rouge">LocalTime</code>, <code class="language-plaintext highlighter-rouge">LocalDateTime</code>, and <code class="language-plaintext highlighter-rouge">DateTime</code> are emitted as native PackStream structures.</li>
  <li><strong>Duration and Point.</strong> Both round-trip as native Bolt structures in both directions, bound parameters included.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Neo.TransientError.*</code>.</strong> Retryable conflicts now map to transient error codes, so the drivers’ <code class="language-plaintext highlighter-rouge">executeWrite</code> retry loop works out of the box instead of failing your write on the first optimistic-lock conflict.</li>
  <li><strong>HA-aware <code class="language-plaintext highlighter-rouge">ROUTE</code>.</strong> <code class="language-plaintext highlighter-rouge">neo4j://</code> returns the real cluster topology instead of the local node three times.</li>
  <li><strong>Bolt 5.x negotiation.</strong> No more silent downgrade.</li>
  <li><strong>Populated write counters.</strong> <code class="language-plaintext highlighter-rouge">ResultSummary</code> counters reflect what actually happened.</li>
</ul>

<h3 id="one-breaking-change-to-plan-for">One breaking change to plan for</h3>

<p>The temporal fix changes behavior, and it is the one thing to read before you upgrade. Values that used to arrive as strings now arrive as typed objects:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Before 26.7.2: this returned an ISO-8601 String</span>
<span class="nc">String</span> <span class="n">when</span> <span class="o">=</span> <span class="kd">record</span><span class="err">.</span><span class="nc">get</span><span class="o">(</span><span class="s">"created"</span><span class="o">).</span><span class="na">asString</span><span class="o">();</span>

<span class="c1">// 26.7.2 and later: it is a real temporal value</span>
<span class="nc">ZonedDateTime</span> <span class="n">when</span> <span class="o">=</span> <span class="kd">record</span><span class="err">.</span><span class="nc">get</span><span class="o">(</span><span class="s">"created"</span><span class="o">).</span><span class="na">asZonedDateTime</span><span class="o">();</span>
</code></pre></div></div>

<p>This is what every Neo4j driver expects, and it is still a change, so the <a href="/blog/arcadedb-26-7-2/">26.7.2 release notes</a> call it out explicitly.</p>

<h2 id="the-part-that-keeps-it-true">The part that keeps it true</h2>

<p>A test suite that runs when someone remembers to run it eventually stops being true. The pipeline matters more than any single bug on that list.</p>

<p>All five language suites gate every pull request. On top of that, a nightly workflow runs the full cross-product: every scenario, every driver, every pinned version. Each language’s JUnit-style report becomes a set of per-cell results, the cells merge into a single <code class="language-plaintext highlighter-rouge">bolt-compat-matrix.json</code>, and that JSON is cross-referenced against the expected cell set from <code class="language-plaintext highlighter-rouge">driver-versions.md</code>. A cell that should exist and does not is treated exactly like a cell that ran and failed. Silence does not pass.</p>

<p>Three things then happen without anyone touching them:</p>

<ol>
  <li><strong>A regression issue opens itself.</strong> Any red cell auto-opens a <code class="language-plaintext highlighter-rouge">bolt-compat-regression</code> issue, which closes itself when the matrix goes green again.</li>
  <li><strong>The matrix is rendered and committed.</strong> A small renderer turns the nightly JSON plus the spec metadata into <a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/conformance/COMPATIBILITY.md"><code class="language-plaintext highlighter-rouge">COMPATIBILITY.md</code></a>. Rows are scenarios grouped by area, columns are <code class="language-plaintext highlighter-rouge">language:version</code>, and cells are pass, fail, known limitation, not applicable, or not reported. Non-passing cells link to their tracking issue. The page carries a “last verified” timestamp and a link to the CI run that produced it.</li>
  <li><strong>The badge updates.</strong> A shields.io endpoint in the README goes green only when every applicable cell passes.</li>
</ol>

<p>The red nights get published the same way the green ones do. A matrix that could only ever come out green would not be worth publishing.</p>

<h2 id="what-is-not-green">What is not green</h2>

<p>Every applicable cell passes today. Two cells are not green, both deliberately:</p>

<ul>
  <li><strong>ERR-003</strong> (unauthenticated request returns <code class="language-plaintext highlighter-rouge">Neo.ClientError.Security.Forbidden</code>) is marked <strong>not applicable</strong>. It cannot be triggered through any official driver’s public API, because every official driver completes HELLO/LOGON internally before it hands your code a session. Reaching it would take a bespoke raw-socket client, which our own rules exclude. The row stays in the matrix with the explanation attached.</li>
  <li><strong>CONN-004</strong> (<code class="language-plaintext highlighter-rouge">neo4j://</code> routing reflecting a real multi-node topology) is <strong>skipped</strong>, because the nightly runs against a single node. The HA-aware ROUTE implementation shipped in 26.7.2; the multi-node nightly harness for it did not. The cell carries a footnote about the <code class="language-plaintext highlighter-rouge">HA_SERVER_LIST</code> configuration it depends on, and it stays visible until something actually covers it.</li>
</ul>

<p>The Java 4.4.20 column is sparse on purpose. It runs the legacy-driver subset that proves 4.4 wire negotiation still works, not the full modern-API suite.</p>

<h2 id="try-it">Try it</h2>

<p>Point any official Neo4j driver at an ArcadeDB server. Nothing special is required. Here is the same query in all five certified drivers:</p>

<div class="code-tabs">
  <div class="code-tabs-nav" role="tablist" aria-label="Driver language">
    <button class="code-tab active" role="tab" data-lang="java" aria-selected="true" tabindex="0">Java</button>
    <button class="code-tab" role="tab" data-lang="javascript" aria-selected="false" tabindex="-1">JavaScript</button>
    <button class="code-tab" role="tab" data-lang="python" aria-selected="false" tabindex="-1">Python</button>
    <button class="code-tab" role="tab" data-lang="csharp" aria-selected="false" tabindex="-1">C#</button>
    <button class="code-tab" role="tab" data-lang="go" aria-selected="false" tabindex="-1">Go</button>
  </div>

<div class="code-tab-pane active" role="tabpanel" data-lang="java">

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="o">(</span><span class="nc">Driver</span> <span class="n">driver</span> <span class="o">=</span> <span class="nc">GraphDatabase</span><span class="o">.</span><span class="na">driver</span><span class="o">(</span><span class="s">"bolt://localhost:7687"</span><span class="o">,</span>
         <span class="nc">AuthTokens</span><span class="o">.</span><span class="na">basic</span><span class="o">(</span><span class="s">"root"</span><span class="o">,</span> <span class="s">"playwithdata"</span><span class="o">));</span>
     <span class="nc">Session</span> <span class="n">session</span> <span class="o">=</span> <span class="n">driver</span><span class="o">.</span><span class="na">session</span><span class="o">(</span><span class="nc">SessionConfig</span><span class="o">.</span><span class="na">forDatabase</span><span class="o">(</span><span class="s">"beer"</span><span class="o">)))</span> <span class="o">{</span>

  <span class="n">session</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="s">"MATCH (b:Beer)-[:HasCategory]-&gt;(c:Category) "</span>
            <span class="o">+</span> <span class="s">"WHERE c.name = $cat RETURN b.name AS name LIMIT 5"</span><span class="o">,</span>
      <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"cat"</span><span class="o">,</span> <span class="s">"Irish Ale"</span><span class="o">))</span>
      <span class="o">.</span><span class="na">forEachRemaining</span><span class="o">(</span><span class="n">r</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"name"</span><span class="o">).</span><span class="na">asString</span><span class="o">()));</span>
<span class="o">}</span>
</code></pre></div>    </div>

  </div>

<div class="code-tab-pane" role="tabpanel" data-lang="javascript">

    <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">neo4j</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">neo4j-driver</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">driver</span> <span class="o">=</span> <span class="nx">neo4j</span><span class="p">.</span><span class="nf">driver</span><span class="p">(</span><span class="dl">'</span><span class="s1">bolt://localhost:7687</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">neo4j</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nf">basic</span><span class="p">(</span><span class="dl">'</span><span class="s1">root</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">playwithdata</span><span class="dl">'</span><span class="p">));</span>
<span class="kd">const</span> <span class="nx">session</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nf">session</span><span class="p">({</span> <span class="na">database</span><span class="p">:</span> <span class="dl">'</span><span class="s1">beer</span><span class="dl">'</span> <span class="p">});</span>

<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">session</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span>
  <span class="dl">'</span><span class="s1">MATCH (b:Beer)-[:HasCategory]-&gt;(c:Category) </span><span class="dl">'</span> <span class="o">+</span>
  <span class="dl">'</span><span class="s1">WHERE c.name = $cat RETURN b.name AS name LIMIT 5</span><span class="dl">'</span><span class="p">,</span>
  <span class="p">{</span> <span class="na">cat</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Irish Ale</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">);</span>
<span class="nx">result</span><span class="p">.</span><span class="nx">records</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">)));</span>

<span class="k">await</span> <span class="nx">session</span><span class="p">.</span><span class="nf">close</span><span class="p">();</span>
<span class="k">await</span> <span class="nx">driver</span><span class="p">.</span><span class="nf">close</span><span class="p">();</span>
</code></pre></div>    </div>

  </div>

<div class="code-tab-pane" role="tabpanel" data-lang="python">

    <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">neo4j</span> <span class="kn">import</span> <span class="n">GraphDatabase</span>

<span class="n">driver</span> <span class="o">=</span> <span class="n">GraphDatabase</span><span class="p">.</span><span class="nf">driver</span><span class="p">(</span><span class="sh">"</span><span class="s">bolt://localhost:7687</span><span class="sh">"</span><span class="p">,</span>
                              <span class="n">auth</span><span class="o">=</span><span class="p">(</span><span class="sh">"</span><span class="s">root</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">playwithdata</span><span class="sh">"</span><span class="p">))</span>

<span class="k">with</span> <span class="n">driver</span><span class="p">.</span><span class="nf">session</span><span class="p">(</span><span class="n">database</span><span class="o">=</span><span class="sh">"</span><span class="s">beer</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">session</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span>
        <span class="sh">"</span><span class="s">MATCH (b:Beer)-[:HasCategory]-&gt;(c:Category) </span><span class="sh">"</span>
        <span class="sh">"</span><span class="s">WHERE c.name = $cat RETURN b.name AS name LIMIT 5</span><span class="sh">"</span><span class="p">,</span>
        <span class="n">cat</span><span class="o">=</span><span class="sh">"</span><span class="s">Irish Ale</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">record</span><span class="p">[</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">])</span>
</code></pre></div>    </div>

  </div>

<div class="code-tab-pane" role="tabpanel" data-lang="csharp">

    <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">driver</span> <span class="p">=</span> <span class="n">GraphDatabase</span><span class="p">.</span><span class="nf">Driver</span><span class="p">(</span><span class="s">"bolt://localhost:7687"</span><span class="p">,</span>
    <span class="n">AuthTokens</span><span class="p">.</span><span class="nf">Basic</span><span class="p">(</span><span class="s">"root"</span><span class="p">,</span> <span class="s">"playwithdata"</span><span class="p">));</span>
<span class="k">await</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">session</span> <span class="p">=</span> <span class="n">driver</span><span class="p">.</span><span class="nf">AsyncSession</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="nf">WithDatabase</span><span class="p">(</span><span class="s">"beer"</span><span class="p">));</span>

<span class="kt">var</span> <span class="n">names</span> <span class="p">=</span> <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="nf">ExecuteReadAsync</span><span class="p">(</span><span class="k">async</span> <span class="n">tx</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="kt">var</span> <span class="n">cursor</span> <span class="p">=</span> <span class="k">await</span> <span class="n">tx</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span>
        <span class="s">"MATCH (b:Beer)-[:HasCategory]-&gt;(c:Category) "</span> <span class="p">+</span>
        <span class="s">"WHERE c.name = $cat RETURN b.name AS name LIMIT 5"</span><span class="p">,</span>
        <span class="k">new</span> <span class="p">{</span> <span class="n">cat</span> <span class="p">=</span> <span class="s">"Irish Ale"</span> <span class="p">});</span>
    <span class="k">return</span> <span class="k">await</span> <span class="n">cursor</span><span class="p">.</span><span class="nf">ToListAsync</span><span class="p">(</span><span class="n">r</span> <span class="p">=&gt;</span> <span class="n">r</span><span class="p">[</span><span class="s">"name"</span><span class="p">].</span><span class="n">As</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;());</span>
<span class="p">});</span>

<span class="n">names</span><span class="p">.</span><span class="nf">ForEach</span><span class="p">(</span><span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">);</span>
</code></pre></div>    </div>

  </div>

<div class="code-tab-pane" role="tabpanel" data-lang="go">

    <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ctx</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">()</span>

<span class="n">driver</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">neo4j</span><span class="o">.</span><span class="n">NewDriverWithContext</span><span class="p">(</span><span class="s">"bolt://localhost:7687"</span><span class="p">,</span>
    <span class="n">neo4j</span><span class="o">.</span><span class="n">BasicAuth</span><span class="p">(</span><span class="s">"root"</span><span class="p">,</span> <span class="s">"playwithdata"</span><span class="p">,</span> <span class="s">""</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
    <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">driver</span><span class="o">.</span><span class="n">Close</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>

<span class="n">session</span> <span class="o">:=</span> <span class="n">driver</span><span class="o">.</span><span class="n">NewSession</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">neo4j</span><span class="o">.</span><span class="n">SessionConfig</span><span class="p">{</span><span class="n">DatabaseName</span><span class="o">:</span> <span class="s">"beer"</span><span class="p">})</span>
<span class="k">defer</span> <span class="n">session</span><span class="o">.</span><span class="n">Close</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>

<span class="n">result</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">session</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span>
    <span class="s">"MATCH (b:Beer)-[:HasCategory]-&gt;(c:Category) "</span><span class="o">+</span>
        <span class="s">"WHERE c.name = $cat RETURN b.name AS name LIMIT 5"</span><span class="p">,</span>
    <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="n">any</span><span class="p">{</span><span class="s">"cat"</span><span class="o">:</span> <span class="s">"Irish Ale"</span><span class="p">})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
    <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">for</span> <span class="n">result</span><span class="o">.</span><span class="n">Next</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">Record</span><span class="p">()</span><span class="o">.</span><span class="n">Values</span><span class="p">[</span><span class="m">0</span><span class="p">])</span>
<span class="p">}</span>
</code></pre></div>    </div>

  </div>
</div>

<p>Managed transactions, bookmarks, <code class="language-plaintext highlighter-rouge">neo4j://</code> routing, and typed temporal and spatial values behave the way the driver documentation says they should. Where they do not, the matrix says so.</p>

<h2 id="why-we-bothered">Why we bothered</h2>

<p>A large part of the graph world evaluates a database by pointing an existing driver at it, running existing Cypher, and seeing what breaks. That deserves a real answer instead of a compatibility adjective.</p>

<p>It is the same instinct behind <a href="/blog/arcadedb-jepsen-tests-34-pass/">our Jepsen work</a>: publish the harness, publish the results, publish the parts that do not pass. All of it is open:</p>

<ul>
  <li><a href="https://github.com/ArcadeData/arcadedb/issues/4882">Epic #4882: Bolt Driver Compatibility Certification</a></li>
  <li><a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/conformance/COMPATIBILITY.md">The live compatibility matrix</a></li>
  <li><a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/conformance/spec.yaml">The conformance spec</a> and <a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/conformance/README.md">how it is consumed</a></li>
  <li><a href="https://github.com/ArcadeData/arcadedb/blob/main/bolt/SERVER_IDENTITY.md">The advertised server identity and why it says <code class="language-plaintext highlighter-rouge">Neo4j/5.26.0 compatible</code></a></li>
</ul>

<hr />

<p><strong>Get ArcadeDB 26.7.2</strong>: <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.7.2">GitHub Releases</a> or <code class="language-plaintext highlighter-rouge">docker pull arcadedata/arcadedb:26.7.2</code></p>

<p>Run a Bolt workload against ArcadeDB and hit a case the matrix does not cover? <a href="https://github.com/ArcadeData/arcadedb/issues">Open an issue</a>. A scenario we never thought to write is the most useful bug report we can get.</p>

<p>Roberto Franchini
Director R&amp;D</p>]]></content><author><name>Roberto Franchini</name></author><category term="Bolt" /><category term="Neo4j" /><category term="Graph Database" /><category term="Cypher" /><category term="Drivers" /><category term="Testing" /><category term="CI" /><category term="Compatibility" /><category term="ArcadeDB" /><summary type="html"><![CDATA[ArcadeDB 26.7.2 ships full Bolt driver certification: 39 conformance scenarios run nightly against the Java, JavaScript, Python, .NET, and Go Neo4j drivers across 14 pinned versions, published as a live compatibility matrix and a status badge.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/bolt-protocol-fidelity.png" /><media:content medium="image" url="https://arcadedb.com/assets/images/bolt-protocol-fidelity.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ArcadeDB 26.7.2: Deep Engine Audit, Neo4j Bolt Certification &amp;amp; 5 Critical Security Fixes</title><link href="https://arcadedb.com/blog/arcadedb-26-7-2/" rel="alternate" type="text/html" title="ArcadeDB 26.7.2: Deep Engine Audit, Neo4j Bolt Certification &amp;amp; 5 Critical Security Fixes" /><published>2026-07-09T00:00:00+00:00</published><updated>2026-07-09T00:00:00+00:00</updated><id>https://arcadedb.com/blog/arcadedb-26-7-2</id><content type="html" xml:base="https://arcadedb.com/blog/arcadedb-26-7-2/"><![CDATA[<p>We’re pleased to announce <strong>ArcadeDB 26.7.2</strong>, a stability and correctness release that resolves <strong>over 130 issues</strong>. Where <a href="https://arcadedb.com/blog/arcadedb-26-7-1/">26.7.1</a> focused on Raft resilience, OpenTelemetry tracing, and standards alignment, 26.7.2 turns the spotlight on the <strong>deep internals</strong>: a full engine audit across storage, WAL recovery, transactions, LSM indexes, and concurrency. On top of that it completes <strong>Neo4j Bolt driver certification</strong>, hardens High Availability further, and ships <strong>five critical security fixes</strong>.</p>

<h2 id="major-highlights">Major Highlights</h2>

<h3 id="deep-engine-audit">Deep Engine Audit</h3>

<p>The heart of this release is a systematic audit of the storage and execution engine. These are the guarantees you rely on every commit, tightened across the board:</p>

<ul>
  <li><strong>Storage &amp; recovery:</strong> WAL append is now the transaction’s point of no return, with pre-write validation. Torn 64KB page writes are repaired during recovery, and the page cache prevents both lost updates and unbounded growth.</li>
  <li><strong>LSM index stability:</strong> Compaction no longer loses re-inserted keys or leaks orphaned pages, range scans are no longer truncated by tombstoned entries, and non-unique lookups no longer resurrect deleted records.</li>
  <li><strong>Transaction safety:</strong> Double-indexed updates within a single transaction no longer corrupt indexes, dead-thread cleanup rolls back abandoned transactions properly, and virtual-thread compatibility is restored.</li>
  <li><strong>Parallel query safety:</strong> A dedicated thread pool for bucket scans prevents deadlocks, each worker gets an isolated command context, and native iterators bound producer offers and close cleanly.</li>
</ul>

<p>These are the fixes you feel under load and during the unhappy path, not in a benchmark.</p>

<h3 id="neo4j-bolt-driver-certification">Neo4j Bolt Driver Certification</h3>

<p>ArcadeDB now achieves <strong>full compatibility certification</strong> with the Neo4j Bolt driver:</p>

<ul>
  <li>Native temporal PackStream structures: <code class="language-plaintext highlighter-rouge">date</code>, <code class="language-plaintext highlighter-rouge">time</code>, <code class="language-plaintext highlighter-rouge">datetime</code>, and <code class="language-plaintext highlighter-rouge">localdatetime</code>.</li>
  <li>Native <strong>Path</strong>, <strong>Duration</strong>, and <strong>Point</strong> types.</li>
  <li><strong>Retryable conflict mapping</strong> so the driver’s auto-retry works out of the box.</li>
  <li><strong>HA-aware ROUTE</strong> responses and <strong>Bolt 5.x</strong> negotiation support.</li>
  <li>Populated write-result counters.</li>
</ul>

<h3 id="security-hardening-5-critical-fixes">Security Hardening: 5 Critical Fixes</h3>

<p>This release patches five critical vulnerabilities. Upgrading is strongly recommended for internet-facing and multi-tenant deployments:</p>

<ul>
  <li><strong>RCE via JavaScript triggers:</strong> <code class="language-plaintext highlighter-rouge">java.lang.*</code> classes are removed from trigger allow-lists, restricting to benign packages only.</li>
  <li><strong>Arbitrary JavaScript execution:</strong> defining <code class="language-plaintext highlighter-rouge">js</code> functions now requires <code class="language-plaintext highlighter-rouge">UPDATE_SECURITY</code> with restricted polyglot access.</li>
  <li><strong>Secret disclosure via API:</strong> server settings now redact all hidden configuration keys, including cluster tokens.</li>
  <li><strong>Cross-database IDOR:</strong> authorization checks added to 14 HTTP handlers to prevent unauthorized database access.</li>
  <li><strong>Read-only mutation of schema:</strong> missing <code class="language-plaintext highlighter-rouge">UPDATE_SCHEMA</code> guards added to schema and config mutators.</li>
</ul>

<h3 id="high-availability-raft-enhancements">High Availability (Raft) Enhancements</h3>

<ul>
  <li><strong>Durable Raft storage now defaults to <code class="language-plaintext highlighter-rouge">true</code></strong>, preventing follower divergence.</li>
  <li>The leader <strong>auto-recovers wedged replication channels</strong>.</li>
  <li>A new <code class="language-plaintext highlighter-rouge">TransactionCommittedRemotelyException</code> (HTTP 409) prevents duplicate retries.</li>
  <li>Offline bootstrap leadership-transfer now commits a baseline.</li>
</ul>

<h2 id="major-fixes">Major Fixes</h2>

<h3 id="query-engine-opencypher">Query Engine: OpenCypher</h3>

<ul>
  <li>Dynamic property mutations (<code class="language-plaintext highlighter-rouge">SET n[key] = value</code>) are now applied.</li>
  <li>Inline relationship filters are enforced in <code class="language-plaintext highlighter-rouge">MATCH</code>, <code class="language-plaintext highlighter-rouge">exists()</code>, comprehensions, and shortest-path queries.</li>
  <li><code class="language-plaintext highlighter-rouge">FOREACH</code> updates are visible to subsequent <code class="language-plaintext highlighter-rouge">RETURN</code> clauses.</li>
  <li>Pattern comprehension and correlated subquery corrections.</li>
  <li>Temporal normalization is applied in index scans.</li>
</ul>

<h3 id="query-engine-sql">Query Engine: SQL</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">FROM</code> is now usable as a property name in DDL and queries.</li>
  <li><code class="language-plaintext highlighter-rouge">ORDER BY</code> on non-indexed properties returns correct results.</li>
  <li><code class="language-plaintext highlighter-rouge">TRAVERSE</code> with <code class="language-plaintext highlighter-rouge">MAXDEPTH</code> corrected; <code class="language-plaintext highlighter-rouge">BREADTH_FIRST</code> now performs true breadth-first traversal.</li>
  <li>Map indexing and array serialization fixes.</li>
</ul>

<h3 id="additional-features">Additional Features</h3>

<ul>
  <li>User-defined functions are now persisted and distributed across HA clusters.</li>
  <li>Configurable vector quantization for sparse vector indexes.</li>
  <li>Commutative append-merge removes retry storms on high-degree vertices.</li>
  <li>Extended Cypher write-counter surfacing over HTTP and gRPC.</li>
  <li>Point datatype and spatial index support.</li>
  <li>Python binding performance improvements.</li>
</ul>

<h3 id="dependencies">Dependencies</h3>

<p>Notable upgrades include Netty 4.2.16, Jackson 2.22.1, PostgreSQL JDBC 42.7.13, GraalVM 25.1.3, and Neo4j Java driver 6.2.0, plus the usual round of updates.</p>

<h2 id="breaking-changes">Breaking Changes</h2>

<p>Two behavioral changes to note when upgrading:</p>

<ol>
  <li><strong>Raft storage durability:</strong> <code class="language-plaintext highlighter-rouge">arcadedb.ha.raftPersistStorage</code> now defaults to <code class="language-plaintext highlighter-rouge">true</code>. Ensure the storage directory resides on durable media. Test clusters can opt out explicitly.</li>
  <li><strong>Bolt temporal types:</strong> temporal values are now transmitted as native PackStream structures instead of ISO-8601 strings. Clients must read native types (e.g., <code class="language-plaintext highlighter-rouge">asZonedDateTime()</code>) rather than strings.</li>
</ol>

<h2 id="getting-started-with-2672">Getting Started with 26.7.2</h2>

<h3 id="docker">Docker</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull arcadedata/arcadedb:26.7.2
</code></pre></div></div>

<p>Visit our <a href="https://hub.docker.com/r/arcadedata/arcadedb">Docker Hub repository</a> for more information.</p>

<h3 id="maven">Maven</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.arcadedb<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>arcadedb-engine<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>26.7.2<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>All artifacts are available on <a href="https://repo.maven.apache.org/maven2/com/arcadedb/">Maven Central</a>.</p>

<h3 id="documentation">Documentation</h3>

<p>For detailed information on features and usage, refer to our <a href="https://docs.arcadedb.com/">comprehensive documentation</a>.</p>

<h2 id="compatibility-note">Compatibility Note</h2>

<p>This release maintains 100% compatibility with previous database formats, meaning no export/import is required when upgrading. As always, we recommend creating a database backup before upgrading.</p>

<hr />

<p><strong>Download ArcadeDB 26.7.2 now</strong>: <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.7.2">GitHub Releases</a></p>

<p>Thanks to everyone in the community who reported issues, opened PRs, and helped shape this release.</p>

<p>Luca Garulli
ArcadeDB Founder</p>]]></content><author><name>Luca Garulli</name></author><category term="Multi-Model" /><category term="High Availability" /><category term="Security" /><category term="Neo4j" /><category term="Graph Database" /><category term="Release" /><summary type="html"><![CDATA[ArcadeDB 26.7.2 is a stability and correctness release with 130+ resolved issues: a deep engine audit across storage, WAL recovery, transactions, LSM indexes and concurrency, full Neo4j Bolt driver certification, Raft HA hardening, and 5 critical security fixes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/release-v26.7.2.jpg" /><media:content medium="image" url="https://arcadedb.com/assets/images/release-v26.7.2.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ArcadeDB Cloud Observability: OpenTelemetry Tracing, Structured Logging, and Kubernetes Health Probes</title><link href="https://arcadedb.com/blog/arcadedb-cloud-observability-opentelemetry-kubernetes/" rel="alternate" type="text/html" title="ArcadeDB Cloud Observability: OpenTelemetry Tracing, Structured Logging, and Kubernetes Health Probes" /><published>2026-07-07T00:00:00+00:00</published><updated>2026-07-07T00:00:00+00:00</updated><id>https://arcadedb.com/blog/arcadedb-cloud-observability-opentelemetry-kubernetes</id><content type="html" xml:base="https://arcadedb.com/blog/arcadedb-cloud-observability-opentelemetry-kubernetes/"><![CDATA[<p>A database in Kubernetes has to answer two questions the orchestrator keeps asking: are you alive, and are you ready? It also has to answer the one an on-call engineer asks at 2am, which is why a query that normally takes 8ms just took four seconds. ArcadeDB could answer none of them well. The <a href="https://github.com/ArcadeData/arcadedb/issues/4463">Cloud Observability Architecture</a> work fixes that with four pieces that ship independently: health probes, deeper metrics with OTLP export, structured logging with correlation IDs, and distributed tracing over OpenTelemetry.</p>

<p>All of it is opt-in. Touch no configuration key and your server behaves exactly as it does today.</p>

<h2 id="what-was-already-there-and-what-wasnt">What was already there, and what wasn’t</h2>

<p>Single-node observability was in decent shape: <a href="https://micrometer.io/">Micrometer</a> metrics with an in-memory registry, an optional <code class="language-plaintext highlighter-rouge">/prometheus</code> endpoint, JVM and executor-pool binders, an engine <code class="language-plaintext highlighter-rouge">Profiler</code> that tracks cache hit ratio, pages, WAL, transaction counts, and MVCC conflicts, a manual query profiler, and an <code class="language-plaintext highlighter-rouge">/api/v1/ready</code> endpoint.</p>

<p>The gaps only show up once you put that server in a cluster. Query, command, and transaction latency lived in the manual profiler, which means you had to go looking for it; there were no always-on RED (Rate, Errors, Duration) timers and no percentile histograms. There were no spans at all, so a request that crossed the query engine, a transaction, and a Raft replication hop was three disconnected mysteries. Logs were text-only, with nothing tying a line back to the request that produced it. And there was a readiness endpoint but no liveness check, which is the one Kubernetes reaches for first.</p>

<h2 id="instrument-once-get-two-signals">Instrument once, get two signals</h2>

<p>The part of this design I like most is the one that required the least new code. Micrometer’s Observation API lets you instrument a code path once and emit either a timer or a span from it, depending on what happens to be registered at runtime. ArcadeDB already shipped Micrometer, so the core server now wraps its hot paths (HTTP request handling, query and command execution, transaction commit, and Raft replication) in <code class="language-plaintext highlighter-rouge">Observation</code> calls and stops there.</p>

<p>With no tracer registered, an <code class="language-plaintext highlighter-rouge">Observation</code> is a metrics-only timer: what already happened, under a new name. Install the tracing plugin and those same call sites start producing spans as well. No second instrumentation pass, no parallel set of trace annotations drifting out of sync with the metric ones. If you have ever maintained a codebase where the metrics and the traces disagree about what a “query” is, you know why this matters.</p>

<h2 id="metrics-red-timers-and-otlp-export">Metrics: RED timers and OTLP export</h2>

<p>Three always-on timers, with percentile histograms and SLO buckets, exposed through <code class="language-plaintext highlighter-rouge">/prometheus</code> and optionally through OTLP:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">arcadedb.http.requests</code>, tagged by method, path template, status, and database. The path template is deliberate: tagging by raw URI is how you blow up your cardinality budget and get a call from whoever pays the metrics bill.</li>
  <li><code class="language-plaintext highlighter-rouge">arcadedb.query.duration</code>, tagged by language, database, and query type.</li>
  <li><code class="language-plaintext highlighter-rouge">arcadedb.tx.duration</code>, tagged by database and outcome (commit or rollback).</li>
</ul>

<p>A new <code class="language-plaintext highlighter-rouge">EngineMetricsBinder</code>, modeled on the existing <code class="language-plaintext highlighter-rouge">PoolMetrics</code>, pulls the engine <code class="language-plaintext highlighter-rouge">Profiler</code>’s cache hit ratio, page and WAL statistics, MVCC conflict counts, and the database-level and sparse-vector numbers into Micrometer gauges tagged by database. Set <code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.otlp.enabled=true</code> and the same series push to an OTLP collector. The Prometheus scrape path is not touched.</p>

<h2 id="tracing-in-a-module-you-can-leave-on-the-shelf">Tracing, in a module you can leave on the shelf</h2>

<p>Tracing lives in a separate, optional <code class="language-plaintext highlighter-rouge">tracing</code> module, packaged the same way the <code class="language-plaintext highlighter-rouge">metrics</code> module already is: its own Maven module, <code class="language-plaintext highlighter-rouge">provided</code> scope on the server, loaded through the <code class="language-plaintext highlighter-rouge">ServerPlugin</code> SPI. The OpenTelemetry SDK, <code class="language-plaintext highlighter-rouge">micrometer-tracing-bridge-otel</code>, and the OTLP exporter stay inside it. The core server’s compile classpath never sees them, which was a hard requirement: nobody should inherit the OTel dependency tree because they wanted a graph database.</p>

<p>Set <code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.tracing.enabled=true</code> and the plugin registers a bridged tracer into Micrometer’s global <code class="language-plaintext highlighter-rouge">ObservationRegistry</code>. The Observations described above start producing spans, nested from HTTP down through query execution and transaction commit. Inbound requests continue an upstream trace through the W3C <code class="language-plaintext highlighter-rouge">traceparent</code> header, and outbound Raft RPCs propagate context, so a write is traceable from leader to follower.</p>

<p>Leave the jar off the classpath or the flag unset and it is a genuine no-op. No span overhead, no registry, nothing.</p>

<h2 id="structured-logging-and-correlation-ids">Structured logging and correlation IDs</h2>

<p>A timer tells you the p99 moved. A span tells you which hop ate the time. Neither tells you about the <code class="language-plaintext highlighter-rouge">IOException</code> that got swallowed and retried, and that is usually the thing you actually needed. So the logs have to join the same conversation.</p>

<p>At the start of each request, ArcadeDB populates a diagnostic context with the active trace and span IDs, plus a generated request ID so correlation still works when tracing is off. The context is scoped per request and cleared in a <code class="language-plaintext highlighter-rouge">finally</code> block, because the server hands threads back to a worker pool and a leaked MDC entry means the next request inherits someone else’s trace ID. That bug is miserable to find. The <code class="language-plaintext highlighter-rouge">finally</code> is not decorative.</p>

<p>An opt-in <code class="language-plaintext highlighter-rouge">JsonLogFormatter</code> (<code class="language-plaintext highlighter-rouge">arcadedb.server.logFormat=json</code>) writes one JSON object per line: timestamp, level, logger, thread, message, trace ID, span ID, database, request ID, exception. It is built on ArcadeDB’s existing <code class="language-plaintext highlighter-rouge">JSONObject</code>, so it adds no JSON dependency. If you want correlation without moving to JSON, <code class="language-plaintext highlighter-rouge">arcadedb.server.logIncludeTrace=true</code> appends a <code class="language-plaintext highlighter-rouge">[traceId=...]</code> tag to the text format instead.</p>

<h2 id="health-probes-for-kubernetes">Health probes for Kubernetes</h2>

<p>This is the smallest change in the whole set and probably the one most people will use: <code class="language-plaintext highlighter-rouge">GET /api/v1/health</code>. It is deliberately cheap. No database I/O, no auth, returns <code class="language-plaintext highlighter-rouge">200</code> as long as the process and the HTTP layer are up.</p>

<p>Cheapness is the entire point. Liveness must not depend on database readiness. Wire a readiness-style check into <code class="language-plaintext highlighter-rouge">livenessProbe</code> and Kubernetes will helpfully kill a node that is still replaying its WAL, then kill the replacement for the same reason, and you will spend an afternoon reading kubelet events before you work out that your health check is the outage.</p>

<p><code class="language-plaintext highlighter-rouge">/api/v1/ready</code> is unchanged by default. It gains one optional behavior: with <code class="language-plaintext highlighter-rouge">arcadedb.server.readinessRequiresHA=true</code> and HA active, readiness reports false until the node has joined the Raft group and caught up. That gives clustered deployments a <code class="language-plaintext highlighter-rouge">readinessProbe</code> that means something.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">livenessProbe</span><span class="pi">:</span>
  <span class="na">httpGet</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">/api/v1/health</span>
    <span class="na">port</span><span class="pi">:</span> <span class="m">2480</span>
  <span class="na">initialDelaySeconds</span><span class="pi">:</span> <span class="m">10</span>
  <span class="na">periodSeconds</span><span class="pi">:</span> <span class="m">10</span>
<span class="na">readinessProbe</span><span class="pi">:</span>
  <span class="na">httpGet</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">/api/v1/ready</span>
    <span class="na">port</span><span class="pi">:</span> <span class="m">2480</span>
  <span class="na">initialDelaySeconds</span><span class="pi">:</span> <span class="m">5</span>
  <span class="na">periodSeconds</span><span class="pi">:</span> <span class="m">5</span>
</code></pre></div></div>

<h2 id="configuration-reference">Configuration reference</h2>

<p>Every new key defaults to off, or to what the server does today:</p>

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Default</th>
      <th>Effect</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.otlp.enabled</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>Register an OTLP metrics registry alongside Prometheus</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.otlp.endpoint</code></td>
      <td><code class="language-plaintext highlighter-rouge">http://localhost:4317</code></td>
      <td>OTLP metrics endpoint</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.tracing.enabled</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>Activate the tracing plugin (no-op if the jar isn’t present)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.tracing.endpoint</code></td>
      <td><code class="language-plaintext highlighter-rouge">http://localhost:4317</code></td>
      <td>OTLP trace endpoint</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.serverMetrics.tracing.samplingRate</code></td>
      <td><code class="language-plaintext highlighter-rouge">0.0</code></td>
      <td>Parent-based sampling ratio</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.server.logFormat</code></td>
      <td><code class="language-plaintext highlighter-rouge">text</code></td>
      <td><code class="language-plaintext highlighter-rouge">json</code> selects the structured <code class="language-plaintext highlighter-rouge">JsonLogFormatter</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.server.logIncludeTrace</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>Append <code class="language-plaintext highlighter-rouge">[traceId=...]</code> to text-mode logs</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">arcadedb.server.readinessRequiresHA</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>Make <code class="language-plaintext highlighter-rouge">/api/v1/ready</code> HA-aware (Raft membership and catch-up)</td>
    </tr>
  </tbody>
</table>

<h2 id="on-not-breaking-anything">On not breaking anything</h2>

<p>The whole design was built against one constraint: with no new configuration, an upgraded deployment behaves exactly as it did before. Existing endpoints and response shapes (<code class="language-plaintext highlighter-rouge">/prometheus</code>, <code class="language-plaintext highlighter-rouge">/api/v1/server</code>, <code class="language-plaintext highlighter-rouge">/api/v1/ready</code>) were added to, never modified. No configuration key was renamed. No new mandatory dependency lands on the core server, which is why the OpenTelemetry SDK sits behind the SPI in its own module rather than in the server <code class="language-plaintext highlighter-rouge">pom.xml</code> where it would have been considerably easier to put it.</p>

<p>Upgrade whenever you like, then turn things on one flag at a time.</p>

<h2 id="pairing-it-with-grafana">Pairing it with Grafana</h2>

<p>If you already run the <a href="/blog/arcadedb-grafana-plugin-bi-dashboards-for-your-multi-model-database/">ArcadeDB Grafana plugin</a> for BI dashboards over SQL, Cypher, and Gremlin, the OTLP metrics and traces drop into the same stack. Point an OpenTelemetry Collector at the server, fan out to Prometheus and Tempo (or whatever your vendor of the month is), and you can go from a p99 spike on a dashboard to the trace behind it to the log line that explains it, without leaving the cluster.</p>]]></content><author><name>Roberto Franchini</name></author><category term="Observability" /><category term="OpenTelemetry" /><category term="Kubernetes" /><category term="Metrics" /><category term="Tracing" /><category term="Logging" /><category term="Prometheus" /><category term="Micrometer" /><category term="Grafana" /><category term="DevOps" /><summary type="html"><![CDATA[ArcadeDB adds OpenTelemetry tracing, RED latency metrics with OTLP export, structured JSON logging with correlation IDs, and Kubernetes health probes. Every one of them is opt-in and backward compatible.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/arcadedb-cloud-observability.jpg" /><media:content medium="image" url="https://arcadedb.com/assets/images/arcadedb-cloud-observability.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ArcadeDB 26.7.1: Raft Hardening, OpenTelemetry Tracing, BM25 &amp;amp; GQL</title><link href="https://arcadedb.com/blog/arcadedb-26-7-1/" rel="alternate" type="text/html" title="ArcadeDB 26.7.1: Raft Hardening, OpenTelemetry Tracing, BM25 &amp;amp; GQL" /><published>2026-07-01T00:00:00+00:00</published><updated>2026-07-01T00:00:00+00:00</updated><id>https://arcadedb.com/blog/arcadedb-26-7-1</id><content type="html" xml:base="https://arcadedb.com/blog/arcadedb-26-7-1/"><![CDATA[<p>We’re pleased to announce <strong>ArcadeDB 26.7.1</strong>, a large stability, resilience, and security release with <strong>420+ commits</strong> resolving <strong>238 issues</strong>. Where <a href="https://arcadedb.com/blog/arcadedb-26-6-1/">26.6.1</a> added encrypted HA clusters and a durability hardening pass, 26.7.1 goes deeper into <strong>cluster resilience</strong>: the Raft engine now degrades gracefully instead of halting, diverged followers heal themselves, and the whole system is far easier to observe in production thanks to <strong>OpenTelemetry distributed tracing</strong>. On top of that come native <strong>BM25</strong> full-text scoring, <strong>ISO GQL</strong> standards alignment, and another broad security sweep.</p>

<h2 id="major-highlights">Major Highlights</h2>

<h3 id="raft--high-availability-hardening">Raft &amp; High Availability Hardening</h3>

<p>The headline of this release is resilience. A cluster should survive a bad entry, a network blip, or a diverged replica without operator intervention:</p>

<ul>
  <li>A failed apply on one database <strong>no longer halts the entire node</strong>. The affected database is put into <strong>per-database quarantine</strong> while every other database on the node keeps serving traffic.</li>
  <li>Diverged followers can now <strong>self-recover</strong> back to the leader’s state instead of getting stuck.</li>
  <li>Snapshot integrity is strengthened with <strong>fsync on write</strong> and <strong>manifest verification</strong>, so a follower never installs a truncated or corrupt snapshot.</li>
  <li>Membership changes are applied as <strong>atomic deltas under quorum protection</strong>, closing the classic split-brain and lost-quorum windows.</li>
  <li><strong>Silent write loss</strong> on replication timeouts has been eliminated: a write that cannot be safely replicated is reported, not dropped.</li>
  <li><code class="language-plaintext highlighter-rouge">TRUNCATE TYPE</code> and <code class="language-plaintext highlighter-rouge">TRUNCATE BUCKET</code> are now <strong>HA-safe</strong>, follower index correlation is fixed by stable peer-id, and a stalled replica stuck at <code class="language-plaintext highlighter-rouge">matchIndex=-1</code> now auto-recovers.</li>
</ul>

<p>These are the fixes you feel in production during the unhappy path, not in a benchmark.</p>

<h3 id="opentelemetry-distributed-tracing--structured-logging">OpenTelemetry Distributed Tracing &amp; Structured Logging</h3>

<p>Observability is now first-class. A new <strong>OpenTelemetry</strong> module provides end-to-end distributed tracing with optional <strong>OTLP export</strong>, so you can follow a request across the wire protocols, the query engine, and the storage layer in your existing tracing backend. Alongside it:</p>

<ul>
  <li><strong>Structured JSON logging</strong> with per-request <strong>correlation IDs</strong>, so a single request is trivial to reconstruct across logs and traces.</li>
  <li>Enhanced metrics with <strong>RED timers</strong> (Rate, Errors, Duration) and engine gauges.</li>
</ul>

<h3 id="native-bm25-full-text-scoring">Native BM25 Full-Text Scoring</h3>

<p>Full-text search now ships a native <strong>BM25</strong> scoring implementation with <strong>field boosts</strong> and <strong>caret (<code class="language-plaintext highlighter-rouge">^</code>) boost syntax</strong>, so relevance ranking is tunable per field. <code class="language-plaintext highlighter-rouge">EXPLAIN</code> and <code class="language-plaintext highlighter-rouge">PROFILE</code> now cover search operations too, making it easy to see how a full-text query is planned and executed.</p>

<h3 id="iso-gql-standards-alignment">ISO GQL Standards Alignment</h3>

<p>ArcadeDB continues moving toward the <strong>ISO GQL</strong> graph query standard:</p>

<ul>
  <li><strong>Session management</strong> statements: <code class="language-plaintext highlighter-rouge">SESSION SET</code> / <code class="language-plaintext highlighter-rouge">SESSION RESET</code> / <code class="language-plaintext highlighter-rouge">SESSION CLOSE</code>.</li>
  <li><strong>Transaction control</strong> statements: <code class="language-plaintext highlighter-rouge">START TRANSACTION</code> / <code class="language-plaintext highlighter-rouge">COMMIT</code> / <code class="language-plaintext highlighter-rouge">ROLLBACK</code>.</li>
  <li><strong>Strict numeric types</strong> for standards-compliant arithmetic and comparisons.</li>
</ul>

<h3 id="security-hardening">Security Hardening</h3>

<p>Another broad security sweep tightens the defaults for multi-tenant and internet-facing deployments:</p>

<ul>
  <li><strong>Polyglot scripting</strong> now requires <strong>administrative privileges</strong>.</li>
  <li><strong>Centralized gRPC authorization</strong> enforcement across all endpoints.</li>
  <li>Additional <strong>path-traversal</strong> protection hardening.</li>
  <li><strong>DoS bounding</strong> on gRPC result materialization.</li>
  <li><strong>Cluster-management endpoints</strong> are now restricted to root access.</li>
</ul>

<h2 id="major-fixes">Major Fixes</h2>

<h3 id="vector-engine">Vector Engine</h3>

<ul>
  <li>New import/interop formats: <strong>MATLAB</strong>, <strong>MATLAB_COLUMN</strong>, <strong>JULIA</strong>, and <strong>NUMPY</strong>.</li>
  <li>New helper functions: <strong><code class="language-plaintext highlighter-rouge">asVector()</code></strong>, <strong><code class="language-plaintext highlighter-rouge">asSparse()</code></strong>, and <strong><code class="language-plaintext highlighter-rouge">vectorDequantizeBinary</code></strong>.</li>
  <li>Improved <strong>RRF</strong> (Reciprocal Rank Fusion) array input handling.</li>
</ul>

<h3 id="mongodb-protocol">MongoDB Protocol</h3>

<ul>
  <li>Added <strong><code class="language-plaintext highlighter-rouge">update</code></strong>, <strong><code class="language-plaintext highlighter-rouge">delete</code></strong>, and <strong><code class="language-plaintext highlighter-rouge">createIndexes</code></strong> commands.</li>
  <li><strong>SASL PLAIN</strong> authentication support.</li>
  <li>Improved <code class="language-plaintext highlighter-rouge">find</code> command data handling.</li>
</ul>

<h3 id="kubernetes-operations">Kubernetes Operations</h3>

<ul>
  <li>Health probe endpoints for <strong>liveness</strong>, <strong>readiness</strong>, and <strong>startup</strong>.</li>
  <li><strong>Auto-acquire</strong> capability for databases the node has not seen yet.</li>
  <li><strong>StatefulSet scale-up</strong> support beyond a static peer list.</li>
</ul>

<h3 id="sql--queries">SQL &amp; Queries</h3>

<ul>
  <li>Map/collection <strong>key removal</strong> now persists correctly in <code class="language-plaintext highlighter-rouge">UPDATE</code>.</li>
  <li>Improved <strong>index selection</strong> for composite-key queries.</li>
  <li><strong>Three-valued logic</strong> for NULL operands in <code class="language-plaintext highlighter-rouge">IN</code> / <code class="language-plaintext highlighter-rouge">NOT IN</code>.</li>
  <li>Correct <strong>mixed-type numeric handling</strong> in <code class="language-plaintext highlighter-rouge">GROUP BY</code> and aggregation functions.</li>
</ul>

<h3 id="storage--recovery">Storage &amp; Recovery</h3>

<ul>
  <li><strong>Schema recovery from the backup file</strong> when the primary schema is corrupted.</li>
  <li>Corrupt <strong>hash-index detection and recovery</strong>.</li>
  <li>Time-series <strong>negative-timestamp alignment</strong> fixes.</li>
  <li>Page I/O locking improvements.</li>
</ul>

<h3 id="wire-protocols">Wire Protocols</h3>

<ul>
  <li><strong>gRPC:</strong> <code class="language-plaintext highlighter-rouge">InsertStream</code> now terminates correctly after a commit failure.</li>
  <li><strong>DATETIME precision</strong> is preserved across microsecond/nanosecond formats.</li>
  <li><strong>OpenCypher</strong> now tolerates dangling edges.</li>
  <li>Improved client-side <strong>nested type hydration</strong>.</li>
</ul>

<h3 id="dependencies">Dependencies</h3>

<p>Notable upgrades include Netty 4.2.15, Lucene 10.5.0, OpenTelemetry BOM 1.63.0, Jackson Databind 2.22.0, and JVector 4.0.0-rc.8, plus the usual round of Studio frontend, e2e harness, and CI updates.</p>

<h2 id="getting-started-with-2671">Getting Started with 26.7.1</h2>

<h3 id="docker">Docker</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull arcadedata/arcadedb:26.7.1
</code></pre></div></div>

<p>Visit our <a href="https://hub.docker.com/r/arcadedata/arcadedb">Docker Hub repository</a> for more information.</p>

<h3 id="maven">Maven</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.arcadedb<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>arcadedb-engine<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>26.7.1<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>All artifacts are available on <a href="https://repo.maven.apache.org/maven2/com/arcadedb/">Maven Central</a>.</p>

<h3 id="documentation">Documentation</h3>

<p>For detailed information on features and usage, refer to our <a href="https://docs.arcadedb.com/">comprehensive documentation</a>.</p>

<h2 id="compatibility-note">Compatibility Note</h2>

<p>This release maintains 100% compatibility with previous database formats, meaning no export/import is required when upgrading. As always, we recommend creating a database backup before upgrading.</p>

<hr />

<p><strong>Download ArcadeDB 26.7.1 now</strong>: <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.7.1">GitHub Releases</a></p>

<p>Thanks to everyone in the community who reported issues, opened PRs, and helped shape this release.</p>

<p>Luca Garulli
ArcadeDB Founder</p>]]></content><author><name>Luca Garulli</name></author><category term="Multi-Model" /><category term="High Availability" /><category term="Observability" /><category term="Security" /><category term="Graph Database" /><category term="Release" /><summary type="html"><![CDATA[ArcadeDB 26.7.1 is a large stability, resilience and security release with 420+ commits and 238 resolved issues: Raft/HA hardening with per-database quarantine and divergence self-recovery, OpenTelemetry distributed tracing, native BM25 full-text scoring, ISO GQL alignment, and a broad security sweep.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/release-v26.7.1.jpg" /><media:content medium="image" url="https://arcadedb.com/assets/images/release-v26.7.1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ArcadeDB 26.6.1: TLS for HA Clusters, Durability Hardening &amp;amp; Security</title><link href="https://arcadedb.com/blog/arcadedb-26-6-1/" rel="alternate" type="text/html" title="ArcadeDB 26.6.1: TLS for HA Clusters, Durability Hardening &amp;amp; Security" /><published>2026-06-03T00:00:00+00:00</published><updated>2026-06-03T00:00:00+00:00</updated><id>https://arcadedb.com/blog/arcadedb-26-6-1</id><content type="html" xml:base="https://arcadedb.com/blog/arcadedb-26-6-1/"><![CDATA[<p>We’re pleased to announce <strong>ArcadeDB 26.6.1</strong>, a stability, durability, and security focused release with <strong>280+ commits</strong> resolving <strong>66 issues</strong>. Where <a href="https://arcadedb.com/blog/arcadedb-26-5-1/">26.5.1</a> was about new retrieval features, 26.6.1 is about making the engine harder to break: <strong>encrypted HA clusters</strong>, <strong>crash-safe durability</strong>, and a broad <strong>security hardening</strong> pass, on top of a long list of OpenCypher, SQL, vector, and wire-protocol fixes.</p>

<h2 id="major-highlights">Major Highlights</h2>

<h3 id="tlsssl-across-the-ha-cluster">TLS/SSL Across the HA Cluster</h3>

<p>The Raft-based High Availability cluster can now run fully encrypted. Inter-node replication traffic supports <strong>SSL/TLS</strong>, and the snapshot installer was fixed so a follower can download a leader snapshot over the <strong>HTTPS listener</strong> instead of failing with <code class="language-plaintext highlighter-rouge">Unsupported or unrecognized SSL message</code>. Encrypted clustering is now a first-class deployment option for regulated and zero-trust environments.</p>

<h3 id="durability--crash-recovery-hardening">Durability &amp; Crash-Recovery Hardening</h3>

<p>A large batch of fixes closes data-integrity gaps across the storage, WAL, and serialization layers, so committed transactions survive crashes and power loss, and recovery never silently drops data:</p>

<ul>
  <li>The <strong>WAL is fsynced on commit</strong> by default, and data files are fsynced before WAL files are deleted on a clean close.</li>
  <li><strong>Crash recovery aborts on a WAL version gap</strong> and preserves the WAL files instead of silently skipping it.</li>
  <li><code class="language-plaintext highlighter-rouge">MutablePage.move</code> no longer mis-tracks the modified range on backward shifts, so defragmentation bytes are never omitted from the WAL.</li>
  <li>Binary serialization now writes a property count that matches the bytes written, and handles partial reads via <code class="language-plaintext highlighter-rouge">readFully</code>.</li>
  <li>Short-write / short-read returns are respected in the paginated component file.</li>
  <li>LZ4 compression no longer corrupts data when the source buffer position is non-zero.</li>
  <li>The Simple-8b codec no longer silently truncates <code class="language-plaintext highlighter-rouge">Long.MAX_VALUE</code> / <code class="language-plaintext highlighter-rouge">Long.MIN_VALUE</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">migratedFileIds</code> is persisted in <code class="language-plaintext highlighter-rouge">schema.json</code>, so compaction no longer silently drops in-flight transactions across a restart.</li>
  <li>A <code class="language-plaintext highlighter-rouge">NegativeArraySizeException</code> on transaction commit was fixed.</li>
</ul>

<p>These are the kind of fixes you never see in a benchmark but feel in production: the database does what it promised on the unhappy path.</p>

<h3 id="security-hardening">Security Hardening</h3>

<ul>
  <li>All schema mutators now require the <strong><code class="language-plaintext highlighter-rouge">UPDATE_SCHEMA</code></strong> permission (previously only <code class="language-plaintext highlighter-rouge">createProperty</code> was gated).</li>
  <li><strong><code class="language-plaintext highlighter-rouge">IMPORT DATABASE</code></strong> now validates its source and requires admin privilege, closing SSRF and local-file-inclusion vectors.</li>
  <li>SQL injection in <code class="language-plaintext highlighter-rouge">RemoteVertex.newEdge</code> was fixed by switching to <strong>parameter binding</strong> (which also fixes breakage on apostrophes).</li>
  <li>JavaScript injection in the polyglot engine was closed by replacing a “looks-like-JSON” source-concatenation heuristic with a safe <code class="language-plaintext highlighter-rouge">Value.execute()</code> call.</li>
  <li>A full <strong>CodeQL cleanup</strong> resolved open Java and JavaScript code-scanning alerts at their true sources (workflow permissions, ReDoS, path-injection).</li>
</ul>

<h2 id="major-fixes">Major Fixes</h2>

<h3 id="high-availability--clustering">High Availability &amp; Clustering</h3>

<ul>
  <li><strong>TimeSeries data now replicates correctly</strong> across an HA cluster, and a compaction/append deadlock that caused a WAL version gap on Raft followers was eliminated.</li>
  <li>Concurrent single-row time-series <code class="language-plaintext highlighter-rouge">INSERT</code>s no longer silently lose samples.</li>
  <li><strong>Bolt writes to a follower</strong> no longer fail with “no authenticated user in the current security context”.</li>
  <li><code class="language-plaintext highlighter-rouge">PeerAddressAllowlistFilter</code> no longer rejects legitimate peers during a Kubernetes DNS-resolution race on startup or restart.</li>
  <li>New configurable paths for read-only and containerized deployments: <code class="language-plaintext highlighter-rouge">arcadedb.ha.raftStorageDirectory</code>, a configurable server log directory, and <code class="language-plaintext highlighter-rouge">arcadedb.ha.clusterTokenPath</code> to read the cluster shared secret from a file.</li>
  <li><code class="language-plaintext highlighter-rouge">RemoteDatabase</code> no longer reuses a session id across servers on HA failover during an open transaction; a clear <code class="language-plaintext highlighter-rouge">TransactionException</code> is raised on server switch instead.</li>
  <li>New <strong><code class="language-plaintext highlighter-rouge">STICKY</code></strong> strategy pins HTTP transactions to a concrete cluster member.</li>
  <li><code class="language-plaintext highlighter-rouge">/api/v1/server?mode=cluster</code> returns the <code class="language-plaintext highlighter-rouge">ha</code> section again after the Raft migration.</li>
  <li>New <strong>“Force Resync”</strong> button in Studio to recover a diverged follower from the leader.</li>
</ul>

<h3 id="opencypher">OpenCypher</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CREATE INDEX</code> now <strong>implicitly creates the referenced property</strong> (Neo4j-style lazy schema).</li>
  <li><code class="language-plaintext highlighter-rouge">nodes()</code>, <code class="language-plaintext highlighter-rouge">relationships()</code>, and <code class="language-plaintext highlighter-rouge">length()</code> on variable-length path patterns (e.g. <code class="language-plaintext highlighter-rouge">[*1..3]</code>) are now implemented.</li>
  <li>Records written via SQL are now visible to subsequent Cypher queries (and vice versa) within the same transaction.</li>
  <li><code class="language-plaintext highlighter-rouge">EXPLAIN</code> no longer fails with an idempotency error on a multi-statement query containing <code class="language-plaintext highlighter-rouge">CREATE</code>.</li>
  <li>Label disjunction <code class="language-plaintext highlighter-rouge">(n:A|B)</code> no longer returns zero rows.</li>
  <li><code class="language-plaintext highlighter-rouge">allShortestPaths()</code> returns all co-shortest paths instead of just one.</li>
  <li><code class="language-plaintext highlighter-rouge">MERGE</code> uses a bound anchor as the traversal start instead of a full edge-type scan, and no longer crashes on single-quote property values or rebinds variables from an <code class="language-plaintext highlighter-rouge">OPTIONAL MATCH</code> null endpoint.</li>
  <li><code class="language-plaintext highlighter-rouge">DATETIME</code> comparison with <code class="language-plaintext highlighter-rouge">datetime()</code> no longer returns zero rows, and results are now consistent between parameterized and hard-coded values.</li>
</ul>

<h3 id="sql">SQL</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">IN :param</code> with a collection parameter now returns rows when an index is used.</li>
  <li><code class="language-plaintext highlighter-rouge">MOVE VERTEX</code> no longer generates an internal error.</li>
  <li><code class="language-plaintext highlighter-rouge">expand()</code> projection honors its <code class="language-plaintext highlighter-rouge">AS</code> alias instead of always being named <code class="language-plaintext highlighter-rouge">value</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">IN (SELECT …)</code> no longer always returns empty.</li>
  <li><code class="language-plaintext highlighter-rouge">MERGE</code> on a UNIQUE-indexed property no longer throws on a duplicate key when the same key appears twice in a batch (matching Neo4j semantics).</li>
  <li><code class="language-plaintext highlighter-rouge">node.*</code> and <code class="language-plaintext highlighter-rouge">rel.*</code> functions no longer silently return null from SQL.</li>
  <li>TimeSeries timestamps are now returned in queries.</li>
  <li>New <code class="language-plaintext highlighter-rouge">cypherRID()</code> SQL function and <code class="language-plaintext highlighter-rouge">asCypherRID()</code> method for interoperating with Cypher numeric ids.</li>
</ul>

<h3 id="vector--index">Vector &amp; Index</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">TRUNCATE TYPE</code> no longer resets an <code class="language-plaintext highlighter-rouge">LSM_VECTOR</code> index dimension to 0, nor leaves UNIQUE indexes in an inconsistent state.</li>
  <li><code class="language-plaintext highlighter-rouge">LSMVectorIndex</code> now converts JVector’s EUCLIDEAN return to L2² distance in all search paths, so K-NN no longer returns the worst matches first.</li>
  <li><code class="language-plaintext highlighter-rouge">REBUILD INDEX</code> now works for <code class="language-plaintext highlighter-rouge">BY ITEM</code> indexes.</li>
  <li><code class="language-plaintext highlighter-rouge">vector.fuse()</code> is now recognized as a SQL function.</li>
</ul>

<h3 id="wire-protocols">Wire Protocols</h3>

<ul>
  <li><strong>Bolt:</strong> parameterized Cypher <code class="language-plaintext highlighter-rouge">MATCH</code> queries via the JavaScript <code class="language-plaintext highlighter-rouge">neo4j-driver</code> now work; integer property values are no longer coerced to strings after <code class="language-plaintext highlighter-rouge">CREATE INDEX</code>.</li>
  <li><strong>PostgreSQL:</strong> scalar columns are advertised with native OIDs.</li>
  <li><strong>gRPC:</strong> correct exceptions (<code class="language-plaintext highlighter-rouge">NOT_FOUND</code> for missing records), proper <code class="language-plaintext highlighter-rouge">LocalDateTime</code> / <code class="language-plaintext highlighter-rouge">LocalDate</code> handling, and <code class="language-plaintext highlighter-rouge">InsertStream</code> no longer rolls back a whole stream on a commit-time duplicate with <code class="language-plaintext highlighter-rouge">CONFLICT_IGNORE</code>.</li>
  <li><strong>HTTP:</strong> <code class="language-plaintext highlighter-rouge">DuplicatedKeyException</code> now returns <code class="language-plaintext highlighter-rouge">409 Conflict</code> instead of <code class="language-plaintext highlighter-rouge">503 Service Unavailable</code>.</li>
</ul>

<h3 id="studio--operations">Studio &amp; Operations</h3>

<ul>
  <li>Optional <strong>production-mode Studio</strong>, enabled by a global setting on request.</li>
  <li>New show/hide toggle for the Appearance section in the graph side panel.</li>
  <li>AI assistant flow, database selection, and layout improvements; query profiler “Analyze with AI”; refreshed server and profiler metrics.</li>
  <li>New offline build mode for the distribution builder.</li>
</ul>

<h3 id="dependencies">Dependencies</h3>

<p>Notable upgrades include Netty 4.2.14.Final, Undertow 2.4.1.Final, Protobuf 4.35.0, JLine 4.1.3, JUnit Jupiter 6.1.0, Jackson Databind 2.21.4, Apache Commons Configuration 2.15.1, Swagger 2.2.50, SLF4J 2.0.18, and Logback 1.5.33, plus the usual round of Studio frontend, e2e harness, and CI updates.</p>

<h2 id="getting-started-with-2661">Getting Started with 26.6.1</h2>

<h3 id="docker">Docker</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull arcadedata/arcadedb:26.6.1
</code></pre></div></div>

<p>Visit our <a href="https://hub.docker.com/r/arcadedata/arcadedb">Docker Hub repository</a> for more information.</p>

<h3 id="maven">Maven</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.arcadedb<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>arcadedb-engine<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>26.6.1<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>All artifacts are available on <a href="https://repo.maven.apache.org/maven2/com/arcadedb/">Maven Central</a>.</p>

<h3 id="documentation">Documentation</h3>

<p>For detailed information on features and usage, refer to our <a href="https://docs.arcadedb.com/">comprehensive documentation</a>.</p>

<h2 id="compatibility-note">Compatibility Note</h2>

<p>This release maintains 100% compatibility with previous database formats, meaning no export/import is required when upgrading. As always, we recommend creating a database backup before upgrading.</p>

<hr />

<p><strong>Download ArcadeDB 26.6.1 now</strong>: <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.6.1">GitHub Releases</a></p>

<p>Thanks to everyone in the community who reported issues, opened PRs, and helped shape this release.</p>

<p>Luca Garulli
ArcadeDB Founder</p>]]></content><author><name>Luca Garulli</name></author><category term="Multi-Model" /><category term="High Availability" /><category term="Security" /><category term="Graph Database" /><category term="Release" /><summary type="html"><![CDATA[ArcadeDB 26.6.1 brings end-to-end TLS/SSL for HA clusters, a deep durability and crash-recovery hardening pass across the WAL and storage layers, a broad security hardening sweep, and a long list of OpenCypher, SQL, vector and wire-protocol fixes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/release-v26.6.1.jpg" /><media:content medium="image" url="https://arcadedb.com/assets/images/release-v26.6.1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Deploy an ArcadeDB Cluster on Kubernetes with the Official Helm Chart</title><link href="https://arcadedb.com/blog/deploy-arcadedb-cluster-kubernetes-helm/" rel="alternate" type="text/html" title="Deploy an ArcadeDB Cluster on Kubernetes with the Official Helm Chart" /><published>2026-05-13T00:00:00+00:00</published><updated>2026-05-13T00:00:00+00:00</updated><id>https://arcadedb.com/blog/deploy-arcadedb-cluster-kubernetes-helm</id><content type="html" xml:base="https://arcadedb.com/blog/deploy-arcadedb-cluster-kubernetes-helm/"><![CDATA[<p>Running <a href="https://arcadedb.com/">ArcadeDB</a> as a single container is easy. Running it as a replicated service on a Kubernetes cluster used to mean writing a fair amount of YAML and reading the HA docs twice. With the official <a href="https://github.com/ArcadeData/arcadedb-helm">arcadedb-helm</a> chart, it now takes one command.</p>

<p>In this post I walk through the chart, show how to bring up a three-node HA cluster, and point at the companion <a href="https://github.com/ArcadeData/arcadedb-deployments">arcadedb-deployments</a> repository if you want a runnable local example before touching your production cluster.</p>

<h2 id="why-run-arcadedb-on-kubernetes">Why Run ArcadeDB on Kubernetes</h2>

<p>ArcadeDB is built around an embedded engine that scales vertically very well. What you get from Kubernetes is the operational layer: rolling upgrades, persistent volumes, automatic restarts when a node dies, horizontal scale for read-heavy workloads, and replication across availability zones.</p>

<p>The Helm chart wraps that into a StatefulSet with stable network identities, a headless service for peer discovery, and probes wired to the <a href="https://docs.arcadedb.com/"><code class="language-plaintext highlighter-rouge">/api/v1/ready</code></a> endpoint. When <code class="language-plaintext highlighter-rouge">replicaCount</code> is greater than 1, the chart turns on <a href="https://docs.arcadedb.com/arcadedb/concepts/ha-cluster.html">Raft consensus</a> across the pods. No extra flags, no manual peer lists.</p>

<h2 id="what-the-helm-chart-gives-you">What the Helm Chart Gives You</h2>

<p>The chart lives under <a href="https://github.com/ArcadeData/arcadedb-helm/tree/main/charts/arcadedb"><code class="language-plaintext highlighter-rouge">charts/arcadedb</code></a> and is published on <a href="https://artifacthub.io/packages/helm/arcadedb/arcadedb">Artifact Hub</a>. The current chart version is <code class="language-plaintext highlighter-rouge">26.4.2</code>, the same as the ArcadeDB engine version it deploys.</p>

<p>The defaults are sensible. You get a StatefulSet with stable pod names (<code class="language-plaintext highlighter-rouge">arcadedb-0</code>, <code class="language-plaintext highlighter-rouge">arcadedb-1</code>, …) and ordered rollout, a headless service so each pod resolves its peers via DNS (<code class="language-plaintext highlighter-rouge">arcadedb-0.arcadedb.default.svc.cluster.local</code>), and a PersistentVolumeClaim template (8Gi ReadWriteOnce by default) mounted at <code class="language-plaintext highlighter-rouge">/home/arcadedb/databases</code>. Liveness and readiness probes hit <code class="language-plaintext highlighter-rouge">/api/v1/ready</code>.</p>

<p>Security is also taken care of: the pod runs as non-root UID/GID 1000, all Linux capabilities are dropped, privilege escalation is disabled, and the ServiceAccount token is unmounted because the database does not call the Kubernetes API. A <code class="language-plaintext highlighter-rouge">NetworkPolicy</code> can lock the Raft gRPC port down to ArcadeDB pods only, and there is <code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code> support that pre-sizes the Raft peer list to <code class="language-plaintext highlighter-rouge">maxReplicas</code> so scale-out joins are clean.</p>

<p>The whole chart is small enough to read in a single sitting, which I recommend before you push it to production.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>You need a Kubernetes cluster (1.27 or newer is fine), Helm 3.16 or newer, <code class="language-plaintext highlighter-rouge">kubectl</code> pointed at the target cluster, and a storage class that supports <code class="language-plaintext highlighter-rouge">ReadWriteOnce</code>. The defaults on EKS, GKE, AKS, and DigitalOcean all work. For local experimentation, <a href="https://kind.sigs.k8s.io/">kind</a> 0.24 or newer is enough.</p>

<h2 id="the-30-second-install">The 30-Second Install</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>helm repo add arcadedb https://helm.arcadedb.com/
helm repo update
helm <span class="nb">install </span>my-arcadedb arcadedb/arcadedb
</code></pre></div></div>

<p>That is it. You now have a single-pod ArcadeDB with a persistent volume and a ClusterIP service.</p>

<p>Port-forward to reach <a href="https://docs.arcadedb.com/arcadedb/tools/studio.html">Studio</a>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl port-forward svc/my-arcadedb 2480:2480
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">http://localhost:2480</code> in your browser. Done.</p>

<p>For a dev box, a CI fixture, or a smoke test, this is enough. Anything user-facing needs more.</p>

<h2 id="production-values-a-three-node-ha-cluster">Production Values: a Three-Node HA Cluster</h2>

<p>For the multi-node setup, drop the following into a <code class="language-plaintext highlighter-rouge">values.yaml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">replicaCount</span><span class="pi">:</span> <span class="m">3</span>

<span class="na">image</span><span class="pi">:</span>
  <span class="na">repository</span><span class="pi">:</span> <span class="s">arcadedata/arcadedb</span>
  <span class="na">tag</span><span class="pi">:</span> <span class="s2">"</span><span class="s">26.4.2"</span>
  <span class="na">pullPolicy</span><span class="pi">:</span> <span class="s">IfNotPresent</span>

<span class="na">arcadedb</span><span class="pi">:</span>
  <span class="na">rootPassword</span><span class="pi">:</span>
    <span class="na">secret</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">arcadedb-credentials</span>
      <span class="na">key</span><span class="pi">:</span> <span class="s">rootPassword</span>

<span class="na">persistence</span><span class="pi">:</span>
  <span class="na">enabled</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">size</span><span class="pi">:</span> <span class="s">50Gi</span>
  <span class="na">storageClass</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fast-ssd"</span>

<span class="na">resources</span><span class="pi">:</span>
  <span class="na">requests</span><span class="pi">:</span>
    <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1"</span>
    <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">4Gi"</span>
  <span class="na">limits</span><span class="pi">:</span>
    <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>
    <span class="na">memory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">8Gi"</span>

<span class="na">service</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>

<span class="na">ingress</span><span class="pi">:</span>
  <span class="na">enabled</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">className</span><span class="pi">:</span> <span class="s2">"</span><span class="s">nginx"</span>
  <span class="na">hosts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">arcadedb.example.com</span>
      <span class="na">paths</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
          <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
  <span class="na">tls</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretName</span><span class="pi">:</span> <span class="s">arcadedb-tls</span>
      <span class="na">hosts</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">arcadedb.example.com</span>

<span class="na">networkPolicy</span><span class="pi">:</span>
  <span class="na">enabled</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<p>Create the credentials secret separately, so the password never lives in your Helm values or your Git history:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl create secret generic arcadedb-credentials <span class="se">\</span>
  <span class="nt">--from-literal</span><span class="o">=</span><span class="nv">rootPassword</span><span class="o">=</span><span class="s1">'choose-something-strong'</span>
</code></pre></div></div>

<p>Then install (or upgrade) the chart:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>helm upgrade <span class="nt">--install</span> arcadedb arcadedb/arcadedb <span class="se">\</span>
  <span class="nt">--namespace</span> arcadedb <span class="nt">--create-namespace</span> <span class="se">\</span>
  <span class="nt">-f</span> values.yaml <span class="nt">--wait</span> <span class="nt">--timeout</span> 10m
</code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">replicaCount: 3</code>, the chart wires the StatefulSet for <a href="https://docs.arcadedb.com/arcadedb/concepts/ha-cluster.html">Raft HA</a>. Each pod gets its own PVC, joins the cluster through the headless service, and the three-node quorum elects a leader.</p>

<h2 id="verifying-the-cluster">Verifying the Cluster</h2>

<p>Watch the pods come up:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nt">-n</span> arcadedb get pods <span class="nt">-w</span>
</code></pre></div></div>

<p>You should see <code class="language-plaintext highlighter-rouge">arcadedb-0</code>, <code class="language-plaintext highlighter-rouge">arcadedb-1</code>, and <code class="language-plaintext highlighter-rouge">arcadedb-2</code> reach <code class="language-plaintext highlighter-rouge">Running</code> in order. Once all three are ready, ask the cluster who is in charge:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nt">-n</span> arcadedb port-forward svc/arcadedb 2480:2480 &amp;
curl <span class="nt">-u</span> root:choose-something-strong http://localhost:2480/api/v1/server | jq .ha
</code></pre></div></div>

<p>The response includes the current leader, the list of replicas, and the network status of each peer. If you see three online servers and one of them flagged as <code class="language-plaintext highlighter-rouge">leader</code>, you have a working HA cluster.</p>

<p>To prove the failover works, delete the leader pod and watch the cluster re-elect:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nt">-n</span> arcadedb delete pod arcadedb-0
kubectl <span class="nt">-n</span> arcadedb get pods <span class="nt">-w</span>
</code></pre></div></div>

<p>The remaining nodes hold quorum, a new leader is elected within seconds, and Kubernetes brings the missing pod back. Its PVC is reattached, the data is intact, and it rejoins the Raft group as a follower.</p>

<h2 id="try-it-locally-first-the-arcadedb-deployments-repo">Try It Locally First: the arcadedb-deployments Repo</h2>

<p>Before opening a PR against your platform team’s repo, run the thing end-to-end on your laptop. The <a href="https://github.com/ArcadeData/arcadedb-deployments">arcadedb-deployments</a> repository has a ready-to-run example under <code class="language-plaintext highlighter-rouge">kubernetes/</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">start.sh</code> script creates a <a href="https://kind.sigs.k8s.io/">kind</a> cluster named <code class="language-plaintext highlighter-rouge">arcadedb</code>, runs <code class="language-plaintext highlighter-rouge">helm dependency update</code>, installs the chart with <code class="language-plaintext highlighter-rouge">--wait</code>, applies a 3-replica <code class="language-plaintext highlighter-rouge">values.yaml</code> and the credentials secret, waits for <code class="language-plaintext highlighter-rouge">/api/v1/ready</code> to respond on every pod, and sets up a background <code class="language-plaintext highlighter-rouge">kubectl port-forward</code> to <code class="language-plaintext highlighter-rouge">http://localhost:2480</code>. <code class="language-plaintext highlighter-rouge">test.sh</code> then drives an end-to-end smoke test against the cluster.</p>

<p>Clone, run, done:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/ArcadeData/arcadedb-deployments.git
<span class="nb">cd </span>arcadedb-deployments/kubernetes
./start.sh
./test.sh
</code></pre></div></div>

<p>When you are finished:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./stop.sh
</code></pre></div></div>

<p>It is the fastest way to convince yourself (or your team) that the chart behaves the way you expect. Same chart, same values shape, same probes, smaller cluster.</p>

<p>The same repository ships an <code class="language-plaintext highlighter-rouge">ha-cluster/</code> scenario built on Docker Compose if you want to compare the same topology without Kubernetes in the picture.</p>

<h2 id="operating-the-cluster">Operating the Cluster</h2>

<p>A few practical notes for day-two operations.</p>

<h3 id="upgrades">Upgrades</h3>

<p>Bump the chart and image tag together, then <code class="language-plaintext highlighter-rouge">helm upgrade</code>. The StatefulSet rolls pods one at a time, the readiness probe gates each step, and Raft tolerates the missing follower throughout. Always upgrade in a non-production environment first to validate the engine version.</p>

<h3 id="scaling">Scaling</h3>

<p>To scale out, increase <code class="language-plaintext highlighter-rouge">replicaCount</code> and run <code class="language-plaintext highlighter-rouge">helm upgrade</code>. New pods come up, join the Raft group as followers, and start serving reads.</p>

<p>Scale-down needs more care. Never drop below the quorum size of your current cluster, and always remove pods one at a time. Three or five nodes covers most workloads. Seven is the upper end before the Raft commit cost outweighs the redundancy you get back.</p>

<h3 id="backups">Backups</h3>

<p>ArcadeDB has built-in <a href="https://arcadedb.com/blog/introducing-automatic-database-backups-in-arcadedb/">automatic database backups</a>. On Kubernetes, point the backup directory at a separate volume (or a CSI driver that snapshots to object storage) so backup data lives outside the database PVC. Take the snapshot at the leader to get a consistent view.</p>

<h3 id="observability">Observability</h3>

<p>The chart exposes the standard ArcadeDB metrics on the HTTP port. Scrape them with your existing Prometheus stack and alert on Raft leader changes, replication lag, and PVC capacity.</p>

<h3 id="security">Security</h3>

<p>Change the default <code class="language-plaintext highlighter-rouge">root</code> password. Always. Use a <code class="language-plaintext highlighter-rouge">Secret</code>, never <code class="language-plaintext highlighter-rouge">--set</code> it on the command line. Enable the included <code class="language-plaintext highlighter-rouge">NetworkPolicy</code> to keep the Raft port internal to the namespace. If you expose Studio publicly, put it behind your usual ingress, OIDC proxy, or VPN.</p>

<h2 id="where-to-go-next">Where to Go Next</h2>

<ul>
  <li><a href="https://github.com/ArcadeData/arcadedb-helm">arcadedb-helm</a>: chart source, values reference, and CI tests</li>
  <li><a href="https://github.com/ArcadeData/arcadedb-deployments">arcadedb-deployments</a>: runnable Kubernetes and Docker Compose examples</li>
  <li><a href="https://docs.arcadedb.com/arcadedb/concepts/ha-cluster.html">ArcadeDB HA Cluster docs</a>: how Raft replication works under the hood</li>
  <li><a href="https://arcadedb.com/academy.html">ArcadeDB Academy</a>: free courses, including hands-on labs</li>
</ul>

<p>If something does not work the way this post describes, open an issue on the chart repo. PRs are welcome too. The chart is actively maintained, the CI pipeline lints every change, and the <code class="language-plaintext highlighter-rouge">helm-unittest</code> suite already covers most templates.</p>]]></content><author><name>Roberto Franchini</name></author><category term="Kubernetes" /><category term="Helm" /><category term="HA Cluster" /><category term="Raft" /><category term="DevOps" /><category term="Deployment" /><category term="StatefulSet" /><summary type="html"><![CDATA[Step-by-step guide to deploying a high-availability ArcadeDB cluster on Kubernetes using the official Helm chart. Includes a kind-based local example, production values, Raft consensus, persistence, and verification.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/arcadedb-helm-kubernetes.svg" /><media:content medium="image" url="https://arcadedb.com/assets/images/arcadedb-helm-kubernetes.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ArcadeDB 26.5.1: Sparse Vector Index, Hybrid Retrieval &amp;amp; INT8 End-to-End</title><link href="https://arcadedb.com/blog/arcadedb-26-5-1/" rel="alternate" type="text/html" title="ArcadeDB 26.5.1: Sparse Vector Index, Hybrid Retrieval &amp;amp; INT8 End-to-End" /><published>2026-05-11T00:00:00+00:00</published><updated>2026-05-11T00:00:00+00:00</updated><id>https://arcadedb.com/blog/arcadedb-26-5-1</id><content type="html" xml:base="https://arcadedb.com/blog/arcadedb-26-5-1/"><![CDATA[<p>We’re excited to announce <strong>ArcadeDB 26.5.1</strong>, a major release with <strong>270+ commits</strong> resolving <strong>128 issues</strong>. The headline feature is a brand-new <strong>sparse vector index</strong> with <strong>server-side hybrid retrieval</strong> and <strong>INT8 quantization end-to-end</strong>, alongside extensive <strong>OpenCypher correctness</strong> improvements and <strong>query partitioning</strong>.</p>

<h2 id="major-new-features">Major New Features</h2>

<h3 id="sparse-vector-index--hybrid-retrieval">Sparse Vector Index &amp; Hybrid Retrieval</h3>

<p>The new <code class="language-plaintext highlighter-rouge">LSM_SPARSE_VECTOR</code> index type enables sparse-embedding retrieval (BM25/SPLADE-style) directly inside ArcadeDB.</p>

<figure style="margin: 32px 0; text-align: center;">
  <svg viewBox="0 0 760 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Dense vector vs sparse vector representation" style="max-width: 100%; height: auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
    <!-- Dense Vector Panel -->
    <g>
      <rect x="20" y="20" width="340" height="320" rx="12" fill="#F0F4F8" stroke="#D1D9E0" stroke-width="1" />
      <text x="190" y="50" text-anchor="middle" font-size="18" font-weight="700" fill="#1F2937">Dense Vector</text>
      <text x="190" y="70" text-anchor="middle" font-size="11" fill="#6B7280">float32 embedding (semantic)</text>

      <g transform="translate(50, 110)">
        <rect x="0" y="0" width="20" height="44" fill="#0066CC" opacity="0.35" />
        <rect x="20" y="0" width="20" height="44" fill="#0066CC" opacity="0.78" />
        <rect x="40" y="0" width="20" height="44" fill="#0066CC" opacity="0.21" />
        <rect x="60" y="0" width="20" height="44" fill="#0066CC" opacity="0.55" />
        <rect x="80" y="0" width="20" height="44" fill="#0066CC" opacity="0.89" />
        <rect x="100" y="0" width="20" height="44" fill="#0066CC" opacity="0.42" />
        <rect x="120" y="0" width="20" height="44" fill="#0066CC" opacity="0.67" />
        <rect x="140" y="0" width="20" height="44" fill="#0066CC" opacity="0.15" />
        <rect x="160" y="0" width="20" height="44" fill="#0066CC" opacity="0.72" />
        <rect x="180" y="0" width="20" height="44" fill="#0066CC" opacity="0.48" />
        <rect x="200" y="0" width="20" height="44" fill="#0066CC" opacity="0.91" />
        <rect x="220" y="0" width="20" height="44" fill="#0066CC" opacity="0.28" />
        <rect x="240" y="0" width="20" height="44" fill="#0066CC" opacity="0.61" />
        <rect x="260" y="0" width="20" height="44" fill="#0066CC" opacity="0.34" />
      </g>

      <text x="190" y="190" text-anchor="middle" font-size="11" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" fill="#4B5563">[0.35, 0.78, 0.21, 0.55, 0.89, 0.42, ...]</text>

      <text x="190" y="235" text-anchor="middle" font-size="13" fill="#1F2937"><tspan font-weight="600">384 &#8211; 1,536</tspan> dimensions</text>
      <text x="190" y="260" text-anchor="middle" font-size="13" fill="#1F2937">Every position has a value</text>
      <text x="190" y="305" text-anchor="middle" font-size="13" font-weight="600" fill="#0066CC">Semantic similarity</text>
    </g>

    <!-- Sparse Vector Panel -->
    <g>
      <rect x="400" y="20" width="340" height="320" rx="12" fill="#FFF7ED" stroke="#FED7AA" stroke-width="1" />
      <text x="570" y="50" text-anchor="middle" font-size="18" font-weight="700" fill="#1F2937">Sparse Vector</text>
      <text x="570" y="70" text-anchor="middle" font-size="11" fill="#D97706" font-weight="600">NEW &middot; BM25 / SPLADE-style</text>

      <g transform="translate(430, 110)">
        <rect x="0" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="10" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="20" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="30" y="0" width="10" height="44" fill="#F59E0B" opacity="0.85" />
        <rect x="40" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="50" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="60" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="70" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="80" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="90" y="0" width="10" height="44" fill="#F59E0B" opacity="0.60" />
        <rect x="100" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="110" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="120" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="130" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="140" y="0" width="10" height="44" fill="#F59E0B" opacity="0.95" />
        <rect x="150" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="160" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="170" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="180" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="190" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="200" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="210" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="220" y="0" width="10" height="44" fill="#F59E0B" opacity="0.75" />
        <rect x="230" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="240" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="250" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
        <rect x="260" y="0" width="10" height="44" fill="#F59E0B" opacity="0.50" />
        <rect x="270" y="0" width="10" height="44" fill="#FFFFFF" stroke="#FED7AA" stroke-width="0.5" />
      </g>

      <text x="570" y="190" text-anchor="middle" font-size="11" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" fill="#4B5563">{ 3: 0.85, 9: 0.60, 14: 0.95, 22: 0.75, ... }</text>

      <text x="570" y="235" text-anchor="middle" font-size="13" fill="#1F2937"><tspan font-weight="600">30,000+</tspan> vocabulary positions</text>
      <text x="570" y="260" text-anchor="middle" font-size="13" fill="#1F2937">Only a few non-zero values</text>
      <text x="570" y="305" text-anchor="middle" font-size="13" font-weight="600" fill="#D97706">Lexical / keyword recall</text>
    </g>
  </svg>
  <figcaption style="margin-top: 12px; font-size: 0.9rem; color: #6c7a89;">Dense vectors capture semantic meaning across every dimension; sparse vectors capture exact keyword signals across a much larger vocabulary, with most positions empty. ArcadeDB 26.5.1 supports both, and can fuse them server-side.</figcaption>
</figure>

<p>Highlights:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">vector.fuse(...)</code> performs <strong>server-side result fusion</strong> using RRF, DBSF, and LINEAR strategies, so dense + sparse + lexical scores can be combined without round-trips to the client.</li>
  <li><code class="language-plaintext highlighter-rouge">vector.neighbors(...)</code> supports <code class="language-plaintext highlighter-rouge">groupBy</code> / <code class="language-plaintext highlighter-rouge">groupSize</code> options for <strong>diversified retrieval</strong> with nested-field grouping.</li>
  <li><strong>WAND / BlockMax-WAND</strong> dynamic pruning scales sparse retrieval to 100M+ documents.</li>
  <li><strong>Sparse-vector partitioning</strong> allows sharding by tenant or domain.</li>
  <li>New reranker SQL functions enable two-stage retrieval pipelines.</li>
</ul>

<h3 id="int8-quantization-for-dense-vectors">INT8 Quantization for Dense Vectors</h3>

<p>End-to-end <strong>INT8 support</strong> throughout the dense vector pipeline, dramatically reducing disk and RSS by avoiding the FP32 path entirely. A shared 8-bit representation now flows across ingest, storage, and query.</p>

<h3 id="external-property-storage">EXTERNAL Property Storage</h3>

<p>A new paired-bucket layout isolates <strong>heavy property values</strong> (vectors, large strings, JSON) to separate external buckets while keeping the hot row data compact. The result: significantly cheaper scans on wide records.</p>

<figure style="margin: 32px 0; text-align: center;">
  <svg viewBox="0 0 760 500" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Classic bucket layout vs EXTERNAL paired-bucket layout" style="max-width: 100%; height: auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
    <!-- Top panel: classic layout -->
    <g>
      <rect x="20" y="20" width="720" height="200" rx="12" fill="#F0F4F8" stroke="#D1D9E0" stroke-width="1" />
      <text x="380" y="50" text-anchor="middle" font-size="16" font-weight="700" fill="#1F2937">Without EXTERNAL &middot; everything in the main bucket</text>

      <g transform="translate(80, 80)">
        <rect x="0" y="0" width="600" height="34" fill="#FFFFFF" stroke="#D1D9E0" />
        <rect x="0" y="0" width="60" height="34" fill="#DBEAFE" stroke="#D1D9E0" />
        <text x="30" y="22" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">id</text>
        <rect x="60" y="0" width="80" height="34" fill="#E0E7FF" stroke="#D1D9E0" />
        <text x="100" y="22" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">name</text>
        <rect x="140" y="0" width="460" height="34" fill="#FEE2E2" stroke="#D1D9E0" />
        <text x="370" y="22" text-anchor="middle" font-size="11" font-weight="600" fill="#991B1B">vector (1,536 floats) &middot; large JSON &middot; blob</text>

        <rect x="0" y="42" width="600" height="34" fill="#FFFFFF" stroke="#D1D9E0" />
        <rect x="0" y="42" width="60" height="34" fill="#DBEAFE" stroke="#D1D9E0" />
        <text x="30" y="64" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">id</text>
        <rect x="60" y="42" width="80" height="34" fill="#E0E7FF" stroke="#D1D9E0" />
        <text x="100" y="64" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">name</text>
        <rect x="140" y="42" width="460" height="34" fill="#FEE2E2" stroke="#D1D9E0" />
        <text x="370" y="64" text-anchor="middle" font-size="11" font-weight="600" fill="#991B1B">vector (1,536 floats) &middot; large JSON &middot; blob</text>

        <rect x="0" y="84" width="600" height="34" fill="#FFFFFF" stroke="#D1D9E0" />
        <rect x="0" y="84" width="60" height="34" fill="#DBEAFE" stroke="#D1D9E0" />
        <text x="30" y="106" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">id</text>
        <rect x="60" y="84" width="80" height="34" fill="#E0E7FF" stroke="#D1D9E0" />
        <text x="100" y="106" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">name</text>
        <rect x="140" y="84" width="460" height="34" fill="#FEE2E2" stroke="#D1D9E0" />
        <text x="370" y="106" text-anchor="middle" font-size="11" font-weight="600" fill="#991B1B">vector (1,536 floats) &middot; large JSON &middot; blob</text>
      </g>

      <text x="380" y="210" text-anchor="middle" font-size="12" fill="#991B1B">Every scan reads heavy payloads &rarr; wide rows, slow scans</text>
    </g>

    <!-- Down arrow between panels -->
    <text x="380" y="245" text-anchor="middle" font-size="14" font-weight="700" fill="#6B7280">&#9660;</text>

    <!-- Bottom panel: EXTERNAL -->
    <g>
      <rect x="20" y="260" width="720" height="230" rx="12" fill="#ECFDF5" stroke="#A7F3D0" stroke-width="1" />
      <text x="380" y="290" text-anchor="middle" font-size="16" font-weight="700" fill="#065F46">With EXTERNAL (NEW) &middot; paired-bucket layout</text>

      <text x="170" y="320" text-anchor="middle" font-size="12" font-weight="600" fill="#1F2937">Main Bucket (compact, hot)</text>
      <text x="600" y="320" text-anchor="middle" font-size="12" font-weight="600" fill="#1F2937">External Bucket (lazy)</text>

      <!-- Main bucket: thin rows -->
      <g transform="translate(50, 340)">
        <rect x="0" y="0" width="240" height="28" fill="#FFFFFF" stroke="#A7F3D0" />
        <rect x="0" y="0" width="60" height="28" fill="#DBEAFE" stroke="#A7F3D0" />
        <text x="30" y="19" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">id</text>
        <rect x="60" y="0" width="100" height="28" fill="#E0E7FF" stroke="#A7F3D0" />
        <text x="110" y="19" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">name</text>
        <rect x="160" y="0" width="80" height="28" fill="#FEF3C7" stroke="#A7F3D0" />
        <text x="200" y="19" text-anchor="middle" font-size="10" font-weight="600" fill="#92400E">&rarr; ref</text>

        <rect x="0" y="36" width="240" height="28" fill="#FFFFFF" stroke="#A7F3D0" />
        <rect x="0" y="36" width="60" height="28" fill="#DBEAFE" stroke="#A7F3D0" />
        <text x="30" y="55" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">id</text>
        <rect x="60" y="36" width="100" height="28" fill="#E0E7FF" stroke="#A7F3D0" />
        <text x="110" y="55" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">name</text>
        <rect x="160" y="36" width="80" height="28" fill="#FEF3C7" stroke="#A7F3D0" />
        <text x="200" y="55" text-anchor="middle" font-size="10" font-weight="600" fill="#92400E">&rarr; ref</text>

        <rect x="0" y="72" width="240" height="28" fill="#FFFFFF" stroke="#A7F3D0" />
        <rect x="0" y="72" width="60" height="28" fill="#DBEAFE" stroke="#A7F3D0" />
        <text x="30" y="91" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">id</text>
        <rect x="60" y="72" width="100" height="28" fill="#E0E7FF" stroke="#A7F3D0" />
        <text x="110" y="91" text-anchor="middle" font-size="11" font-weight="600" fill="#1E40AF">name</text>
        <rect x="160" y="72" width="80" height="28" fill="#FEF3C7" stroke="#A7F3D0" />
        <text x="200" y="91" text-anchor="middle" font-size="10" font-weight="600" fill="#92400E">&rarr; ref</text>
      </g>

      <!-- Lazy-load arrows -->
      <g stroke="#10B981" stroke-width="1.5" stroke-dasharray="5 3" fill="none">
        <line x1="295" y1="354" x2="485" y2="354" />
        <line x1="295" y1="390" x2="485" y2="390" />
        <line x1="295" y1="426" x2="485" y2="426" />
      </g>
      <g fill="#10B981" stroke="none">
        <polygon points="485,354 477,350 477,358" />
        <polygon points="485,390 477,386 477,394" />
        <polygon points="485,426 477,422 477,430" />
      </g>
      <text x="390" y="343" text-anchor="middle" font-size="10" font-style="italic" fill="#065F46">loaded lazily on access</text>

      <!-- External bucket: heavy rows -->
      <g transform="translate(490, 340)">
        <rect x="0" y="0" width="220" height="28" fill="#FEE2E2" stroke="#A7F3D0" />
        <text x="110" y="19" text-anchor="middle" font-size="10" font-weight="600" fill="#991B1B">vector / JSON / blob</text>

        <rect x="0" y="36" width="220" height="28" fill="#FEE2E2" stroke="#A7F3D0" />
        <text x="110" y="55" text-anchor="middle" font-size="10" font-weight="600" fill="#991B1B">vector / JSON / blob</text>

        <rect x="0" y="72" width="220" height="28" fill="#FEE2E2" stroke="#A7F3D0" />
        <text x="110" y="91" text-anchor="middle" font-size="10" font-weight="600" fill="#991B1B">vector / JSON / blob</text>
      </g>

      <text x="380" y="475" text-anchor="middle" font-size="12" font-weight="600" fill="#065F46">Compact rows &rarr; fast scans. Heavy values fetched only when read.</text>
    </g>
  </svg>
  <figcaption style="margin-top: 12px; font-size: 0.9rem; color: #6c7a89;">EXTERNAL Property Storage moves heavy values (vectors, large strings, JSON) to a paired external bucket. The main bucket stays compact, scans stay hot, and large payloads are loaded lazily only when the row is actually read.</figcaption>
</figure>

<h3 id="query-partitioning">Query Partitioning</h3>

<p>A partition-aware planner now <strong>prunes unnecessary partitions</strong> from SQL and Cypher execution plans, with integrity safeguards for partitioned types.</p>

<h3 id="high-availability-offline-cluster-bootstrap">High Availability: Offline Cluster Bootstrap</h3>

<p>Fresh HA clusters can now initialize from <strong>pre-seeded databases</strong> via snapshot-and-restore, eliminating the need for full dataset re-replication when expanding or rebuilding a cluster.</p>

<h3 id="production-ready-helm-chart">Production-Ready Helm Chart</h3>

<p>The Helm chart has been reworked to align with the Raft-based HA subsystem introduced in 26.4.2, and is now suitable for production deployments.</p>

<h3 id="cypher-administrative-commands">Cypher Administrative Commands</h3>

<p>Standard administrative commands <code class="language-plaintext highlighter-rouge">SHOW INDEXES</code> and <code class="language-plaintext highlighter-rouge">SHOW CONSTRAINTS</code> are now supported in OpenCypher.</p>

<h3 id="sql-find-references">SQL: FIND REFERENCES</h3>

<p>The OrientDB-compatible <code class="language-plaintext highlighter-rouge">FIND REFERENCES</code> command is back, making it easy to locate all records pointing to a given RID — particularly useful for <a href="https://arcadedb.com/orientdb.html">migrations from OrientDB</a>.</p>

<h3 id="c-end-to-end-testing">C# End-to-End Testing</h3>

<p>A new C# test suite validates ArcadeDB over the PostgreSQL wire protocol via <strong>Npgsql</strong> and <strong>Testcontainers</strong> on every build.</p>

<h3 id="studio-enhancements">Studio Enhancements</h3>

<ul>
  <li>Full-screen graph view mode</li>
  <li>Clear query button / textbox</li>
  <li>Session reset on token expiration</li>
  <li>Persistent error message display</li>
  <li>Query history no longer auto-submits</li>
  <li>Inherited indexes now visible</li>
  <li>HA cluster peer add / remove controls</li>
  <li>Human-readable peer names in <code class="language-plaintext highlighter-rouge">HA_SERVER_LIST</code></li>
</ul>

<h2 id="major-fixes">Major Fixes</h2>

<h3 id="opencypher-correctness">OpenCypher Correctness</h3>

<p>This release lands an <strong>extensive batch of OpenCypher fixes</strong> across pattern matching, write clauses, subqueries, and temporal expressions. Among the highlights:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">valueType(...)</code> now reports the <code class="language-plaintext highlighter-rouge">NOT NULL</code> suffix for non-null values.</li>
  <li><code class="language-plaintext highlighter-rouge">point(...)</code> WGS-84-3D exposes <code class="language-plaintext highlighter-rouge">.height</code> as a <code class="language-plaintext highlighter-rouge">.z</code> alias.</li>
  <li><code class="language-plaintext highlighter-rouge">CALL ... YIELD</code> preserves carried <code class="language-plaintext highlighter-rouge">WITH</code> variables.</li>
  <li>Variable-length patterns no longer re-traverse previously bound relationships.</li>
  <li><code class="language-plaintext highlighter-rouge">MERGE</code> with an unbound label-only endpoint creates fresh nodes appropriately.</li>
  <li><code class="language-plaintext highlighter-rouge">SET</code> correctly propagates across all aliases for the same node.</li>
  <li>Self-referential property updates remain idempotent across row fanout.</li>
  <li>Temporal component access on date/datetime values now works correctly.</li>
  <li><code class="language-plaintext highlighter-rouge">EXISTS { ... }</code> subqueries correctly evaluate outer-variable expressions.</li>
  <li><code class="language-plaintext highlighter-rouge">MATCH</code> immediately after <code class="language-plaintext highlighter-rouge">CREATE</code> now sees newly created labeled nodes.</li>
  <li><code class="language-plaintext highlighter-rouge">MERGE ... ON MATCH SET</code> returns post-update property values.</li>
  <li><code class="language-plaintext highlighter-rouge">MATCH</code> on parent edge types matches sub-typed edges (polymorphic traversal).</li>
  <li><code class="language-plaintext highlighter-rouge">shortestPath</code> / <code class="language-plaintext highlighter-rouge">allShortestPaths</code> with variable-length alternation match correctly.</li>
  <li><code class="language-plaintext highlighter-rouge">WHERE false</code> literal predicates are no longer ignored.</li>
</ul>

<p>…plus dozens more. See the <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.5.1">full release notes</a> for the complete list.</p>

<h3 id="sql">SQL</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CONTAINSALL</code> compares lists of <code class="language-plaintext highlighter-rouge">Identifiable</code>s against RID strings correctly.</li>
  <li>Correlated <code class="language-plaintext highlighter-rouge">COLLECT { ... }</code> / <code class="language-plaintext highlighter-rouge">COUNT { ... }</code> subqueries evaluate with outer-variable access.</li>
  <li><code class="language-plaintext highlighter-rouge">SEARCH_INDEX</code> and <code class="language-plaintext highlighter-rouge">SEARCH_FIELDS</code> propagate return values in filters and handle wildcards properly.</li>
  <li><code class="language-plaintext highlighter-rouge">SELECT</code> with a non-unique LSM index returns rows after partial deletes.</li>
  <li>Edge creation with <code class="language-plaintext highlighter-rouge">CONTENT</code> no longer ignores properties.</li>
  <li><code class="language-plaintext highlighter-rouge">algo.dijkstra</code> yields correct weight calculations.</li>
  <li><code class="language-plaintext highlighter-rouge">UPDATE EDGE SET @in / @out</code> correctly rewires vertex edge lists.</li>
  <li><code class="language-plaintext highlighter-rouge">point.withinBBox(...)</code> supports cross-meridian bounding boxes.</li>
</ul>

<h3 id="storage-indexing--schema">Storage, Indexing &amp; Schema</h3>

<ul>
  <li>HASH index lookups return rows with data encryption enabled.</li>
  <li>Orphan <code class="language-plaintext highlighter-rouge">TypeIndex</code> wrappers are dropped when the last bucket child is removed.</li>
  <li>Subclass indexes are no longer incorrectly related to superclass indexes.</li>
  <li>Manual index names are respected on creation.</li>
  <li>Inherited indexes are now visible in Studio.</li>
</ul>

<h3 id="high-availability">High Availability</h3>

<ul>
  <li>Schema changes replicate to followers, closing WAL gaps.</li>
  <li>Cluster inconsistency reports after node shutdowns resolved.</li>
  <li>Massive inserts via gRPC replicate correctly.</li>
  <li><code class="language-plaintext highlighter-rouge">/api/v1/batch</code> no longer fails on followers with “Error on updating dictionary”.</li>
  <li><code class="language-plaintext highlighter-rouge">/batch</code> endpoint eliminates HTTP 500 NPE after successful commits.</li>
  <li>e2e-ha integration tests stabilized with on-demand Toxiproxy support.</li>
</ul>

<h3 id="wire-protocols">Wire Protocols</h3>

<p><strong>PostgreSQL</strong></p>

<ul>
  <li>Empty <code class="language-plaintext highlighter-rouge">SELECT</code> results include <code class="language-plaintext highlighter-rouge">RowDescription</code> schema.</li>
  <li><code class="language-plaintext highlighter-rouge">SHOW server_version</code> returns a proper value for SQLAlchemy.</li>
  <li>Cypher <code class="language-plaintext highlighter-rouge">WHERE id(n) IN $array</code> round-trips correctly.</li>
  <li>Binary array deserialization implemented for JDBC <code class="language-plaintext highlighter-rouge">setArray</code>.</li>
  <li>Named and positional parameters now work via Npgsql (C#).</li>
</ul>

<p><strong>Bolt</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">EXPLAIN</code> / <code class="language-plaintext highlighter-rouge">PROFILE</code> plans are included in <code class="language-plaintext highlighter-rouge">PULL</code> <code class="language-plaintext highlighter-rouge">SUCCESS</code> metadata.</li>
  <li>Executor recognizes the new sparse vector type.</li>
</ul>

<p><strong>gRPC</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">InsertStream</code> throughput stays consistent after extended <code class="language-plaintext highlighter-rouge">executeQuery</code> calls.</li>
  <li>Commit-time constraint violations surface as stream-level errors.</li>
  <li><code class="language-plaintext highlighter-rouge">DATE</code> columns no longer corrupted via parameter binding.</li>
  <li><code class="language-plaintext highlighter-rouge">ARRAY_OF_LONGS</code> and <code class="language-plaintext highlighter-rouge">DATETIME</code> preserve precision in parameter binding.</li>
</ul>

<p><strong>HTTP</strong></p>

<ul>
  <li>INT8 query vectors routed via <code class="language-plaintext highlighter-rouge">$bytes</code> / <code class="language-plaintext highlighter-rouge">$int8</code> markers.</li>
  <li><code class="language-plaintext highlighter-rouge">RemoteGraphBatch</code> honors unique edge constraints.</li>
  <li>Edge <code class="language-plaintext highlighter-rouge">DATETIME</code> parser accepts ISO suffixes.</li>
</ul>

<h3 id="dependencies">Dependencies</h3>

<p>Notable upgrades include Netty 4.2.13.Final, Undertow 2.4.0.Final, PostgreSQL JDBC 42.7.11, Neo4j Java Driver 6.1.0, Jackson Databind 2.21.3, GraalVM 25.0.3, Testcontainers 2.0.5, plus Studio frontend improvements and security updates across the dependency stack.</p>

<h2 id="getting-started-with-2651">Getting Started with 26.5.1</h2>

<h3 id="docker">Docker</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull arcadedata/arcadedb:26.5.1
</code></pre></div></div>

<p>Visit our <a href="https://hub.docker.com/r/arcadedata/arcadedb">Docker Hub repository</a> for more information.</p>

<h3 id="maven">Maven</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.arcadedb<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>arcadedb-engine<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>26.5.1<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>All artifacts are available on <a href="https://repo.maven.apache.org/maven2/com/arcadedb/">Maven Central</a>.</p>

<h3 id="documentation">Documentation</h3>

<p>For detailed information on features and usage, refer to our <a href="https://docs.arcadedb.com/">comprehensive documentation</a>.</p>

<h2 id="compatibility-note">Compatibility Note</h2>

<p>This release maintains 100% compatibility with previous database formats, meaning no export/import is required when upgrading. As always, we recommend creating a database backup before upgrading.</p>

<hr />

<p><strong>Download ArcadeDB 26.5.1 now</strong>: <a href="https://github.com/ArcadeData/arcadedb/releases/tag/26.5.1">GitHub Releases</a></p>

<p>Thanks to everyone in the community who reported issues, opened PRs, and helped shape this release.</p>

<p>Luca Garulli
ArcadeDB Founder</p>]]></content><author><name>Luca Garulli</name></author><category term="Multi-Model" /><category term="Vector Search" /><category term="OpenCypher" /><category term="Graph Database" /><category term="Release" /><summary type="html"><![CDATA[ArcadeDB 26.5.1 ships a brand-new sparse vector index with server-side hybrid retrieval, INT8 quantization end-to-end, EXTERNAL property storage, query partitioning, offline HA cluster bootstrap, and an extensive batch of OpenCypher correctness fixes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arcadedb.com/assets/images/release-v26.5.1.jpg" /><media:content medium="image" url="https://arcadedb.com/assets/images/release-v26.5.1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>