<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>LoraDB Blog</title>
        <link>https://loradb.com/blog</link>
        <description>Engineering notes, architecture pieces, release notes, and design writing from the LoraDB team.</description>
        <lastBuildDate>Sat, 25 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 LoraDB.</copyright>
        <item>
            <title><![CDATA[LoraDB v0.4.0: WAL, checkpoints, and crash recovery]]></title>
            <link>https://loradb.com/blog/loradb-v0-4-0-wal</link>
            <guid>https://loradb.com/blog/loradb-v0-4-0-wal</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.4.0 adds continuous durability on every filesystem-backed surface via a write-ahead log, plus checkpoints, recovery, WAL admin routes, and simple directory-based persistent startup for embedded bindings.]]></description>
            <content:encoded><![CDATA[<p>LoraDB v0.4.0 adds a write-ahead log.</p>
<p>The engine is still in-memory and local-first. What changes in this
release is the durability boundary: on the surfaces that own a
filesystem and process lifecycle, committed writes no longer have to
live entirely in RAM between two manual snapshots.</p>
<p>The shortest mental model:</p>
<ul>
<li><code>createDatabase()</code> in Node is still a fresh in-memory graph.</li>
<li><code>createDatabase("application", { databaseDir: "./data" })</code> opens a
persistent archive-backed graph at <code>./data/application.loradb</code>.</li>
<li><code>Database.create("app", {"database_dir": "./data"})</code>, <code>lora.New("app", lora.Options{DatabaseDir: "./data"})</code>, and
<code>LoraRuby::Database.create("app", {"database_dir": "./data"})</code> do the same thing on Python,
Go, and Ruby.</li>
<li><code>lora-server --wal-dir /var/lib/lora/wal</code> turns the HTTP server into
a WAL-backed process.</li>
<li>Rust gets the full open, recover, checkpoint, and sync-mode surface.</li>
</ul>
<p>Snapshots do not go away. They stay the portable file you can back up,
ship, and restore elsewhere. v0.4.0 makes them stronger by giving them
something to checkpoint against.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-ships-in-v040">What ships in v0.4.0<a href="https://loradb.com/blog/loradb-v0-4-0-wal#what-ships-in-v040" class="hash-link" aria-label="Direct link to What ships in v0.4.0" title="Direct link to What ships in v0.4.0">​</a></h2>
<table><thead><tr><th>Surface</th><th>New durability surface</th></tr></thead><tbody><tr><td>Rust (<code>lora-database</code>)</td><td><code>Database::open_with_wal(...)</code>, <code>Database::recover(...)</code>, <code>Database::checkpoint_to(...)</code>, <code>WalConfig</code>, <code>SyncMode</code></td></tr><tr><td>Node (<code>@loradb/lora-node</code>)</td><td><code>await createDatabase(name, { databaseDir })</code> for simple persistent embedded graphs with automatic replay on reopen</td></tr><tr><td>Python (<code>lora_python</code>)</td><td><code>Database.create(name, {"database_dir": dir})</code>, <code>Database(name, {"database_dir": dir})</code>, and <code>await AsyncDatabase.create(name, {"database_dir": dir})</code> for simple persistent embedded graphs with automatic replay on reopen</td></tr><tr><td>Go (<code>lora-go</code>)</td><td><code>lora.New(name, lora.Options{DatabaseDir: dir})</code> and <code>lora.NewDatabase(name, lora.Options{DatabaseDir: dir})</code> for simple persistent embedded graphs with automatic replay on reopen</td></tr><tr><td>Ruby (<code>lora-ruby</code>)</td><td><code>LoraRuby::Database.create(name, { database_dir: dir })</code> and <code>LoraRuby::Database.new(name, { database_dir: dir })</code> for simple persistent embedded graphs with automatic replay on reopen</td></tr><tr><td>HTTP server (<code>lora-server</code>)</td><td><code>--wal-dir</code>, <code>--wal-sync-mode</code>, <code>--restore-from</code>, <code>POST /admin/checkpoint</code>, <code>POST /admin/wal/status</code>, <code>POST /admin/wal/truncate</code></td></tr><tr><td>Every binding</td><td>Snapshot save / load stays available exactly as before</td></tr></tbody></table>
<p>That split is intentional. Rust and <code>lora-server</code> expose the full
operator surface. The embedded bindings get the smallest ergonomic
thing that is useful for local apps: pass a database name plus a directory
when you want persistence, omit the directory when you want a fresh
in-memory graph.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-node-shape-is-deliberately-simple">The Node shape is deliberately simple<a href="https://loradb.com/blog/loradb-v0-4-0-wal#the-node-shape-is-deliberately-simple" class="hash-link" aria-label="Direct link to The Node shape is deliberately simple" title="Direct link to The Node shape is deliberately simple">​</a></h2>
<p>The new Node API is meant to read like the difference between a scratch
database and a durable one:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-node'</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> scratch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">                         </span><span class="token comment">// in-memory</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token string">"application"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  databaseDir</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"./data"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">                                                            </span><span class="token comment">// ./data/application.loradb</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Reopen the same directory later:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-node'</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token string">"application"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> databaseDir</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"./data"</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> rows </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token string">"MATCH (p:Person) RETURN p.name AS name"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The name is resolved inside <code>databaseDir</code> as a <code>.loradb</code> archive. Relative
paths resolve from the current working directory. The first slice uses the
engine defaults:</p>
<ul>
<li><code>SyncMode::PerCommit</code></li>
<li><code>8 MiB</code> target segment size</li>
</ul>
<p>Node does <strong>not</strong> expose WAL status, checkpoint, truncate, or sync-mode
knobs yet. If you need that operator surface today, use Rust directly
or run <code>lora-server</code>.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-the-wal-actually-guarantees">What the WAL actually guarantees<a href="https://loradb.com/blog/loradb-v0-4-0-wal#what-the-wal-actually-guarantees" class="hash-link" aria-label="Direct link to What the WAL actually guarantees" title="Direct link to What the WAL actually guarantees">​</a></h2>
<p>This is not "marketing durability." The contract is concrete:</p>
<ul>
<li>Every mutating query is logged before the call returns.</li>
<li>Read-only queries write nothing to the WAL.</li>
<li>Recovery replays only committed writes, in commit order.</li>
<li>A torn tail on the active segment is truncated back to the last
valid record.</li>
<li>A checkpoint writes a snapshot stamped with <code>walLsn</code>, appends a
checkpoint marker, and truncates safe WAL history up to that fence.</li>
</ul>
<p>That means the engine now has a clean recovery staircase:</p>
<ol>
<li>pure in-memory,</li>
<li>snapshot-only,</li>
<li>WAL-only,</li>
<li>snapshot + WAL checkpointing.</li>
</ol>
<p>The important part is that each step is readable. The release does not
blur "we can save a file" together with "we can recover committed
writes." Those are different guarantees, and the docs now say so.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="lora-server-grows-into-an-operator-surface"><code>lora-server</code> grows into an operator surface<a href="https://loradb.com/blog/loradb-v0-4-0-wal#lora-server-grows-into-an-operator-surface" class="hash-link" aria-label="Direct link to lora-server-grows-into-an-operator-surface" title="Direct link to lora-server-grows-into-an-operator-surface">​</a></h2>
<p>The HTTP server picks up the real production-adjacent durability knobs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain"># Fresh boot with a WAL.</span><br></span><span class="token-line"><span class="token plain">lora-server --wal-dir /var/lib/lora/wal</span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"># Snapshot + WAL recovery.</span><br></span><span class="token-line"><span class="token plain">lora-server \</span><br></span><span class="token-line"><span class="token plain">  --wal-dir /var/lib/lora/wal \</span><br></span><span class="token-line"><span class="token plain">  --snapshot-path /var/lib/lora/graph.bin \</span><br></span><span class="token-line"><span class="token plain">  --restore-from /var/lib/lora/graph.bin</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>And the admin routes that make the log inspectable and manageable:</p>
<ul>
<li><code>POST /admin/wal/status</code></li>
<li><code>POST /admin/wal/truncate</code></li>
<li><code>POST /admin/checkpoint</code></li>
</ul>
<p>There are also sync modes now:</p>
<table><thead><tr><th>Mode</th><th>Meaning</th></tr></thead><tbody><tr><td><code>per-commit</code></td><td><code>fsync</code> before each commit returns</td></tr><tr><td><code>group</code></td><td>buffer commits and flush in the background</td></tr><tr><td><code>none</code></td><td>no <code>fsync</code>; rely on the OS or external durability</td></tr></tbody></table>
<p>The server is still honest about its boundary: one process, one graph,
no auth, no TLS, no multi-database routing. The durability story is
stronger, but it is still a small local system, not a hosted graph
service in disguise.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-did-not-change">What did not change<a href="https://loradb.com/blog/loradb-v0-4-0-wal#what-did-not-change" class="hash-link" aria-label="Direct link to What did not change" title="Direct link to What did not change">​</a></h2>
<p>v0.4.0 is a durability release, not a reinvention of the product:</p>
<ul>
<li>LoraDB is still an in-memory engine.</li>
<li>Snapshots are still manual and explicit.</li>
<li>There is still no automatic checkpoint loop.</li>
<li>Node, Python, Go, and Ruby still expose the simple archive-backed open
path only; full checkpoint, truncate, status, and sync-mode controls
still live on Rust and <code>lora-server</code>.</li>
<li>WASM stays snapshot-only for now.</li>
<li>The HTTP admin surface is still unauthenticated and meant to live
behind your own ingress.</li>
</ul>
<p>That last point matters. The new admin routes are useful, but only when
deployed behind a boundary you control.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-docs-changed-with-the-product">The docs changed with the product<a href="https://loradb.com/blog/loradb-v0-4-0-wal#the-docs-changed-with-the-product" class="hash-link" aria-label="Direct link to The docs changed with the product" title="Direct link to The docs changed with the product">​</a></h2>
<p>This release also forced a documentation cleanup across the website.
The main fixes:</p>
<ul>
<li>the embedded-binding docs now state the initialization rule
explicitly: no argument means in-memory, and name plus directory means
persistent;</li>
<li>the HTTP server and API docs no longer describe a pre-WAL world;</li>
<li>snapshots are now documented as a standalone primitive that can also
act as a checkpoint target;</li>
<li>there is a dedicated WAL page instead of scattering durability
details across unrelated guides.</li>
</ul>
<p>The result is that the website now answers the operational questions in
the same place the release introduces them.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="read-next">Read next<a href="https://loradb.com/blog/loradb-v0-4-0-wal#read-next" class="hash-link" aria-label="Direct link to Read next" title="Direct link to Read next">​</a></h2>
<ul>
<li><a href="https://loradb.com/docs/wal">WAL and checkpoints</a></li>
<li><a href="https://loradb.com/docs/snapshot">Snapshots</a></li>
<li><a href="https://loradb.com/docs/getting-started/node">Node guide</a></li>
<li><a href="https://loradb.com/docs/getting-started/server">HTTP server quickstart</a></li>
<li><a href="https://loradb.com/docs/api/http">HTTP API reference</a></li>
</ul>
<p>v0.3 made "save the graph to one file" real. v0.4.0 makes "reopen the
process and keep committed writes" real. That is the release.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Persistence</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[Snapshots before a log]]></title>
            <link>https://loradb.com/blog/snapshots-before-a-log</link>
            <guid>https://loradb.com/blog/snapshots-before-a-log</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB v0.3 ships manual point-in-time snapshots before any write-ahead log, what that primitive teaches, and how durability gets layered on top of it.]]></description>
            <content:encoded><![CDATA[<p>Most databases I have worked with had a write-ahead log before they had a
snapshot story. LoraDB went the other way.</p>
<p>v0.3 ships manual point-in-time snapshots and nothing else on the
persistence side. No append-only log. No background checkpoint loop. No
continuous durability. One file on disk, taken on demand, atomic on
rename.</p>
<p>The order is intentional, and it is the kind of decision that is easier
to defend before the release than after.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-default-narrative-for-adding-persistence">The Default Narrative For "Adding Persistence"<a href="https://loradb.com/blog/snapshots-before-a-log#the-default-narrative-for-adding-persistence" class="hash-link" aria-label="Direct link to The Default Narrative For &quot;Adding Persistence&quot;" title="Direct link to The Default Narrative For &quot;Adding Persistence&quot;">​</a></h2>
<p>The default narrative for adding persistence to an in-memory database is
some version of:</p>
<ol>
<li>ship a write-ahead log,</li>
<li>background checkpoints flush state to disk,</li>
<li>on boot, replay the log on top of the latest checkpoint,</li>
<li>announce "durable."</li>
</ol>
<p>That is what mature databases do, eventually. It is also the wrong
<em>first</em> step for a project where the data model and the storage tier are
both still settling.</p>
<p>A WAL is a long-term commitment to a concrete write path. Every mutation
has to know how to serialize itself. Every recovery routine has to
dispatch on event type. Every release after the first one inherits the
log format, the recovery state machine, and the assumptions baked into
both. Get any of that wrong on day one and the project carries the
mistake forward — or pays for an expensive migration to fix it.</p>
<p>For a database that is two minor versions old and still figuring out
what its read and write paths look like, that is too much surface area
to commit to.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-a-snapshot-teaches-that-a-wal-would-not">What A Snapshot Teaches That A WAL Would Not<a href="https://loradb.com/blog/snapshots-before-a-log#what-a-snapshot-teaches-that-a-wal-would-not" class="hash-link" aria-label="Direct link to What A Snapshot Teaches That A WAL Would Not" title="Direct link to What A Snapshot Teaches That A WAL Would Not">​</a></h2>
<p>A snapshot is the lowest-risk way to learn the shape of "the graph as a
serialized artifact." It forces the project to answer a small set of
concrete questions:</p>
<ul>
<li>What does the file format look like? <code>LORASNAP</code> magic, format version,
reserved header bits, bincode payload, CRC32 footer.</li>
<li>How is the write atomic? <code>&lt;path&gt;.tmp</code>, <code>fsync</code>, rename over the
target.</li>
<li>How is the read atomic? Hold the store mutex, validate, swap the
graph in one shot.</li>
<li>What does the API look like across every binding? <code>save_snapshot</code>,
<code>load_snapshot</code>, <code>in_memory_from_snapshot</code>, plus an opt-in HTTP admin
surface.</li>
<li>What does every binding return? A single <code>SnapshotMeta</code> shape with
<code>formatVersion</code>, <code>nodeCount</code>, <code>relationshipCount</code>, and a reserved
<code>walLsn</code>.</li>
<li>What does the operator contract look like? <code>--snapshot-path</code>,
<code>--restore-from</code>, off-by-default admin endpoints, no auth, behind
ingress only.</li>
</ul>
<p>None of those answers go away when a WAL eventually arrives. A
checkpoint, by definition, is a snapshot with a WAL LSN attached. The
header already reserves the slot. The reader already accepts files
where the flag is set. The day the WAL ships, the file format does not
change — the LSN field stops being null, and the recovery logic learns
to replay from the log past it.</p>
<p>In other words, the snapshot work is not throwaway scaffolding. It is
the foundation a future log sits on.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-a-snapshot-is-honest-about">What A Snapshot Is Honest About<a href="https://loradb.com/blog/snapshots-before-a-log#what-a-snapshot-is-honest-about" class="hash-link" aria-label="Direct link to What A Snapshot Is Honest About" title="Direct link to What A Snapshot Is Honest About">​</a></h2>
<p>A snapshot is not durability. It is point-in-time persistence. The
difference matters.</p>
<ul>
<li>A crash between two saves loses every mutation in the window.</li>
<li>The store mutex is held for the duration of both save and load.
Concurrent queries block.</li>
<li>There is no incremental save. The whole graph serializes each time.</li>
<li>There is no auto-cadence. Saves happen because someone called
<code>save_snapshot</code> or hit the admin endpoint.</li>
</ul>
<p>That set of caveats is also exactly what makes the primitive useful
right now without overcommitting. A single-node service, a notebook, a
seeded process, a service with a controlled shutdown window, a backup
cron — all of those need the property "the graph as of now, written to
one file." None of them need a continuous log to be useful.</p>
<p>The shapes that genuinely need a WAL — multi-node clusters, zero-data-loss
writes, mid-second crash recovery — are not the shapes LoraDB is good at
today. Building a half-finished log inside a single-process engine ends
up with a journal that is less reliable than just snapshotting more
often, and worse, with a contract that is harder to read.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-honest-boundaries-matter-more-than-marketing">Why Honest Boundaries Matter More Than Marketing<a href="https://loradb.com/blog/snapshots-before-a-log#why-honest-boundaries-matter-more-than-marketing" class="hash-link" aria-label="Direct link to Why Honest Boundaries Matter More Than Marketing" title="Direct link to Why Honest Boundaries Matter More Than Marketing">​</a></h2>
<p>The thing I see most often in databases that overpromised on durability
is silent data loss between two undocumented seams. That happens when
"persistent" is sold as a complete story before the moving parts have
settled — when there is durability marketing language without a clean
operator contract underneath.</p>
<p>The contract I want for snapshots is small enough to fit on a card:</p>
<ul>
<li>The save renames <code>&lt;path&gt;.tmp</code> over <code>&lt;path&gt;</code>. A crash mid-save can leave
the <code>.tmp</code> file behind. It cannot leave a half-written <code>&lt;path&gt;</code>.</li>
<li>The load swaps the live graph in one shot. Concurrent queries see the
old graph or the new one. Never both. Never a partial.</li>
<li>A crash between two saves loses every mutation in the window. Pick a
cadence accordingly.</li>
<li>The HTTP admin endpoints are off by default. They have no
authentication. They are intended to sit behind your ingress.</li>
</ul>
<p>That is what v0.3 ships. It is the smallest set of guarantees that
actually mean what they say. None of them are advertised more
aggressively than they are documented.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="where-the-persistence-story-goes-next">Where The Persistence Story Goes Next<a href="https://loradb.com/blog/snapshots-before-a-log#where-the-persistence-story-goes-next" class="hash-link" aria-label="Direct link to Where The Persistence Story Goes Next" title="Direct link to Where The Persistence Story Goes Next">​</a></h2>
<p>Three steps line up against the boundary above, in order.</p>
<p><strong>A write-ahead log.</strong> The snapshot header already reserves the LSN.
The mutation event vocabulary already exists in <code>lora-store</code> —
<code>MutationRecorder</code> is a no-op observer today, and the <code>MutationEvent</code>
enum carries one variant per <code>GraphStorageMut</code> method. That is the
vocabulary the log will append to. When it ships, the snapshot file
format does not change; the reader keeps loading today's v1 files. A
checkpoint becomes "a snapshot with a meaningful <code>walLsn</code>," exactly the
shape the reader was written for.</p>
<p><strong>A checkpoint loop.</strong> Once the log exists, the engine can fold
snapshots and the log together in the background. The operator stops
having to time saves themselves. The trigger should be
throughput-aware, not wall-clock — saving a graph that has barely
changed is wasted I/O.</p>
<p><strong>Auth on the admin surface.</strong> Token-based auth in front of <code>/admin/*</code>
so the endpoints can be enabled on hosts that face a real network
without an external reverse proxy. Hosted operations come after that,
not before — the moment to charge people to run LoraDB for them is the
moment its operator contract is durable enough to charge for.</p>
<p>There is a fourth thread that is less visible but matters more: the
contract should stay easy to read while it grows. Each step adds
capability without adding ambiguity. A WAL should not turn "durable"
into a fuzzy word; it should turn the existing snapshot contract into
a strictly stronger one.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="how-this-fits-the-customer-journey">How This Fits The Customer Journey<a href="https://loradb.com/blog/snapshots-before-a-log#how-this-fits-the-customer-journey" class="hash-link" aria-label="Direct link to How This Fits The Customer Journey" title="Direct link to How This Fits The Customer Journey">​</a></h2>
<p>The persistence staircase mirrors the adoption staircase.</p>
<ol>
<li><strong>Discovery.</strong> A developer runs <code>cargo run --bin lora-server</code> and
types a query. There is no persistence to think about yet.</li>
<li><strong>Local prototype.</strong> They want to keep the graph between sessions.
<code>--snapshot-path</code> and <code>--restore-from</code> are enough.</li>
<li><strong>Internal service.</strong> They want graceful-shutdown saves and
scheduled backups. <code>POST /admin/snapshot/save</code> from a systemd unit
or a cron is enough.</li>
<li><strong>Production with tighter SLAs.</strong> They need continuous durability —
point-in-time recovery to the last second, not the last save. That
is when the WAL lands.</li>
<li><strong>Managed operations.</strong> They do not want to operate the engine at
all. That is when the hosted platform takes over the snapshot
cadence, the WAL config, and the auth boundary.</li>
</ol>
<p>Each step adds capability the previous step's users do not regress on.
That is the point of building from a snapshot up rather than a WAL
down.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-feedback-that-will-shape-v04">The Feedback That Will Shape v0.4<a href="https://loradb.com/blog/snapshots-before-a-log#the-feedback-that-will-shape-v04" class="hash-link" aria-label="Direct link to The Feedback That Will Shape v0.4" title="Direct link to The Feedback That Will Shape v0.4">​</a></h2>
<p>The clearest signal for whether v0.3 lands is concrete:</p>
<ul>
<li>how big does your graph get, and how long does <code>save_snapshot</code> take
at that size;</li>
<li>what cadence did you settle on — seconds, minutes, on-shutdown only,
every-N-mutations;</li>
<li>did atomic rename land cleanly on your filesystem (we test on Linux
ext4/xfs and macOS APFS);</li>
<li>which binding did you use, and did the WASM byte-oriented surface
fit your storage layer (IndexedDB, OPFS, a backend POST) without
extra glue;</li>
<li>what does your ingress look like for the admin endpoints — reverse
proxy, Unix socket, or "not exposed at all";</li>
<li>where did the lack of a WAL stop being acceptable for your workload?</li>
</ul>
<p>The answers decide what cadence the WAL has to support, which crash
windows we need to harden first, and which surfaces need the auth
contract before the rest.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="closing">Closing<a href="https://loradb.com/blog/snapshots-before-a-log#closing" class="hash-link" aria-label="Direct link to Closing" title="Direct link to Closing">​</a></h2>
<p>LoraDB's persistence story is not "we shipped a quarter of a WAL." It
is "we shipped the smallest persistence primitive that means what it
says, and the next steps build on top of it without changing what we
already promised."</p>
<p>Snapshots before a log is the order I would pick if I had to do this
again. The right first step toward durability is not a journal. It is
a contract.</p>
<hr>
<p>Canonical references:</p>
<ul>
<li><a href="https://loradb.com/docs/snapshot">Snapshots</a> — file format, atomic-rename
protocol, binding examples, and the security warning on the admin
surface.</li>
<li><a href="https://loradb.com/docs/getting-started/server#snapshots-wal-and-restore">HTTP server quickstart → Snapshots, WAL, and restore</a>
— <code>--snapshot-path</code> and <code>--restore-from</code> in context.</li>
<li><a href="https://loradb.com/blog/loradb-v0-3-snapshots">v0.3 release notes</a> — the team-side
announcement, the full binding table, and the explicit list of what
is still out of scope.</li>
</ul>]]></content:encoded>
            <category>Founder notes</category>
            <category>Persistence</category>
            <category>Design</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.3: snapshots for saving and restoring graph state]]></title>
            <link>https://loradb.com/blog/loradb-v0-3-snapshots</link>
            <guid>https://loradb.com/blog/loradb-v0-3-snapshots</guid>
            <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.3 adds manual point-in-time snapshots — a single-file dump of the in-memory graph, atomic on rename, restorable at boot or on demand, exposed through every binding and the HTTP admin surface.]]></description>
            <content:encoded><![CDATA[<p>LoraDB v0.3 adds manual point-in-time snapshots.</p>
<p>You can now dump the entire in-memory graph to a single file and
restore it later. The save is atomic on rename, the load replaces the
live graph in one shot, and the feature is exposed on every surface
that the engine talks through — the Rust core, the Python, Node, WASM,
Go, and Ruby bindings, the shared C FFI, and the HTTP server as an
opt-in admin endpoint.</p>
<p>What this release is <strong>not</strong> is full persistence. There is no
write-ahead log, no background checkpoint loop, no continuous
durability. A snapshot is exactly what the name says: a point-in-time
dump you take on demand. Data mutated between two saves is lost on
crash. That boundary is deliberate — making the explicit, operator-
controlled shape work cleanly is the foundation a WAL will sit on, and
it closes the "no persistence at all" gap for the workloads that only
need occasional checkpoints today (seeded services, notebooks,
controlled shutdowns, scheduled backups).</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-changed">What Changed<a href="https://loradb.com/blog/loradb-v0-3-snapshots#what-changed" class="hash-link" aria-label="Direct link to What Changed" title="Direct link to What Changed">​</a></h2>
<p>The short list:</p>
<ul>
<li>A new single-file snapshot format (<code>LORASNAP</code> magic, format version
<code>1</code>, bincode-serialized payload, CRC32 footer).</li>
<li>Atomic saves — writes go to <code>&lt;path&gt;.tmp</code>, are <code>fsync</code>'d, and then
renamed over the target. A crashed save never leaves a half-written
file at the target path.</li>
<li>Atomic loads — the store mutex is held for the full restore, so
concurrent queries see the old or the new graph, never a partial
one.</li>
<li>Reserved header space for a future WAL/checkpoint hybrid
(<code>walLsn</code> / <code>has_wal_lsn</code>); pure snapshots emit it as null today.</li>
<li>Forward-compatible reader — formats are dispatched by version, so
today's v1 files will keep loading after the next format bump until
support is deliberately dropped.</li>
<li>Snapshot metadata (<code>formatVersion</code>, <code>nodeCount</code>,
<code>relationshipCount</code>, <code>walLsn</code>) returned from every save and load.</li>
</ul>
<p>Binding support that actually exists in v0.3:</p>
<table><thead><tr><th>Surface</th><th>Save</th><th>Load</th><th>Shape</th></tr></thead><tbody><tr><td>Rust (<code>lora-database</code>)</td><td><code>save_snapshot_to(path)</code></td><td><code>load_snapshot_from(path)</code>, <code>in_memory_from_snapshot(path)</code></td><td>file path</td></tr><tr><td>Python (sync <code>Database</code>)</td><td><code>save_snapshot(path)</code></td><td><code>load_snapshot(path)</code></td><td>file path</td></tr><tr><td>Python (<code>AsyncDatabase</code>)</td><td><code>await save_snapshot(path)</code></td><td><code>await load_snapshot(path)</code></td><td>file path</td></tr><tr><td>Node.js (<code>@loradb/lora-node</code>)</td><td><code>await saveSnapshot(path)</code></td><td><code>await loadSnapshot(path)</code></td><td>file path</td></tr><tr><td>WebAssembly (<code>@loradb/lora-wasm</code>)</td><td><code>await saveSnapshotToBytes()</code></td><td><code>await loadSnapshotFromBytes(bytes)</code></td><td><code>Uint8Array</code></td></tr><tr><td>Go (<code>lora-go</code>)</td><td><code>db.SaveSnapshot(path)</code></td><td><code>db.LoadSnapshot(path)</code></td><td>file path</td></tr><tr><td>Ruby (<code>lora-ruby</code>)</td><td><code>db.save_snapshot(path)</code></td><td><code>db.load_snapshot(path)</code></td><td>file path</td></tr><tr><td>C FFI (<code>lora-ffi</code>)</td><td><code>lora_db_save_snapshot(handle, path, ...)</code></td><td><code>lora_db_load_snapshot(handle, path, ...)</code></td><td>file path</td></tr><tr><td>HTTP server (<code>lora-server</code>)</td><td><code>POST /admin/snapshot/save</code></td><td><code>POST /admin/snapshot/load</code></td><td>file path on the server's disk</td></tr></tbody></table>
<p>WebAssembly is byte-oriented by design — WASM has no filesystem, so
the caller is responsible for persisting the <code>Uint8Array</code> to IndexedDB,
localStorage, <code>fs.writeFileSync</code>, a backend upload, or wherever their
app already stores state.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-snapshots-matter">Why Snapshots Matter<a href="https://loradb.com/blog/loradb-v0-3-snapshots#why-snapshots-matter" class="hash-link" aria-label="Direct link to Why Snapshots Matter" title="Direct link to Why Snapshots Matter">​</a></h2>
<p>The v0.1 and v0.2 model was "one process, one in-memory graph, lost on
exit." That is fine for notebooks, tests, demos, and embedded
read-mostly caches, but it forces every operator into one of two
patterns neither of which the engine supported well:</p>
<ul>
<li><strong>Reload from source on every boot.</strong> Works if the source is cheap,
but adds real seeding time on restart and pushes reload logic into
every deployment.</li>
<li><strong>Rebuild a parallel persistence layer.</strong> The application writes
every mutation to Lan external store, then replays it on boot. A
second data model to maintain, a second consistency story.</li>
</ul>
<p>Neither is what you want for the shape of workload LoraDB is actually
good at: a graph view over data the host process already owns, or a
small seeded context that the agent / service accumulates in memory.
For those, the right primitive is a file on disk that captures "the
graph as of this moment" — cheap to take, cheap to restore, no second
data model.</p>
<p>That is what v0.3 ships. The Cypher surface does not change; the
storage tier gets one new verb (<code>save_snapshot</code>), one new verse
(<code>load_snapshot</code>), and one new file on disk.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-a-snapshot-is-not">What A Snapshot Is Not<a href="https://loradb.com/blog/loradb-v0-3-snapshots#what-a-snapshot-is-not" class="hash-link" aria-label="Direct link to What A Snapshot Is Not" title="Direct link to What A Snapshot Is Not">​</a></h2>
<p>Same list as above, stated as the bright line:</p>
<ul>
<li><strong>Not continuous durability.</strong> A crash between two saves loses every
mutation in the window. If you need zero data loss, you need a WAL;
LoraDB does not have one yet.</li>
<li><strong>Not a checkpoint loop.</strong> Nothing schedules saves for you. The host
process, an external cron, or the admin HTTP endpoint decides when a
save happens.</li>
<li><strong>Not a general persistent storage tier.</strong> There is no storage
backend other than the in-memory graph; the snapshot is a dump of
that graph, not a format a different engine writes into.</li>
<li><strong>Not zero-cost at save time.</strong> The store mutex is held for the
duration of the save. Concurrent queries wait. Pick a snapshot
cadence that leaves headroom.</li>
<li><strong>Not a boundary for multi-tenancy.</strong> One process still holds one
graph; each process needs its own snapshot path.</li>
</ul>
<p>Those are not roadmap omissions hidden behind marketing language. They
are what "simple, explicit, operator-controlled" means.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="using-snapshots">Using Snapshots<a href="https://loradb.com/blog/loradb-v0-3-snapshots#using-snapshots" class="hash-link" aria-label="Direct link to Using Snapshots" title="Direct link to Using Snapshots">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="save-and-load-from-rust">Save and load from Rust<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-rust" class="hash-link" aria-label="Direct link to Save and load from Rust" title="Direct link to Save and load from Rust">​</a></h3>
<p>The reference surface. Every other binding wraps these two methods.</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">use</span><span class="token plain"> </span><span class="token namespace">lora_database</span><span class="token namespace punctuation">::</span><span class="token class-name">Database</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Database</span><span class="token punctuation">::</span><span class="token function">in_memory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">None</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// Dump the full graph to disk.</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> meta </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">save_snapshot_to</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token macro property">println!</span><span class="token punctuation">(</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token string">"{} nodes, {} relationships"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    meta</span><span class="token punctuation">.</span><span class="token plain">node_count</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">relationship_count</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// Boot a fresh Database directly from the file.</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> db2 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Database</span><span class="token punctuation">::</span><span class="token function">in_memory_from_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// Or restore onto an existing handle (concurrent queries block on the</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// store mutex for the duration of the load).</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token function">load_snapshot_from</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Every save and load returns a <code>SnapshotMeta</code>:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"formatVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"nodeCount"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1024</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"relationshipCount"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">4096</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"walLsn"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword">null</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The <code>walLsn</code> field is reserved for the future WAL/checkpoint hybrid
and is always <code>null</code> for today's pure snapshots.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="save-and-load-from-python">Save and load from Python<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-python" class="hash-link" aria-label="Direct link to Save and load from Python" title="Direct link to Save and load from Python">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">from</span><span class="token plain"> lora_python </span><span class="token keyword">import</span><span class="token plain"> Database</span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">db </span><span class="token operator">=</span><span class="token plain"> Database</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token plain">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">meta </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token plain">save_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token plain">meta</span><span class="token punctuation">[</span><span class="token string">"nodeCount"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">[</span><span class="token string">"relationshipCount"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">db2 </span><span class="token operator">=</span><span class="token plain"> Database</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">db2</span><span class="token punctuation">.</span><span class="token plain">load_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The <code>AsyncDatabase</code> wrapper exposes the same two methods as
coroutines:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> asyncio</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">from</span><span class="token plain"> lora_python </span><span class="token keyword">import</span><span class="token plain"> AsyncDatabase</span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">async</span><span class="token plain"> </span><span class="token keyword">def</span><span class="token plain"> </span><span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> AsyncDatabase</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token plain">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token plain">save_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">asyncio</span><span class="token punctuation">.</span><span class="token plain">run</span><span class="token punctuation">(</span><span class="token plain">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Both forms run with the GIL released / on a worker thread so the event
loop stays free during large saves.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="save-and-load-from-node--typescript">Save and load from Node / TypeScript<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-node--typescript" class="hash-link" aria-label="Direct link to Save and load from Node / TypeScript" title="Direct link to Save and load from Node / TypeScript">​</a></h3>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-node'</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> meta </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">saveSnapshot</span><span class="token punctuation">(</span><span class="token string">'graph.bin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">meta</span><span class="token punctuation">.</span><span class="token plain">nodeCount</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">relationshipCount</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db2 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">loadSnapshot</span><span class="token punctuation">(</span><span class="token string">'graph.bin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><code>saveSnapshot</code> / <code>loadSnapshot</code> return Promises that resolve to a
<code>SnapshotMeta</code> object with the same <code>formatVersion</code> / <code>nodeCount</code> /
<code>relationshipCount</code> / <code>walLsn</code> fields as every other binding.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="save-and-load-from-webassembly">Save and load from WebAssembly<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-webassembly" class="hash-link" aria-label="Direct link to Save and load from WebAssembly" title="Direct link to Save and load from WebAssembly">​</a></h3>
<p>WASM has no filesystem, so the snapshot API is byte-in / byte-out:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-wasm'</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// Dump the graph to a Uint8Array.</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> bytes</span><span class="token operator">:</span><span class="token plain"> Uint8Array </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">saveSnapshotToBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// Persist the bytes wherever you already store state — IndexedDB,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// localStorage, a POST to your backend, `fs.writeFileSync` in Node.</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token comment">// Later:</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db2 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">loadSnapshotFromBytes</span><span class="token punctuation">(</span><span class="token plain">bytes</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The Worker-backed surface (<code>createWorkerDatabase</code>) does not yet plumb
snapshots through the worker protocol — for snapshotting from a
browser worker today, call <code>saveSnapshotToBytes</code> in-process in the
worker and post the bytes back to the main thread yourself. In-process
WASM (<code>createDatabase</code>) supports snapshots on both the Node and
bundler targets.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="save-and-load-from-go">Save and load from Go<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-go" class="hash-link" aria-label="Direct link to Save and load from Go" title="Direct link to Save and load from Go">​</a></h3>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> lora </span><span class="token string">"github.com/lora-db/lora/crates/lora-go"</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">db</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> lora</span><span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">defer</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> </span><span class="token boolean">_</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">meta</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">SaveSnapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">fmt</span><span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"nodes=%d rels=%d\n"</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">NodeCount</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">RelationshipCount</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">db2</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> lora</span><span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">defer</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> </span><span class="token boolean">_</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">LoadSnapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The Go FFI header (<code>crates/lora-go/include/lora_ffi.h</code>) now declares
<code>lora_db_save_snapshot</code> / <code>lora_db_load_snapshot</code> alongside a
<code>LoraSnapshotMeta</code> struct; the Go wrapper turns that into an idiomatic
<code>*SnapshotMeta</code> with a nullable <code>WalLsn</code> pointer.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="restoring-and-saving-through-the-http-server">Restoring And Saving Through The HTTP Server<a href="https://loradb.com/blog/loradb-v0-3-snapshots#restoring-and-saving-through-the-http-server" class="hash-link" aria-label="Direct link to Restoring And Saving Through The HTTP Server" title="Direct link to Restoring And Saving Through The HTTP Server">​</a></h2>
<p><code>lora-server</code> exposes two opt-in admin endpoints for snapshot
operations. They do not exist unless the server is started with
<code>--snapshot-path</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">lora-server \</span><br></span><span class="token-line"><span class="token plain">  --host 127.0.0.1 --port 4747 \</span><br></span><span class="token-line"><span class="token plain">  --snapshot-path /var/lib/lora/db.bin \</span><br></span><span class="token-line"><span class="token plain">  --restore-from  /var/lib/lora/db.bin</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ul>
<li><code>--snapshot-path &lt;PATH&gt;</code> mounts <code>POST /admin/snapshot/save</code> and
<code>POST /admin/snapshot/load</code> against this file. Without the flag the
routes return <code>404</code> — the admin surface is off by default.</li>
<li><code>--restore-from &lt;PATH&gt;</code> loads a snapshot at boot before the server
accepts queries. A missing file is fine (empty graph, logged); a
malformed file is fatal.</li>
</ul>
<p>Once enabled, saving and restoring is a plain HTTP call:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/save</span><br></span><span class="token-line"><span class="token plain"># =&gt; {"formatVersion":1,"nodeCount":1024,"relationshipCount":4096,"walLsn":null,"path":"/var/lib/lora/db.bin"}</span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/load</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Both endpoints accept an optional <code>{ "path": "…" }</code> body to override
the configured default for a single request — useful for ad-hoc
backups to a rotated filename:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/save \</span><br></span><span class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></span><span class="token-line"><span class="token plain">  -d '{"path": "/var/backups/lora/2026-04-24.bin"}'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><code>--restore-from</code> is independent of <code>--snapshot-path</code>. You can restore
from a read-only seed and save to a writable runtime path:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">lora-server \</span><br></span><span class="token-line"><span class="token plain">  --restore-from  /var/lib/lora/seed.bin \</span><br></span><span class="token-line"><span class="token plain">  --snapshot-path /var/lib/lora/runtime.bin</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Security</div><div class="admonitionContent_BuS1"><p>The admin endpoints have <strong>no authentication</strong>, and the optional
<code>path</code> body field is passed straight to the OS. Any client that can
reach the admin port can write files anywhere the server UID can
write, or swap the live graph by pointing <code>load</code> at an attacker-staged
file. Do not expose the admin surface on a network-reachable host
without authenticated ingress in front (a reverse proxy with auth, a
Unix socket, or simply not binding the port at all). Future releases
may add authentication; until then, the correct deployment is "admin
surface disabled by default, enabled only behind an auth boundary".</p></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-snapshots-are-useful-even-without-a-wal">Why Snapshots Are Useful Even Without A WAL<a href="https://loradb.com/blog/loradb-v0-3-snapshots#why-snapshots-are-useful-even-without-a-wal" class="hash-link" aria-label="Direct link to Why Snapshots Are Useful Even Without A WAL" title="Direct link to Why Snapshots Are Useful Even Without A WAL">​</a></h2>
<p>A snapshot is not a replacement for continuous durability, but it
closes enough of the gap for the workloads LoraDB currently serves:</p>
<ul>
<li><strong>Seeded services.</strong> Build the graph offline from a cheaper source
(SQL exports, a scrape, an ETL job), snapshot it, and ship the
snapshot alongside the deployment. Every restart boots in one
file-read rather than a multi-minute replay.</li>
<li><strong>Notebooks and research tooling.</strong> Save the graph you've curated at
the end of a session; reload it the next morning with one call.</li>
<li><strong>Agents and LLM context stores.</strong> Periodic snapshots of the working
graph give you trivial "go back to yesterday's state" without the
complexity of a full transactional store.</li>
<li><strong>HTTP operator loop.</strong> <code>ExecStop=curl … /admin/snapshot/save</code> on a
systemd unit gives a graceful-shutdown save without any new
tooling. Add a <code>--restore-from</code> on boot and you have a durable-
enough deployment for a single-node service.</li>
<li><strong>Scheduled backups.</strong> A cron that calls <code>POST /admin/snapshot/save</code> every N minutes, optionally with a rotating
<code>{"path": "…"}</code>, is a complete backup policy for small graphs.</li>
</ul>
<p>The bright line is still the same: a crash between saves loses every
mutation in the window. The question to ask is whether that window is
narrow enough for your workload. For most of the shapes above, it is.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="whats-still-out-of-scope">What's Still Out Of Scope<a href="https://loradb.com/blog/loradb-v0-3-snapshots#whats-still-out-of-scope" class="hash-link" aria-label="Direct link to What's Still Out Of Scope" title="Direct link to What's Still Out Of Scope">​</a></h2>
<p>Explicitly not in this release, so the feature stays honest about its
boundary:</p>
<ul>
<li><strong>No WAL / checkpoint loop.</strong> The header reserves space for a WAL
LSN, but the engine does not yet write one. A future release will
turn checkpoints into "snapshots with a meaningful <code>walLsn</code>" — the
reader already accepts the flag.</li>
<li><strong>No automatic persistence.</strong> Snapshots are always manual. Nothing
runs them on a schedule unless you do.</li>
<li><strong>No partial / incremental snapshots.</strong> A save serializes the whole
graph. For v0.3 the expected scale is graphs that fit in memory
comfortably and dump in seconds.</li>
<li><strong>Non-blocking save.</strong> The store mutex is held for the full save.
Concurrent queries block. Real per-mutation copy-on-write will come
with the WAL work.</li>
<li><strong>No multi-graph file format.</strong> One file, one graph — same one-process
model as the rest of the engine.</li>
<li><strong>No auth on the HTTP admin surface.</strong> Opt-in, off by default, and
still not safe on a network-reachable host without an ingress.</li>
</ul>
<p>Those are the things a future release will address. They are not
hidden in the implementation — every one of them is a place the docs
say so.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="try-it">Try It<a href="https://loradb.com/blog/loradb-v0-3-snapshots#try-it" class="hash-link" aria-label="Direct link to Try It" title="Direct link to Try It">​</a></h2>
<p>Get the repo, build, and snapshot:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">cargo run --bin lora-server -- \</span><br></span><span class="token-line"><span class="token plain">  --snapshot-path /tmp/loradb.bin \</span><br></span><span class="token-line"><span class="token plain">  --restore-from  /tmp/loradb.bin</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then from a second shell:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/query \</span><br></span><span class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></span><span class="token-line"><span class="token plain">  -d '{"query":"CREATE (:Person {name:\"Ada\"})"}' &gt; /dev/null</span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/save</span><br></span><span class="token-line"><span class="token plain"># =&gt; {"formatVersion":1,"nodeCount":1,"relationshipCount":0,"walLsn":null,"path":"/tmp/loradb.bin"}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Stop the server, start it again with the same flags, and the graph is
still there.</p>
<p>The docs site has a dedicated page for snapshots — the file format,
atomicity guarantees, binding examples, and the full HTTP admin
surface:</p>
<ul>
<li><a href="https://loradb.com/docs/snapshot">Snapshots</a></li>
<li><a href="https://loradb.com/docs/getting-started/server#snapshots-wal-and-restore">HTTP server quickstart → Snapshots, WAL, and restore</a></li>
<li><a href="https://loradb.com/docs/api/http#admin-endpoints-opt-in">HTTP API → Admin endpoints (opt-in)</a></li>
</ul>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-comes-next">What Comes Next<a href="https://loradb.com/blog/loradb-v0-3-snapshots#what-comes-next" class="hash-link" aria-label="Direct link to What Comes Next" title="Direct link to What Comes Next">​</a></h2>
<p>Three directions stand out after v0.3:</p>
<ol>
<li><strong>A WAL.</strong> The snapshot header already reserves the slot; the
missing piece is the append-only log that checkpoints refer to.
That unlocks continuous durability, which in turn unblocks multi-
minute crash-recovery windows.</li>
<li><strong>A checkpoint loop.</strong> Once there is a WAL, the engine can fold
snapshots and the log together in the background — the operator
stops having to decide when to save.</li>
<li><strong>Auth on the admin surface.</strong> Token-based auth in front of
<code>/admin/*</code> so the endpoints can be used on network-reachable hosts
without an external reverse proxy.</li>
</ol>
<p>If you try v0.3 with snapshots, the feedback that will shape those is
concrete:</p>
<ul>
<li>how large does your graph get, and how long does <code>save_snapshot</code>
take at that size;</li>
<li>what cadence did you end up running — seconds, minutes, on shutdown
only;</li>
<li>did the atomic-rename guarantee land cleanly on your filesystem
(we've tested on Linux ext4/xfs and macOS APFS);</li>
<li>what does your ingress look like for the admin endpoints;</li>
<li>which binding did you use, and did the byte-based WASM surface fit
your storage layer (IndexedDB, OPFS, a backend POST) without extra
glue.</li>
</ul>
<p>That is the feedback that will shape v0.4.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Persistence</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.2: vector values for connected AI context]]></title>
            <link>https://loradb.com/blog/loradb-v0-2-vectors</link>
            <guid>https://loradb.com/blog/loradb-v0-2-vectors</guid>
            <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.2 adds first-class VECTOR values, vector functions, binding support, and documentation for graph-shaped AI retrieval on top of the v0.1 core.]]></description>
            <content:encoded><![CDATA[<p>LoraDB v0.2 adds first-class <code>VECTOR</code> values.</p>
<p>You can now construct vectors in Cypher, store them as node or
relationship properties, pass them in as parameters through every
binding, and run exhaustive similarity search against them. The value
type, the wire format, the function surface, and the binding helpers
all landed together so vectors behave like every other typed value in
the engine.</p>
<p>What this release is not is a vector-index product. There is no
approximate nearest-neighbour search, no built-in embedding
generation, and no plugin compatibility layer. Those are deliberately
out of scope for v0.2. The goal here is to make embeddings comfortable
inside the graph model — to ship the foundation that an index-backed
retrieval path will eventually sit on.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-changed">What Changed<a href="https://loradb.com/blog/loradb-v0-2-vectors#what-changed" class="hash-link" aria-label="Direct link to What Changed" title="Direct link to What Changed">​</a></h2>
<p>The short list:</p>
<ul>
<li>
<p><code>VECTOR</code> is a first-class value type, alongside scalars, lists,
maps, temporal values, and spatial points.</p>
</li>
<li>
<p>A new <code>vector(value, dimension, coordinateType)</code> constructor.</p>
</li>
<li>
<p>Six supported coordinate types:</p>
<ul>
<li><code>FLOAT</code> / <code>FLOAT64</code></li>
<li><code>FLOAT32</code></li>
<li><code>INTEGER</code> / <code>INT</code> / <code>INT64</code> / <code>INTEGER64</code> / <code>SIGNED INTEGER</code></li>
<li><code>INTEGER32</code> / <code>INT32</code></li>
<li><code>INTEGER16</code> / <code>INT16</code></li>
<li><code>INTEGER8</code> / <code>INT8</code></li>
</ul>
</li>
<li>
<p>Storage as node and relationship properties.</p>
</li>
<li>
<p><code>toIntegerList(v)</code> and <code>toFloatList(v)</code> for converting coordinates
back to lists.</p>
</li>
<li>
<p><code>vector_dimension_count(v)</code> and <code>size(v)</code> for introspection.</p>
</li>
<li>
<p><code>vector.similarity.cosine(a, b)</code> — bounded to <code>[0, 1]</code>.</p>
</li>
<li>
<p><code>vector.similarity.euclidean(a, b)</code> — bounded to <code>[0, 1]</code>.</p>
</li>
<li>
<p><code>vector_distance(a, b, metric)</code> — signed distance under one of six
metrics.</p>
</li>
<li>
<p><code>vector_norm(v, metric)</code> — Euclidean or Manhattan norm.</p>
</li>
<li>
<p>Parameter support through every binding.</p>
</li>
<li>
<p>A canonical tagged wire shape:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"kind"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"vector"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"dimension"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"coordinateType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"values"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.3</span><span class="token punctuation">]</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
</ul>
<p>Language support is included in this release for:</p>
<ul>
<li>the Rust core;</li>
<li>Node.js / TypeScript (<code>crates/lora-node</code>);</li>
<li>WebAssembly (<code>crates/lora-wasm</code>);</li>
<li>Python (<code>crates/lora-python</code>);</li>
<li>Go (<code>crates/lora-go</code>);</li>
<li>Ruby (<code>crates/lora-ruby</code>).</li>
</ul>
<p>Each binding ships a <code>vector(...)</code> helper and an <code>isVector</code> /
<code>is_vector</code> / <code>IsVector</code> / <code>vector?</code> guard, so callers do not need to
build the tagged object by hand.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-vectors-in-a-graph-database">Why Vectors In A Graph Database<a href="https://loradb.com/blog/loradb-v0-2-vectors#why-vectors-in-a-graph-database" class="hash-link" aria-label="Direct link to Why Vectors In A Graph Database" title="Direct link to Why Vectors In A Graph Database">​</a></h2>
<p>A graph database and a vector store usually get treated as two
products. The graph stores relationships; the vector store retrieves
similar items; the application glues them together.</p>
<p>For most AI workloads that is the wrong shape. You want to retrieve
candidates by similarity <em>and</em> use the graph to rank, filter, or
explain them. When the embedding lives on a separate service, every
query needs a round trip and every piece of context needs a join by
hand.</p>
<p>Putting <code>VECTOR</code> into LoraDB as a value type collapses that
separation. The embedding is a property on the same node that carries
the label, the text, and the relationships. You score with
<code>vector.similarity.cosine(...)</code> and walk with <code>MATCH</code> in the same
Cypher.</p>
<p>That is the whole argument. Similarity finds candidates. The graph
explains them.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="using-vector-values">Using VECTOR Values<a href="https://loradb.com/blog/loradb-v0-2-vectors#using-vector-values" class="hash-link" aria-label="Direct link to Using VECTOR Values" title="Direct link to Using VECTOR Values">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="creating-a-vector-property">Creating A Vector Property<a href="https://loradb.com/blog/loradb-v0-2-vectors#creating-a-vector-property" class="hash-link" aria-label="Direct link to Creating A Vector Property" title="Direct link to Creating A Vector Property">​</a></h3>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain">        </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  title</span><span class="token operator">:</span><span class="token plain">     </span><span class="token string">'Onboarding checklist'</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  embedding</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.3</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"> FLOAT32</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The third argument can be a bare identifier (<code>INTEGER</code>, <code>FLOAT32</code>,
<code>INT8</code>) or a string literal (<code>'INTEGER'</code>, <code>'SIGNED INTEGER'</code>). Bare
identifiers are rewritten to string literals by the analyzer only in
this specific argument position, so normal variable resolution is
unaffected elsewhere in the query.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="passing-a-vector-as-a-parameter">Passing A Vector As A Parameter<a href="https://loradb.com/blog/loradb-v0-2-vectors#passing-a-vector-as-a-parameter" class="hash-link" aria-label="Direct link to Passing A Vector As A Parameter" title="Direct link to Passing A Vector As A Parameter">​</a></h3>
<p>Generate the embedding in your application, pass it in with the
language helper, and use it like any other parameter:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> vector </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">"@loradb/lora-node"</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> query </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token template-string template-punctuation string">`</span><span class="token template-string string">MATCH (d:Doc)</span><br></span><span class="token-line"><span class="token template-string string">   RETURN d.id AS id</span><br></span><span class="token-line"><span class="token template-string string">   ORDER BY vector.similarity.cosine(d.embedding, $q) DESC</span><br></span><span class="token-line"><span class="token template-string string">   LIMIT 10</span><span class="token template-string template-punctuation string">`</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token punctuation">{</span><span class="token plain"> q</span><span class="token operator">:</span><span class="token plain"> query </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The same pattern works in Python (<code>vector(values, dimension, coordinate_type)</code>), Go (<code>lora.Vector(values, dimension, coordinateType)</code>),
Ruby (<code>LoraRuby.vector(values, dimension, coordinate_type)</code>), and the
raw FFI / JSON path used by the C ABI.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="bulk-insert">Bulk Insert<a href="https://loradb.com/blog/loradb-v0-2-vectors#bulk-insert" class="hash-link" aria-label="Direct link to Bulk Insert" title="Direct link to Bulk Insert">​</a></h3>
<p>The canonical way to load many embeddings is a single <code>UNWIND</code> over a
parameter list of rows. Each row is a map with scalar fields and a
tagged vector. The engine fans the list into per-row <code>CREATE</code>s without
round-tripping each one:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> vector </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">"@loradb/lora-node"</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> batch </span><span class="token operator">=</span><span class="token plain"> docs</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token plain">doc</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain">        doc</span><span class="token punctuation">.</span><span class="token plain">id</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  title</span><span class="token operator">:</span><span class="token plain">     doc</span><span class="token punctuation">.</span><span class="token plain">title</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  embedding</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token plain">doc</span><span class="token punctuation">.</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token template-string template-punctuation string">`</span><span class="token template-string string">UNWIND $batch AS row</span><br></span><span class="token-line"><span class="token template-string string">   CREATE (:Doc {id: row.id, title: row.title, embedding: row.embedding})</span><span class="token template-string template-punctuation string">`</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token punctuation">{</span><span class="token plain"> batch </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The same shape in Python:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">from</span><span class="token plain"> lora_python </span><span class="token keyword">import</span><span class="token plain"> Database</span><span class="token punctuation">,</span><span class="token plain"> vector</span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">db </span><span class="token operator">=</span><span class="token plain"> Database</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">batch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">        </span><span class="token string">"id"</span><span class="token punctuation">:</span><span class="token plain">        doc</span><span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">        </span><span class="token string">"title"</span><span class="token punctuation">:</span><span class="token plain">     doc</span><span class="token punctuation">[</span><span class="token string">"title"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">        </span><span class="token string">"embedding"</span><span class="token punctuation">:</span><span class="token plain"> vector</span><span class="token punctuation">(</span><span class="token plain">doc</span><span class="token punctuation">[</span><span class="token string">"embedding"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token punctuation">}</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token keyword">for</span><span class="token plain"> doc </span><span class="token keyword">in</span><span class="token plain"> docs</span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">]</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token plain">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token triple-quoted-string string">"""</span><br></span><span class="token-line"><span class="token triple-quoted-string string">    UNWIND $batch AS row</span><br></span><span class="token-line"><span class="token triple-quoted-string string">    CREATE (:Doc {id: row.id, title: row.title, embedding: row.embedding})</span><br></span><span class="token-line"><span class="token triple-quoted-string string">    """</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">    </span><span class="token punctuation">{</span><span class="token string">"batch"</span><span class="token punctuation">:</span><span class="token plain"> batch</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Two things are worth knowing about this pattern. First, each vector is
stored as its own property on its own node — the batch is a list of
<em>maps</em>, not a list of vectors, so the property rule (no lists of
vectors) is satisfied by construction. Second, <code>UNWIND</code> runs the whole
batch in one query, so the per-row overhead is a map extraction and a
<code>CREATE</code>, not a full parse + plan + execute cycle per document.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="exhaustive-knn">Exhaustive kNN<a href="https://loradb.com/blog/loradb-v0-2-vectors#exhaustive-knn" class="hash-link" aria-label="Direct link to Exhaustive kNN" title="Direct link to Exhaustive kNN">​</a></h3>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">id </span><span class="token keyword">AS</span><span class="token plain"> id</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">ORDER</span><span class="token plain"> </span><span class="token keyword">BY</span><span class="token plain"> vector</span><span class="token punctuation">.</span><span class="token plain">similarity</span><span class="token punctuation">.</span><span class="token function">cosine</span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">.</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token variable">$query</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">DESC</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">10</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Every <code>MATCH</code> candidate is scored. Cost is <code>O(n)</code> in the number of
matched nodes. That is fine for a local dataset, a test, a demo, or a
small internal tool. It is not how you would serve a corpus of
millions — that is what vector indexes are for, and they are not
implemented yet.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="graph-filtered-retrieval">Graph-Filtered Retrieval<a href="https://loradb.com/blog/loradb-v0-2-vectors#graph-filtered-retrieval" class="hash-link" aria-label="Direct link to Graph-Filtered Retrieval" title="Direct link to Graph-Filtered Retrieval">​</a></h3>
<p>The version that actually motivated adding vectors to LoraDB looks
like this:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WITH</span><span class="token plain"> d</span><span class="token punctuation">,</span><span class="token plain"> vector</span><span class="token punctuation">.</span><span class="token plain">similarity</span><span class="token punctuation">.</span><span class="token function">cosine</span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">.</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token variable">$query</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">AS</span><span class="token plain"> score</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">MENTIONS</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">e</span><span class="token operator">:</span><span class="token class-name">Entity</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WHERE</span><span class="token plain"> e</span><span class="token punctuation">.</span><span class="token plain">type </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable">$entity_type</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">id</span><span class="token punctuation">,</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">title</span><span class="token punctuation">,</span><span class="token plain"> score</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token function">collect</span><span class="token punctuation">(</span><span class="token plain">e</span><span class="token punctuation">.</span><span class="token plain">name</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">AS</span><span class="token plain"> entities</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">ORDER</span><span class="token plain"> </span><span class="token keyword">BY</span><span class="token plain"> score </span><span class="token keyword">DESC</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">5</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The similarity function supplies the candidates. The graph structure
(<code>MENTIONS</code>, the entity type filter) explains and constrains them.
Both live in one query and one engine.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="storing-and-reading-back">Storing And Reading Back<a href="https://loradb.com/blog/loradb-v0-2-vectors#storing-and-reading-back" class="hash-link" aria-label="Direct link to Storing And Reading Back" title="Direct link to Storing And Reading Back">​</a></h3>
<p>Vectors round-trip through storage unchanged:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain">id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"> embedding</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"> INTEGER</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain">id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">SET</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">embedding </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"> FLOAT32</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain">id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">RETURN</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">embedding </span><span class="token keyword">AS</span><span class="token plain"> e</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>A vector is also a legal map value, so a property map containing a
vector is stored intact. The one restriction is that <strong>a list
containing vectors cannot be stored as a property</strong> — if you need
many embeddings, hang them off separate nodes. The engine rejects the
write at property-conversion time instead of silently storing a shape
a future vector index could not support.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="ai-use-cases">AI Use Cases<a href="https://loradb.com/blog/loradb-v0-2-vectors#ai-use-cases" class="hash-link" aria-label="Direct link to AI Use Cases" title="Direct link to AI Use Cases">​</a></h2>
<p>Vectors in LoraDB are aimed at the workloads that already sit
awkwardly between "vector store" and "graph database":</p>
<ul>
<li><strong>Agent memory.</strong> Embed documents, chunks, observations, or tool
calls. Connect them with edges to the sessions, entities, and
decisions that reference them. Retrieve by similarity, filter by
recency, explain by provenance.</li>
<li><strong>Semantic document retrieval with context.</strong> Find the k closest
docs, then expand to the entities, authors, or topics they connect
to before returning anything to the application.</li>
<li><strong>Tool and context selection.</strong> Score candidate prompts, tools, or
examples by similarity to the current state, then filter by graph
constraints (same tenant, same permission scope, same task type).</li>
<li><strong>Knowledge graph enrichment.</strong> Attach embeddings to entities so
fuzzy lookups can locate the right node before structural queries
run.</li>
<li><strong>Recommendations with graph guardrails.</strong> Cosine similarity
produces a long list of candidates; graph relationships
(<code>BLOCKED</code>, <code>ALREADY_SEEN</code>, <code>OWNED_BY</code>) filter that list before
ranking.</li>
<li><strong>Internal search over connected product, customer, or support
data.</strong> Small corpora, high structure, and queries that want both
"similar to this ticket" and "in the same account and escalation
path."</li>
</ul>
<p>Every one of those workloads has the same shape: similarity for
candidates, structure for the rest.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-is-not-included-yet">What Is Not Included Yet<a href="https://loradb.com/blog/loradb-v0-2-vectors#what-is-not-included-yet" class="hash-link" aria-label="Direct link to What Is Not Included Yet" title="Direct link to What Is Not Included Yet">​</a></h2>
<p>This release is deliberately narrow. It does not include:</p>
<ul>
<li><strong>Vector indexes.</strong> All vector functions are exhaustive today.</li>
<li><strong>Approximate nearest-neighbour search.</strong> There is no ANN path.</li>
<li><strong>Built-in embedding generation.</strong> LoraDB does not produce
embeddings; bring them in from your application code.</li>
<li><strong>Hardened public-internet database hosting.</strong> The HTTP server is
useful for local development and controlled environments. It is
not a managed service.</li>
</ul>
<p>Those are not hidden. They are the roadmap.</p>
<p>Exhaustive similarity is fine for small datasets, tests, demos, local
prototypes, and internal workflows. It is not yet a substitute for an
index-backed vector search service at scale.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-this-fits-the-loradb-journey">Why This Fits The LoraDB Journey<a href="https://loradb.com/blog/loradb-v0-2-vectors#why-this-fits-the-loradb-journey" class="hash-link" aria-label="Direct link to Why This Fits The LoraDB Journey" title="Direct link to Why This Fits The LoraDB Journey">​</a></h2>
<p>LoraDB is developer-first.</p>
<p>That sequencing applies to vectors too. The first version needs the
value model, the wire shape, the binding helpers, and the Cypher
ergonomics to be right. Those are the parts that user code and tests
depend on. A vector index that sits on a broken value model would
inherit every problem. Shipping the foundation first, and being
honest about the absence of an index, keeps the upgrade path clean.</p>
<p>It also matches how developers actually pick up a new capability.
Clone the repo, generate a few embeddings, store them on nodes, write
a similarity query, see whether the model fits the problem. Only
after that does scale, persistence, and managed operations become
worth talking about.</p>
<p>v0.2 makes that first loop work.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="try-it">Try It<a href="https://loradb.com/blog/loradb-v0-2-vectors#try-it" class="hash-link" aria-label="Direct link to Try It" title="Direct link to Try It">​</a></h2>
<p>Get the repo and run the server:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">cargo run --bin lora-server</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then try a vector query from <code>curl</code> or any binding:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">curl -X POST http://127.0.0.1:4747/query \</span><br></span><span class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></span><span class="token-line"><span class="token plain">  -d '{"query":"RETURN vector([1,2,3], 3, INTEGER) AS v"}'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The docs site has a dedicated page for the value type, the coordinate
rules, the functions, the storage restrictions, and the exhaustive
kNN pattern:</p>
<ul>
<li><a href="https://loradb.com/docs/data-types/vectors">Data types → Vectors</a></li>
<li><a href="https://loradb.com/docs/functions/overview">Functions → Overview</a></li>
<li><a href="https://loradb.com/docs/queries/parameters">Queries → Parameters</a></li>
</ul>
<p>Internal notes on the value model and the Cypher support matrix have
been updated to match.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-comes-next">What Comes Next<a href="https://loradb.com/blog/loradb-v0-2-vectors#what-comes-next" class="hash-link" aria-label="Direct link to What Comes Next" title="Direct link to What Comes Next">​</a></h2>
<p>Three directions stand out after v0.2:</p>
<ol>
<li><strong>A vector index.</strong> The Cypher shape stays the same
(<code>ORDER BY vector.similarity.* LIMIT k</code>); the executor starts
routing scored candidates through an index instead of a linear
scan. The design depends on the workloads people actually bring.</li>
<li><strong>More metrics and norms as real usage demands them.</strong> The
current set (<code>EUCLIDEAN</code>, <code>EUCLIDEAN_SQUARED</code>, <code>MANHATTAN</code>,
<code>COSINE</code>, <code>DOT</code>, <code>HAMMING</code> for distance; <code>EUCLIDEAN</code> and
<code>MANHATTAN</code> for norm) covers the common cases. Extending is
mechanical once a concrete need shows up.</li>
<li><strong>The hosted path.</strong> Vectors on stored nodes eventually need
managed operations to match. That is a separate release, but the
value-type work here is what makes it possible without a second
data model.</li>
</ol>
<p>If you try v0.2 with vectors, the most useful feedback is concrete:</p>
<ul>
<li>what graph did you load, and what embedding workflow did you pair
with it;</li>
<li>where did exhaustive scan stop being enough;</li>
<li>what would a vector index need to support for your workload — a
specific metric, a specific filter shape, a specific freshness
guarantee;</li>
<li>which binding did you use, and did the tagged shape round-trip
cleanly through your application code.</li>
</ul>
<p>That is the feedback that will shape v0.3.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>AI &amp; agents</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[Vectors belong next to relationships]]></title>
            <link>https://loradb.com/blog/vectors-belong-next-to-relationships</link>
            <guid>https://loradb.com/blog/vectors-belong-next-to-relationships</guid>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB adds VECTOR values: similarity is useful, but connected context is what makes retrieval explainable.]]></description>
            <content:encoded><![CDATA[<p>The conventional advice for AI retrieval is to pick a side.</p>
<p>You pick a vector database if you want similarity. You pick a graph
database if you want structure. You bolt them together with glue code
when the product inevitably needs both.</p>
<p>That framing has never matched the workloads I actually care about. The
interesting systems — agent memory, recommendations, internal search
over connected product data, knowledge graphs that feed chat features —
do not want a vector store <em>or</em> a graph store. They want to retrieve
candidates by similarity, then explain and filter those candidates by
relationships. Splitting that into two products splits the query path,
the data model, and eventually the team.</p>
<p>LoraDB v0.2 adds <a href="https://loradb.com/docs/data-types/vectors"><code>VECTOR</code></a> as a first-class
value type. Vectors live directly on nodes and relationships, next to
labels, properties, and edges. The argument is not that a graph
database should replace a vector database. The argument is that
similarity belongs next to the relationships that give it meaning.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="similarity-is-useful-but-it-is-not-memory">Similarity Is Useful, But It Is Not Memory<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#similarity-is-useful-but-it-is-not-memory" class="hash-link" aria-label="Direct link to Similarity Is Useful, But It Is Not Memory" title="Direct link to Similarity Is Useful, But It Is Not Memory">​</a></h2>
<p>Embeddings are good at one thing: finding items that are close to a
query in some learned semantic space. That is genuinely useful. A lot
of retrieval systems live or die on whether the top ten candidates
contain the right answer.</p>
<p>But similarity alone does not preserve the information a product
usually needs <em>around</em> that answer:</p>
<ul>
<li>Where did this chunk come from?</li>
<li>Which entities does it mention?</li>
<li>Which session produced it?</li>
<li>What depends on it?</li>
<li>What contradicts it?</li>
<li>Is it more recent than the other candidates?</li>
<li>Is it from a trusted source?</li>
</ul>
<p>A flat list of similar chunks cannot answer those questions. The
relationships have to be recorded somewhere, and the system that
retrieves embeddings has to reach that somewhere cheaply. When those
two things live in different databases, "cheaply" stops being a local
property of the query engine.</p>
<p>That is why vectors belong next to the graph, not behind a sidecar.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-agent-memory-shape">The Agent Memory Shape<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#the-agent-memory-shape" class="hash-link" aria-label="Direct link to The Agent Memory Shape" title="Direct link to The Agent Memory Shape">​</a></h2>
<p>Think about what an agent actually stores across a session.</p>
<p>It stores documents or chunks, with embeddings so they can be
retrieved by similarity. It stores entities extracted from those
documents. It stores tool calls and their arguments. It stores
decisions, observations, and the context that led to each one. It
stores edges between those things: this observation led to that
decision; this tool was selected because of that memory; this document
mentions those entities.</p>
<p>That is a graph. And it is a graph with embeddings on some of the
nodes.</p>
<p>A small version looks like this:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain">        </span><span class="token string">'doc-17'</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  title</span><span class="token operator">:</span><span class="token plain">     </span><span class="token string">'Onboarding checklist'</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  embedding</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token variable">$embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> FLOAT32</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">e</span><span class="token operator">:</span><span class="token class-name">Entity</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">'Alice'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">MENTIONS</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">e</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">o</span><span class="token operator">:</span><span class="token class-name">Observation</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain">session</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">'s1'</span><span class="token punctuation">,</span><span class="token plain"> ts</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function">datetime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">CREATE</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">o</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">OBSERVED_IN</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Later, when the agent needs to retrieve memory, the query is not "find
the ten closest chunks." The useful query is "find the ten closest
chunks, show which entities they mention, and give me enough structure
to rank or filter":</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token operator">:</span><span class="token class-name">Doc</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WITH</span><span class="token plain"> d</span><span class="token punctuation">,</span><span class="token plain"> vector</span><span class="token punctuation">.</span><span class="token plain">similarity</span><span class="token punctuation">.</span><span class="token function">cosine</span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">.</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token variable">$query</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">AS</span><span class="token plain"> score</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">d</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">MENTIONS</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">e</span><span class="token operator">:</span><span class="token class-name">Entity</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">id</span><span class="token punctuation">,</span><span class="token plain"> d</span><span class="token punctuation">.</span><span class="token plain">title</span><span class="token punctuation">,</span><span class="token plain"> score</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token function">collect</span><span class="token punctuation">(</span><span class="token plain">e</span><span class="token punctuation">.</span><span class="token plain">name</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">AS</span><span class="token plain"> entities</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">ORDER</span><span class="token plain"> </span><span class="token keyword">BY</span><span class="token plain"> score </span><span class="token keyword">DESC</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">5</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Similarity finds the candidates. The graph explains them.</p>
<p>That shape is easier to build — and easier to debug — when the
embedding is a property on the same node that carries the
relationships. There is no sync process, no second database, no
second query pipeline. There is one model.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-vector-is-a-value-type">Why VECTOR Is A Value Type<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#why-vector-is-a-value-type" class="hash-link" aria-label="Direct link to Why VECTOR Is A Value Type" title="Direct link to Why VECTOR Is A Value Type">​</a></h2>
<p>A vector in LoraDB is not a special table or a separate index
namespace. It is a value type, like a string or a point or a duration.
That design choice has consequences.</p>
<p>A <code>LoraVector</code> has:</p>
<ul>
<li>a fixed dimension (<code>1..=4096</code>);</li>
<li>a typed coordinate choice (<code>FLOAT64</code>, <code>FLOAT32</code>, <code>INTEGER</code>,
<code>INTEGER32</code>, <code>INTEGER16</code>, <code>INTEGER8</code>);</li>
<li>its coordinates stored in a single typed array.</li>
</ul>
<p>It can appear anywhere a value can appear:</p>
<ul>
<li>as a node property: <code>CREATE (:Doc {embedding: vector([...], 384, FLOAT32)})</code></li>
<li>as a relationship property: <code>CREATE (a)-[:SIM {score: vector([...], 3, FLOAT32)}]-&gt;(b)</code></li>
<li>as a Cypher parameter: <code>vector(value, dimension, coordinateType)</code></li>
<li>inside a <code>RETURN</code>, <code>WITH</code>, <code>ORDER BY</code>, or <code>WHERE</code> clause.</li>
</ul>
<p>Every binding speaks the same canonical tagged shape:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"kind"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"vector"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"dimension"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"coordinateType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">,</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain">  </span><span class="token property">"values"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.3</span><span class="token punctuation">]</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This is not only a wire format. It is the thing the Node.js, WASM,
Python, Go, and Ruby helpers build. A developer in any of those
languages can construct a vector in application code, pass it in as a
parameter, store it on a node, read it back, and get the same tagged
object on the other side. No bridge code. No JSON schemas to keep in
sync.</p>
<p>That value-type framing also forces a property-storage rule worth
stating out loud: a vector is fine as a property, but a <strong>list of
vectors</strong> is not. If you want many vectors, hang them off separate
nodes with separate embeddings. That restriction is enforced at write
time — the engine rejects the property instead of silently storing a
shape that future indexing could not support.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-exhaustive-first">Why Exhaustive First<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#why-exhaustive-first" class="hash-link" aria-label="Direct link to Why Exhaustive First" title="Direct link to Why Exhaustive First">​</a></h2>
<p>v0.2 ships vectors, but it does not ship vector indexes. That is a
deliberate line.</p>
<p>What works today is exhaustive similarity search. You write a <code>MATCH</code>
that produces a candidate set, you score every candidate with
<code>vector.similarity.cosine(...)</code> or <code>vector.similarity.euclidean(...)</code>,
you <code>ORDER BY score DESC LIMIT k</code>. The engine scans every matched
node.</p>
<p>For a graph of a few hundred thousand embeddings, this is completely
fine on a laptop. For millions, you want a proper index. The
difference is not in the query language — the <code>ORDER BY … LIMIT k</code>
shape is the same either way — it is in what the engine does under
the hood.</p>
<p>Shipping exhaustive first lets v0.2 land the parts that are hardest to
change later:</p>
<ul>
<li>the <code>VECTOR</code> value type and its coordinate rules;</li>
<li>the tagged wire shape that every binding speaks;</li>
<li>the property-storage semantics;</li>
<li>the function surface (<code>vector.similarity.*</code>, <code>vector_distance</code>,
<code>vector_norm</code>, <code>toIntegerList</code>, <code>toFloatList</code>,
<code>vector_dimension_count</code>, <code>size(vector)</code>);</li>
<li>the Cypher ergonomics, including the bare-identifier rewrite that
lets you write <code>INTEGER8</code> and <code>EUCLIDEAN</code> without declaring them as
variables.</li>
</ul>
<p>Those are the decisions that bindings, tests, and user code depend
on. If we got any of them wrong, a vector index would inherit the
mistake. The order is: value model first, index-backed retrieval
later.</p>
<p>It also matches LoraDB's customer journey. The first question is not
"can this serve ten million embeddings?" The first question is "does
the model fit my problem?" An exhaustive scan over a thousand docs is
enough to answer that.</p>
<p>What is explicitly <em>not</em> included in v0.2 is worth stating plainly:</p>
<ul>
<li>no vector indexes;</li>
<li>no approximate nearest-neighbour search;</li>
<li>no built-in embedding generation;</li>
<li>no hardened public-internet database hosting.</li>
</ul>
<p>Generate your embeddings in application code — whatever you already
use, whether that is a hosted API, a local model, or a batch job —
and pass them in as parameters. The database's job is to store them
next to the graph and let you query both in one language.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-customer-journey-for-vectors">The Customer Journey For Vectors<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#the-customer-journey-for-vectors" class="hash-link" aria-label="Direct link to The Customer Journey For Vectors" title="Direct link to The Customer Journey For Vectors">​</a></h2>
<p>The flow I want for a developer adopting vectors in LoraDB mirrors the
one I wanted for adopting LoraDB at all.</p>
<ol>
<li>They have a retrieval problem that is not purely similarity —
there is structure around the items they want to find.</li>
<li>They generate embeddings in application code and store them on
graph nodes with a single <code>CREATE</code>.</li>
<li>They run <code>vector.similarity.cosine(...)</code> against a small local
dataset. The query shape is ordinary Cypher.</li>
<li>They add <code>MATCH</code> patterns that filter or explain the results using
relationships.</li>
<li>They build a prototype, a tool, or a product feature on that
combined query.</li>
<li>When the dataset grows past what an exhaustive scan can serve,
they move to an index-backed variant — without rewriting the
application, because the Cypher stays the same.</li>
<li>Eventually, managed operations follow.</li>
</ol>
<p>That is the same staircase as before. Local trust first, persistence
and platform later. Vectors fit because they slot into the same model
as everything else LoraDB stores.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="closing">Closing<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#closing" class="hash-link" aria-label="Direct link to Closing" title="Direct link to Closing">​</a></h2>
<p>Similarity helps you find candidates. Graphs help you explain them.</p>
<p>Most of the AI systems I care about — agent memory, internal search
over connected data, recommendations with guardrails, knowledge graphs
that feed chat — need both. The mistake is treating that as two
products. LoraDB v0.2 is the argument that it should be one.</p>
<p>The v0.2 release article has the full list of what landed, the
functions, the binding support, and the Cypher examples. The short
version is that <code>vector</code> is now a value you can put on a node, pass as
a parameter, and query with a few small honest functions. The longer
version is everything we are <em>not</em> trying to be — a vector-index
product, a hosted ANN service, a plugin marketplace — because the
first job is to make the graph model comfortable with embeddings
sitting on it.</p>
<p>If you try it, the feedback I want is concrete:</p>
<ul>
<li>what graph did you load, and what embedding workflow did you pair
with it;</li>
<li>what did the query look like once similarity and structure were in
the same Cypher;</li>
<li>at what size did the exhaustive scan stop being good enough;</li>
<li>what would a vector index need to support for your workload — a
specific metric, a specific filter shape, a specific freshness
guarantee?</li>
</ul>
<p>That is what will shape the next release.</p>
<hr>
<p>Canonical references:</p>
<ul>
<li><a href="https://loradb.com/docs/data-types/vectors">Data Types → Vectors</a> — construction, storage,
and the list-of-vectors property restriction.</li>
<li><a href="https://loradb.com/docs/functions/overview">Functions → Overview</a> — the vector similarity
and distance functions used above.</li>
<li><a href="https://loradb.com/docs/queries/parameters#semantic-retrieval-with-a-vector-parameter">Queries → Parameters</a>
— how each binding passes a <code>VECTOR</code> in.</li>
<li><a href="https://loradb.com/docs/cookbook#vector-retrieval-patterns">Cookbook → Vector-retrieval patterns</a>
— top-k and graph-filtered retrieval recipes.</li>
<li><a href="https://loradb.com/docs/limitations#vectors">Limitations → Vectors</a> — no indexes, no ANN,
no embedding generation today.</li>
</ul>]]></content:encoded>
            <category>Founder notes</category>
            <category>AI &amp; agents</category>
            <category>Design</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[LoraDB public release: a fast in-memory graph database in Rust]]></title>
            <link>https://loradb.com/blog/loradb-public-release</link>
            <guid>https://loradb.com/blog/loradb-public-release</guid>
            <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The first public release of LoraDB, what is included, how to try it, the license model, and where the project goes next.]]></description>
            <content:encoded><![CDATA[<p>LoraDB is now public.</p>
<p>It is a fast in-memory graph database written in Rust, with a Cypher-shaped
query engine, an HTTP API, and bindings for Node.js, WebAssembly, and Python.
It is built for developers who need relationship queries close to their
application without adopting a large graph database stack on day one.</p>
<p>This release is the beginning of the public journey: source-available core,
developer-first adoption, and a path toward a hosted platform for teams that
want managed operations later.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-loradb-is">What LoraDB Is<a href="https://loradb.com/blog/loradb-public-release#what-loradb-is" class="hash-link" aria-label="Direct link to What LoraDB Is" title="Direct link to What LoraDB Is">​</a></h2>
<p>LoraDB is an in-memory property graph database.</p>
<p>It stores:</p>
<ul>
<li>nodes with labels and properties;</li>
<li>relationships with a type, direction, endpoints, and properties;</li>
<li>scalar values, lists, maps, temporal values, and spatial points;</li>
<li>query results in row, graph, row-array, and combined formats.</li>
</ul>
<p>It speaks a Cypher-like query language because Cypher is still one of the best
ways to express graph patterns:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">person</span><span class="token operator">:</span><span class="token class-name">Person</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">KNOWS</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">friend</span><span class="token operator">:</span><span class="token class-name">Person</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WHERE</span><span class="token plain"> person</span><span class="token punctuation">.</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable">$name</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> friend</span><span class="token punctuation">.</span><span class="token plain">name</span><span class="token punctuation">,</span><span class="token plain"> friend</span><span class="token punctuation">.</span><span class="token plain">age</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">ORDER</span><span class="token plain"> </span><span class="token keyword">BY</span><span class="token plain"> friend</span><span class="token punctuation">.</span><span class="token plain">age </span><span class="token keyword">DESC</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">10</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Under the hood, LoraDB is split into small Rust crates:</p>
<ul>
<li><code>lora-ast</code> for query syntax structures;</li>
<li><code>lora-parser</code> for parsing;</li>
<li><code>lora-analyzer</code> for semantic analysis;</li>
<li><code>lora-compiler</code> for logical and physical planning;</li>
<li><code>lora-executor</code> for running plans;</li>
<li><code>lora-store</code> for graph storage;</li>
<li><code>lora-database</code> for the database entry point;</li>
<li><code>lora-server</code> for the HTTP server;</li>
<li><code>lora-node</code>, <code>lora-wasm</code>, and <code>lora-python</code> for language bindings.</li>
</ul>
<p>The goal is not to hide the database behind a giant internal system. The goal
is to make the engine readable enough that developers can understand how a
query moves from text to result.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-it-exists">Why It Exists<a href="https://loradb.com/blog/loradb-public-release#why-it-exists" class="hash-link" aria-label="Direct link to Why It Exists" title="Direct link to Why It Exists">​</a></h2>
<p>LoraDB exists because many graph workloads need to be fast, local, and
efficient before they need to be distributed.</p>
<p>Existing graph databases like Neo4j are powerful, but for the workloads that
started this project, they felt too heavy. I wanted a database that could live
in the application loop, load quickly, run in memory, and make storage costs
easy to reason about.</p>
<p>That means LoraDB is optimized for a specific first experience:</p>
<ol>
<li>Clone the repo.</li>
<li>Run the server.</li>
<li>Load a graph.</li>
<li>Write a Cypher query.</li>
<li>Understand the result and the code path behind it.</li>
</ol>
<p>The hosted platform will come later. The core has to earn developer trust
first.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-you-can-do-today">What You Can Do Today<a href="https://loradb.com/blog/loradb-public-release#what-you-can-do-today" class="hash-link" aria-label="Direct link to What You Can Do Today" title="Direct link to What You Can Do Today">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="run-the-server">Run the server<a href="https://loradb.com/blog/loradb-public-release#run-the-server" class="hash-link" aria-label="Direct link to Run the server" title="Direct link to Run the server">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">cargo run --bin lora-server</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>By default, the server listens on <code>127.0.0.1:4747</code>.</p>
<p>Send a query:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">curl -X POST http://127.0.0.1:4747/query \</span><br></span><span class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></span><span class="token-line"><span class="token plain">  -d '{"query":"CREATE (:Person {name: \"Ada\"}) RETURN 1 AS ok"}'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Check health:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">curl http://127.0.0.1:4747/health</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="use-the-rust-api">Use the Rust API<a href="https://loradb.com/blog/loradb-public-release#use-the-rust-api" class="hash-link" aria-label="Direct link to Use the Rust API" title="Direct link to Use the Rust API">​</a></h3>
<p>The Rust API is the most direct way to embed LoraDB in another Rust program:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">use</span><span class="token plain"> </span><span class="token namespace">lora_database</span><span class="token namespace punctuation">::</span><span class="token class-name">Database</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Database</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> rows </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"MATCH (n) RETURN n LIMIT 10"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="use-language-bindings">Use language bindings<a href="https://loradb.com/blog/loradb-public-release#use-language-bindings" class="hash-link" aria-label="Direct link to Use language bindings" title="Direct link to Use language bindings">​</a></h3>
<p>This repository includes package work for:</p>
<ul>
<li>Node.js / TypeScript through <code>crates/lora-node</code>;</li>
<li>WebAssembly through <code>crates/lora-wasm</code>;</li>
<li>Python through <code>crates/lora-python</code>.</li>
</ul>
<p>The bindings are part of the same public release because the customer journey
should not stop at Rust. Graph workloads show up in web apps, notebooks,
automation tools, agent runtimes, and backend services.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="query-support">Query Support<a href="https://loradb.com/blog/loradb-public-release#query-support" class="hash-link" aria-label="Direct link to Query Support" title="Direct link to Query Support">​</a></h2>
<p>This release includes a substantial Cypher-shaped query surface:</p>
<ul>
<li><code>MATCH</code> and <code>OPTIONAL MATCH</code>;</li>
<li><code>WHERE</code>;</li>
<li><code>RETURN</code>;</li>
<li><code>WITH</code>;</li>
<li><code>ORDER BY</code>, <code>SKIP</code>, <code>LIMIT</code>, and <code>DISTINCT</code>;</li>
<li><code>UNWIND</code>;</li>
<li><code>UNION</code> and <code>UNION ALL</code>;</li>
<li><code>CREATE</code>;</li>
<li><code>SET</code>;</li>
<li><code>DELETE</code> and <code>DETACH DELETE</code>;</li>
<li><code>REMOVE</code>;</li>
<li><code>MERGE</code> with <code>ON CREATE</code> and <code>ON MATCH</code>;</li>
<li>variable-length paths;</li>
<li><code>shortestPath()</code> and <code>allShortestPaths()</code>;</li>
<li>aggregation functions;</li>
<li>list, string, math, temporal, spatial, conversion, and entity functions.</li>
</ul>
<p>It also supports parameter binding through the Rust API and typed values for
common graph workloads.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="storage-and-execution">Storage And Execution<a href="https://loradb.com/blog/loradb-public-release#storage-and-execution" class="hash-link" aria-label="Direct link to Storage And Execution" title="Direct link to Storage And Execution">​</a></h2>
<p>The storage layer is intentionally in-memory. That is the point of this first
release.</p>
<p>LoraDB is designed around:</p>
<ul>
<li>cheap local iteration;</li>
<li>predictable graph traversal;</li>
<li>explicit query stages;</li>
<li>small intermediate representations;</li>
<li>clear Rust ownership boundaries;</li>
<li>enough structure to evolve toward persistence without hiding current costs.</li>
</ul>
<p>The planner and executor are still young, but they already handle meaningful
graph patterns, projection, filtering, aggregation, updates, and paths. The
project is written so performance work can happen in the open, with the storage
and execution model visible to contributors.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="documentation">Documentation<a href="https://loradb.com/blog/loradb-public-release#documentation" class="hash-link" aria-label="Direct link to Documentation" title="Direct link to Documentation">​</a></h2>
<p>The documentation site includes:</p>
<ul>
<li><a href="https://loradb.com/docs/getting-started/installation">getting started guides</a> for every binding;</li>
<li><a href="https://loradb.com/docs/queries">query language pages</a> covering each supported clause;</li>
<li><a href="https://loradb.com/docs/functions/overview">function references</a> — string, math, list, aggregation, temporal, spatial;</li>
<li><a href="https://loradb.com/docs/data-types/overview">data type references</a> — scalars, lists, maps, temporal, spatial, vectors;</li>
<li><a href="https://loradb.com/docs/concepts/graph-model">concept docs</a> for the graph model and schema-free behaviour;</li>
<li><a href="https://loradb.com/docs/cookbook">cookbook recipes</a> by domain;</li>
<li><a href="https://loradb.com/docs/troubleshooting">troubleshooting</a>;</li>
<li><a href="https://loradb.com/docs/limitations">known limitations</a>.</li>
</ul>
<p>The root repository also includes architecture, testing, operations, and
release documentation for contributors who want to understand or improve the
engine.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="license">License<a href="https://loradb.com/blog/loradb-public-release#license" class="hash-link" aria-label="Direct link to License" title="Direct link to License">​</a></h2>
<p>The LoraDB core is licensed under the Business Source License 1.1.</p>
<p>The license allows:</p>
<ul>
<li>development use;</li>
<li>non-production use;</li>
<li>internal business use;</li>
<li>internal production systems;</li>
<li>reading, modifying, and distributing the source under the BSL terms.</li>
</ul>
<p>The license does not allow using the core to offer LoraDB as
database-as-a-service, a hosted API for third parties, a competing managed
database platform, or a hosted resale product.</p>
<p>Each covered release converts to Apache License 2.0 on the Change Date listed
in the root <code>LICENSE</code> file. For this release policy, that date is April 19,
2029.</p>
<p>The documentation website under <code>apps/loradb.com</code> is separately MIT licensed.</p>
<p>The goal is simple: developers should be able to adopt and trust the core,
while the hosted platform business remains sustainable.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-is-not-included-yet">What Is Not Included Yet<a href="https://loradb.com/blog/loradb-public-release#what-is-not-included-yet" class="hash-link" aria-label="Direct link to What Is Not Included Yet" title="Direct link to What Is Not Included Yet">​</a></h2>
<p>This release is intentionally honest about what it is not.</p>
<p>LoraDB does not yet include:</p>
<ul>
<li>durable disk persistence;</li>
<li>WAL or snapshots;</li>
<li>clustering;</li>
<li>replication;</li>
<li>authentication;</li>
<li>TLS termination;</li>
<li>transactions across concurrent clients;</li>
<li>property indexes for every workload;</li>
<li>a managed cloud service.</li>
</ul>
<p>The HTTP server is useful for local development, internal experiments, and
controlled environments. It is not yet a hardened internet-facing database
server.</p>
<p>Those limitations are not hidden. They are the roadmap.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="who-should-try-it">Who Should Try It<a href="https://loradb.com/blog/loradb-public-release#who-should-try-it" class="hash-link" aria-label="Direct link to Who Should Try It" title="Direct link to Who Should Try It">​</a></h2>
<p>Start with the <a href="https://loradb.com/docs/getting-started/installation"><strong>installation guide</strong></a>
and the <a href="https://loradb.com/docs/getting-started/tutorial"><strong>ten-minute tour</strong></a> to walk
through the Cypher surface.</p>
<p>Try LoraDB if you are:</p>
<ul>
<li>building an internal tool with relationship-heavy data;</li>
<li>experimenting with graph memory for agents;</li>
<li>prototyping a knowledge graph;</li>
<li>looking for a Rust graph database engine you can read;</li>
<li>evaluating whether Cypher-shaped queries fit your product;</li>
<li>tired of starting with a large graph stack before you know the model works.</li>
</ul>
<p>Do not choose LoraDB yet if you need mature distributed operations, long-term
durability, multi-region replication, or hardened public database hosting
today.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-comes-next">What Comes Next<a href="https://loradb.com/blog/loradb-public-release#what-comes-next" class="hash-link" aria-label="Direct link to What Comes Next" title="Direct link to What Comes Next">​</a></h2>
<p>The next phase has three tracks.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="1-core-database-maturity">1. Core database maturity<a href="https://loradb.com/blog/loradb-public-release#1-core-database-maturity" class="hash-link" aria-label="Direct link to 1. Core database maturity" title="Direct link to 1. Core database maturity">​</a></h3>
<p>More planner work, better indexing, tighter memory usage, clearer error
messages, and deeper test coverage for Cypher behavior.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="2-persistence">2. Persistence<a href="https://loradb.com/blog/loradb-public-release#2-persistence" class="hash-link" aria-label="Direct link to 2. Persistence" title="Direct link to 2. Persistence">​</a></h3>
<p>The in-memory engine is the foundation. The next durable layer should preserve
the simplicity of the current store while adding snapshots, recovery, and a
path toward production workloads that outlive a process.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="3-hosted-platform">3. Hosted platform<a href="https://loradb.com/blog/loradb-public-release#3-hosted-platform" class="hash-link" aria-label="Direct link to 3. Hosted platform" title="Direct link to 3. Hosted platform">​</a></h3>
<p>The long-term product is a hosted LoraDB platform for teams that want the graph
model without operating the database themselves. That means managed projects,
backups, metrics, auth, scaling, and support.</p>
<p>The public core and the hosted product are not in conflict. The core creates
developer adoption. The hosted platform turns that adoption into a sustainable
business.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="closing">Closing<a href="https://loradb.com/blog/loradb-public-release#closing" class="hash-link" aria-label="Direct link to Closing" title="Direct link to Closing">​</a></h2>
<p>LoraDB started from a practical frustration: I needed a graph database that was
fast enough to live in memory, efficient enough to trust, and small enough to
understand.</p>
<p>This public release is the first serious step toward that goal.</p>
<p>If you try it, the most useful feedback is concrete:</p>
<ul>
<li>what graph did you load;</li>
<li>which query did you expect to be easy;</li>
<li>where did performance surprise you;</li>
<li>which limitation blocked you;</li>
<li>which docs page did you wish existed.</li>
</ul>
<p>That feedback will shape the next release.</p>
<p>Welcome to LoraDB.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Architecture</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[From developer trust to hosted platform]]></title>
            <link>https://loradb.com/blog/from-developer-trust-to-hosted-platform</link>
            <guid>https://loradb.com/blog/from-developer-trust-to-hosted-platform</guid>
            <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The customer journey behind LoraDB: local adoption first, managed operations later.]]></description>
            <content:encoded><![CDATA[<p>The easiest way to misunderstand LoraDB is to see the open core and the hosted
platform as separate ideas.</p>
<p>They are the same journey.</p>
<p>The core database has to be developer-first because graph databases ask for a
lot of trust. You are not just storing records. You are putting relationships,
paths, and product logic into a system that needs to be correct and fast. If a
developer cannot run it locally, inspect it, and build confidence in the query
engine, the hosted product has no foundation.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="trust-starts-before-production">Trust Starts Before Production<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#trust-starts-before-production" class="hash-link" aria-label="Direct link to Trust Starts Before Production" title="Direct link to Trust Starts Before Production">​</a></h2>
<p>The first customer is usually not a procurement team. It is one developer with
a problem that keeps resisting simpler models.</p>
<p>They have a set of entities. The relationships matter. SQL joins are becoming
awkward. A document model hides too much structure. A vector database retrieves
similar things, but it does not explain how they connect.</p>
<p>That developer does not want a sales call first. They want to try the thing.</p>
<p>For LoraDB, the first trust moment should look like this:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token plain">cargo run --bin lora-server</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then a query:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">a</span><span class="token operator">:</span><span class="token class-name">Account</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">OWNS</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">p</span><span class="token operator">:</span><span class="token class-name">Project</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">DEPENDS_ON</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">s</span><span class="token operator">:</span><span class="token class-name">Service</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WHERE</span><span class="token plain"> a</span><span class="token punctuation">.</span><span class="token plain">id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable">$account</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> p</span><span class="token punctuation">.</span><span class="token plain">name</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token function">collect</span><span class="token punctuation">(</span><span class="token plain">s</span><span class="token punctuation">.</span><span class="token plain">name</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">AS</span><span class="token plain"> services</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then a reaction: "This is the shape of my problem."</p>
<p>Everything before that moment is friction.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-source-available-core-matters">Why Source-Available Core Matters<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#why-source-available-core-matters" class="hash-link" aria-label="Direct link to Why Source-Available Core Matters" title="Direct link to Why Source-Available Core Matters">​</a></h2>
<p>For infrastructure, source availability is not only about ideology. It is a
trust tool.</p>
<p>Developers can inspect the parser. They can read the planner. They can see
where values are represented. They can understand limitations without waiting
for marketing language to become documentation.</p>
<p>That is especially important for a young database. LoraDB should not ask
people to believe that every edge case is solved. It should show the work:</p>
<ul>
<li>here is the AST;</li>
<li>here is semantic analysis;</li>
<li>here is logical planning;</li>
<li>here is physical execution;</li>
<li>here is the in-memory store;</li>
<li>here are the tests.</li>
</ul>
<p>That transparency is part of the product.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-not-fully-open-for-hosted-resale">Why Not Fully Open For Hosted Resale<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#why-not-fully-open-for-hosted-resale" class="hash-link" aria-label="Direct link to Why Not Fully Open For Hosted Resale" title="Direct link to Why Not Fully Open For Hosted Resale">​</a></h2>
<p>The other side of the strategy is the BSL license.</p>
<p>The goal is not to stop developers from using LoraDB. The goal is to stop the
core engine from being repackaged immediately as a competing hosted database
service by someone who did not build or maintain it.</p>
<p>That boundary is important because database companies need long time horizons.
The hard work is not only writing a parser or a store. It is maintaining the
engine, supporting users, improving performance, documenting behavior, and
building the operational platform around it.</p>
<p>The BSL lets LoraDB be open enough for adoption while protecting the hosted
business model:</p>
<ul>
<li>internal business use is allowed;</li>
<li>development and non-production use are allowed;</li>
<li>source reading and modification are allowed;</li>
<li>hosted database-as-a-service for third parties is restricted;</li>
<li>each version converts to Apache 2.0 after the Change Date.</li>
</ul>
<p>That is the intended balance.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-journey-in-four-stages">The Journey In Four Stages<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#the-journey-in-four-stages" class="hash-link" aria-label="Direct link to The Journey In Four Stages" title="Direct link to The Journey In Four Stages">​</a></h2>
<p>The customer journey I want is deliberately simple.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="1-discovery">1. Discovery<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#1-discovery" class="hash-link" aria-label="Direct link to 1. Discovery" title="Direct link to 1. Discovery">​</a></h3>
<p>A developer finds LoraDB because they need a graph, not because they want a
platform. They read the docs, run examples, and try the query model.</p>
<p>The goal here is clarity. The website should explain what LoraDB is and what it
is not. The repo should be readable. The license should be explicit.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="2-local-adoption">2. Local Adoption<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#2-local-adoption" class="hash-link" aria-label="Direct link to 2. Local Adoption" title="Direct link to 2. Local Adoption">​</a></h3>
<p>The developer builds a prototype with local data. They care about:</p>
<ul>
<li>quick setup;</li>
<li>familiar Cypher-shaped queries;</li>
<li>predictable performance;</li>
<li>useful errors;</li>
<li>enough documentation to keep moving.</li>
</ul>
<p>This is where an in-memory engine shines. No cluster. No hosted account. No
waiting for infrastructure.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="3-internal-production">3. Internal Production<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#3-internal-production" class="hash-link" aria-label="Direct link to 3. Internal Production" title="Direct link to 3. Internal Production">​</a></h3>
<p>The team starts using LoraDB in an internal tool, agent memory system,
workflow engine, or product feature where the graph is close to the
application.</p>
<p>They care about reliability, performance, and whether limitations are honest.
This is why docs, tests, and release notes matter as much as features.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="4-managed-operations">4. Managed Operations<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#4-managed-operations" class="hash-link" aria-label="Direct link to 4. Managed Operations" title="Direct link to 4. Managed Operations">​</a></h3>
<p>Eventually, some teams do not want to operate the database themselves. They
need persistence, backups, monitoring, auth, scaling, and support.</p>
<p>That is where the hosted platform belongs. It should not replace developer
adoption. It should follow it.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-this-matters-for-product-quality">Why This Matters For Product Quality<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#why-this-matters-for-product-quality" class="hash-link" aria-label="Direct link to Why This Matters For Product Quality" title="Direct link to Why This Matters For Product Quality">​</a></h2>
<p>A hosted-first database can accidentally optimize for the buyer before the
builder. LoraDB should optimize for the builder first.</p>
<p>That affects product decisions:</p>
<ul>
<li>keep the core small enough to understand;</li>
<li>make docs practical, not ornamental;</li>
<li>publish limitations clearly;</li>
<li>make release notes explain why changes matter;</li>
<li>keep local development fast;</li>
<li>protect the hosted business without making the core feel closed.</li>
</ul>
<p>The hosted platform becomes credible only if the core earns trust.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-i-want-loradb-to-feel-like">What I Want LoraDB To Feel Like<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#what-i-want-loradb-to-feel-like" class="hash-link" aria-label="Direct link to What I Want LoraDB To Feel Like" title="Direct link to What I Want LoraDB To Feel Like">​</a></h2>
<p>I want LoraDB to feel like a database you can pick up in an afternoon and still
respect after a month.</p>
<p>Small enough to understand. Fast enough to stay close to the application.
Efficient enough that the business model does not depend on waste. Clear enough
that a developer can explain it to a teammate.</p>
<p>That is the bridge from developer trust to hosted platform.</p>
<p>The final post in this series is the public release announcement: what LoraDB
is today, what is included, what is intentionally not included yet, and where
the project goes next.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Design</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[Efficient storage is the product]]></title>
            <link>https://loradb.com/blog/efficient-storage-is-the-product</link>
            <guid>https://loradb.com/blog/efficient-storage-is-the-product</guid>
            <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB treats memory layout, traversal cost, and predictable data structures as product features.]]></description>
            <content:encoded><![CDATA[<p>When people talk about graph databases, they usually talk about query
languages, visualizations, and relationship modeling. All of that matters. But
for the kind of database I wanted, the deeper product question was storage.</p>
<p>If the database is in memory, storage efficiency is not an implementation
detail. It is the product boundary.</p>
<p>Every extra allocation is less graph. Every unnecessary clone is less fan-out.
Every vague data structure is a future performance mystery. A graph database
can have a beautiful query language and still feel wrong if the storage layer
wastes the machine.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-real-cost-of-a-graph">The Real Cost Of A Graph<a href="https://loradb.com/blog/efficient-storage-is-the-product#the-real-cost-of-a-graph" class="hash-link" aria-label="Direct link to The Real Cost Of A Graph" title="Direct link to The Real Cost Of A Graph">​</a></h2>
<p>Graphs are expensive in a specific way.</p>
<p>A node is not just a row. It has labels, properties, and identity. A
relationship is not just a foreign key. It has direction, type, endpoints, and
often properties of its own. A traversal needs to find the next relationships
quickly, then carry enough state to avoid incorrect paths, cycles, or duplicate
rows.</p>
<p>That means the storage layer needs to answer several questions cheaply:</p>
<ul>
<li>Which nodes have this label?</li>
<li>Which relationships leave this node?</li>
<li>Which relationships enter this node?</li>
<li>Which relationships have this type?</li>
<li>What properties are needed for this query?</li>
<li>Can the executor avoid materializing more than it returns?</li>
</ul>
<p>If the answer to all of those questions is "scan and allocate," the database
may still be simple, but it will not be efficient.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-bothered-me-about-existing-options">What Bothered Me About Existing Options<a href="https://loradb.com/blog/efficient-storage-is-the-product#what-bothered-me-about-existing-options" class="hash-link" aria-label="Direct link to What Bothered Me About Existing Options" title="Direct link to What Bothered Me About Existing Options">​</a></h2>
<p>This is where my frustration with existing graph databases became concrete.</p>
<p>I liked the graph model. I liked Cypher. I liked being able to express a path
in a way that looked like the domain. What I did not like was the cost profile
around many systems: too much memory overhead for small graphs, too much
operational weight for local workloads, too much indirection when the graph was
supposed to be close to the application.</p>
<p>Neo4j and other graph databases are built for broad, durable, server-side
workloads. That comes with real strengths. It also means they carry machinery
that a fast embedded or in-process graph engine may not need.</p>
<p>LoraDB starts from a different question:</p>
<blockquote>
<p>If the working set is in memory, what is the smallest honest storage model
that can support expressive graph queries?</p>
</blockquote>
<p>That question shaped the project more than any single feature.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="storage-has-to-match-the-query-engine">Storage Has To Match The Query Engine<a href="https://loradb.com/blog/efficient-storage-is-the-product#storage-has-to-match-the-query-engine" class="hash-link" aria-label="Direct link to Storage Has To Match The Query Engine" title="Direct link to Storage Has To Match The Query Engine">​</a></h2>
<p>The easiest way to build a database is to keep storage generic and make the
executor compensate. The executor can scan, filter, clone, sort, and project
until the result is correct.</p>
<p>Correct is not enough.</p>
<p>The storage layer and query engine have to meet in the middle. If a query only
needs <code>n.name</code>, storage should not force the executor to materialize the whole
node. If a pattern expands out from a known node, the executor should be able
to ask for outgoing relationships directly. If a label narrows the candidate
set, the planner should be able to use that fact.</p>
<p>That is why LoraDB is split into small crates. Storage, analyzer, compiler, and
executor each have a narrow job, but the boundaries are designed so efficiency
can move through the pipeline.</p>
<p>The planner can preserve useful shape. The executor can request the values it
needs. The store can provide graph-oriented access paths instead of pretending
everything is a table.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="memory-is-a-budget-not-a-pool">Memory Is A Budget, Not A Pool<a href="https://loradb.com/blog/efficient-storage-is-the-product#memory-is-a-budget-not-a-pool" class="hash-link" aria-label="Direct link to Memory Is A Budget, Not A Pool" title="Direct link to Memory Is A Budget, Not A Pool">​</a></h2>
<p>An in-memory database makes memory visible. That is good.</p>
<p>Disk-first systems can sometimes hide inefficient intermediate structures
behind page caches, background work, or larger machines. In-memory systems do
not get that luxury. If a query creates too many rows or keeps too many values
alive, you feel it quickly.</p>
<p>That pressure leads to better engineering:</p>
<ul>
<li>prefer stable identifiers over copying whole entities;</li>
<li>project only what the query asks for;</li>
<li>keep intermediate rows narrow;</li>
<li>make path expansion explicit;</li>
<li>choose data structures whose cost can be explained;</li>
<li>avoid turning every operation into a serialization boundary.</li>
</ul>
<p>The result is not just better performance. It is better developer experience,
because the system becomes easier to reason about.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="efficient-storage-changes-the-customer-journey">Efficient Storage Changes The Customer Journey<a href="https://loradb.com/blog/efficient-storage-is-the-product#efficient-storage-changes-the-customer-journey" class="hash-link" aria-label="Direct link to Efficient Storage Changes The Customer Journey" title="Direct link to Efficient Storage Changes The Customer Journey">​</a></h2>
<p>Storage efficiency is not only about serving large users. It helps the first
user too.</p>
<p>A developer evaluating LoraDB should be able to start with a laptop and a real
slice of data. They should not need to provision an oversized machine because
the graph representation is bloated. They should not need to design a caching
strategy before writing the first useful query.</p>
<p>Efficient storage makes the journey smoother:</p>
<ol>
<li>The local prototype feels fast.</li>
<li>The first internal workload stays cheap.</li>
<li>The team trusts that performance comes from the engine, not from accident.</li>
<li>The hosted platform later has a strong unit economics base.</li>
</ol>
<p>That last point matters. A hosted database company lives or dies on efficiency.
If the core engine wastes memory, the cloud product either becomes expensive or
slow. LoraDB's business strategy depends on the same thing the developer
experience depends on: doing more with less.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-efficient-means-for-loradb">What "Efficient" Means For LoraDB<a href="https://loradb.com/blog/efficient-storage-is-the-product#what-efficient-means-for-loradb" class="hash-link" aria-label="Direct link to What &quot;Efficient&quot; Means For LoraDB" title="Direct link to What &quot;Efficient&quot; Means For LoraDB">​</a></h2>
<p>For LoraDB, efficient storage means:</p>
<ul>
<li>graph-native access to nodes and relationships;</li>
<li>predictable in-memory structures;</li>
<li>cheap directional traversal;</li>
<li>clear ownership and borrowing in Rust;</li>
<li>minimal materialization between query stages;</li>
<li>APIs that can evolve toward persistence without hiding current costs.</li>
</ul>
<p>It does not mean the first version is finished. It means the system is built
around the right constraint.</p>
<p>I would rather have a smaller database with a storage model I can explain than
a larger database that only feels fast in benchmark slides.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-standard-i-want">The Standard I Want<a href="https://loradb.com/blog/efficient-storage-is-the-product#the-standard-i-want" class="hash-link" aria-label="Direct link to The Standard I Want" title="Direct link to The Standard I Want">​</a></h2>
<p>The standard for LoraDB is simple:</p>
<p>When a developer asks why a query used the memory it used, we should be able to
answer.</p>
<p>When a customer asks why hosted LoraDB can be cost-effective, the answer should
start in the storage engine, not in pricing tricks.</p>
<p>When a contributor opens the code, the core data structures should be legible.</p>
<p>That is why efficient storage is not a hidden concern. It is the product.</p>
<p>The next post is about trust: how a developer-first graph database becomes a
real customer journey instead of just a repository with good intentions.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Architecture</category>
            <category>Deep dive</category>
        </item>
        <item>
            <title><![CDATA[In-memory or it does not work]]></title>
            <link>https://loradb.com/blog/in-memory-or-it-does-not-work</link>
            <guid>https://loradb.com/blog/in-memory-or-it-does-not-work</guid>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB starts with hot data, predictable traversal, and a graph engine close to the application.]]></description>
            <content:encoded><![CDATA[<p>The phrase "in-memory database" can sound like a performance trick. For
LoraDB, it is more basic than that. The product I wanted to build did not make
sense if the graph was slow to touch.</p>
<p>Graphs are not like simple key-value lookups. The interesting queries walk.
They expand. They branch. They filter while moving through relationships. A
single product interaction can turn into a set of small traversals that need to
feel instant.</p>
<p>If the graph is on the hot path, latency is not an optimization. It is the
product.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-moment-a-graph-becomes-product-logic">The Moment A Graph Becomes Product Logic<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#the-moment-a-graph-becomes-product-logic" class="hash-link" aria-label="Direct link to The Moment A Graph Becomes Product Logic" title="Direct link to The Moment A Graph Becomes Product Logic">​</a></h2>
<p>The first version of a graph workload is often a background report:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">a</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">CONNECTED_TO</span><span class="token operator">*</span><span class="token number">1</span><span class="token operator">..</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">b</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> a</span><span class="token punctuation">,</span><span class="token plain"> b</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">100</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>That can be slow and still useful. Someone waits, gets an answer, and learns
something.</p>
<p>But the workloads I cared about moved closer to the user:</p>
<ul>
<li>Which related entities should appear while someone is typing?</li>
<li>Which memories should an agent read before choosing a tool?</li>
<li>Which product, document, or event is connected enough to matter now?</li>
<li>Which path explains why two things are related?</li>
</ul>
<p>Those are not nightly jobs. Those are interaction-time questions.</p>
<p>Once a graph query becomes product logic, a few milliseconds change the shape
of what you can build. You stop treating the graph as a separate analytics
system and start treating it as part of the application runtime.</p>
<p>That is where in-memory starts to matter.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-a-remote-database-often-felt-wrong">Why A Remote Database Often Felt Wrong<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#why-a-remote-database-often-felt-wrong" class="hash-link" aria-label="Direct link to Why A Remote Database Often Felt Wrong" title="Direct link to Why A Remote Database Often Felt Wrong">​</a></h2>
<p>Remote databases are powerful. They centralize state. They add durability,
access control, replication, backup, and operational boundaries.</p>
<p>They also add distance.</p>
<p>For a graph workload, that distance can be painful because the query engine is
already doing pointer-heavy work. Relationship expansion is not one lookup; it
is a sequence of dependent reads. If the system has to cross process, network,
serialization, and storage boundaries for every meaningful step, the database
may still be correct, but the product no longer feels direct.</p>
<p>With LoraDB, I wanted the first experience to be different:</p>
<ul>
<li>the graph lives next to the code;</li>
<li>the query engine runs in the same machine;</li>
<li>the storage layout is optimized for traversal;</li>
<li>the cost of experimenting is low;</li>
<li>tests can spin up a database without infrastructure.</li>
</ul>
<p>That is not the only valid architecture. It is the right first architecture for
the customer journey I wanted.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="fast-is-not-just-benchmarks">Fast Is Not Just Benchmarks<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#fast-is-not-just-benchmarks" class="hash-link" aria-label="Direct link to Fast Is Not Just Benchmarks" title="Direct link to Fast Is Not Just Benchmarks">​</a></h2>
<p>I care about benchmarks, but benchmarks are not the whole story.</p>
<p>For a developer trying a database, "fast" means:</p>
<ul>
<li>the server starts quickly;</li>
<li>small graphs load quickly;</li>
<li>common queries complete without surprise;</li>
<li>the API does not force unnecessary data conversion;</li>
<li>the result shape is predictable;</li>
<li>the failure mode is understandable.</li>
</ul>
<p>A graph database can post impressive numbers and still feel slow if every
interaction requires operational setup. It can also feel unreliable if a query
allocates aggressively, materializes too much, or hides planner choices.</p>
<p>LoraDB's first design goal was to make the happy path cheap:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">u</span><span class="token operator">:</span><span class="token class-name">User</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">LIKES</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">topic</span><span class="token operator">:</span><span class="token class-name">Topic</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WHERE</span><span class="token plain"> u</span><span class="token punctuation">.</span><span class="token plain">id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable">$user_id</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> topic</span><span class="token punctuation">.</span><span class="token plain">name</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">ORDER</span><span class="token plain"> </span><span class="token keyword">BY</span><span class="token plain"> topic</span><span class="token punctuation">.</span><span class="token plain">score </span><span class="token keyword">DESC</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">10</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>That kind of query should not feel like an analytics job. It should feel like
ordinary application code.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-cypher-still-matters">Why Cypher Still Matters<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#why-cypher-still-matters" class="hash-link" aria-label="Direct link to Why Cypher Still Matters" title="Direct link to Why Cypher Still Matters">​</a></h2>
<p>If speed were the only goal, the easiest answer would be a custom Rust API with
hand-written traversal functions. That would be faster to implement and easier
to optimize in the narrow case.</p>
<p>But it would make the customer journey worse.</p>
<p>Cypher gives the database a shared language. A developer can look at a query
and understand the shape of the graph. A teammate can review it. A user moving
from another graph system does not have to learn an entirely new model before
getting value.</p>
<p>The tradeoff is implementation cost. A real query language means parser,
semantic analysis, planning, execution, projection, aggregation, paths,
functions, errors, and documentation.</p>
<p>I accepted that cost because the goal was not just "fast graph operations." The
goal was a database.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-in-memory-does-not-mean-toy">Why In-Memory Does Not Mean Toy<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#why-in-memory-does-not-mean-toy" class="hash-link" aria-label="Direct link to Why In-Memory Does Not Mean Toy" title="Direct link to Why In-Memory Does Not Mean Toy">​</a></h2>
<p>There is a common assumption that in-memory means temporary, toy, or
non-serious. I think that assumption comes from confusing deployment model with
engineering quality.</p>
<p>An in-memory graph database can still have:</p>
<ul>
<li>a real query language;</li>
<li>a real planner;</li>
<li>typed values;</li>
<li>path semantics;</li>
<li>deterministic tests;</li>
<li>production use for internal workloads;</li>
<li>clear boundaries for future persistence.</li>
</ul>
<p>Starting in memory makes the first version more honest. It forces the core
engine to be useful before the system grows persistence, clustering, and
managed operations around it.</p>
<p>For LoraDB, that was important. I did not want to hide inefficiency behind
hardware. I wanted the engine to be small enough that performance problems had
nowhere to hide.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-product-feeling">The Product Feeling<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#the-product-feeling" class="hash-link" aria-label="Direct link to The Product Feeling" title="Direct link to The Product Feeling">​</a></h2>
<p>The product feeling I wanted is this:</p>
<p>You clone the repo. You run the server. You send a Cypher query. You get a
result. You can read the code path from HTTP request to query plan to graph
storage without losing the thread.</p>
<p>That feeling builds trust.</p>
<p>Trust is what turns a curious developer into an adopter. Adoption is what makes
a hosted platform possible later. The hosted product can add persistence,
backups, scaling, dashboards, auth, and operational guarantees, but the core
has to feel right first.</p>
<p>That is why LoraDB begins in memory.</p>
<p>The next post is about the second constraint: storage efficiency. Because a
fast in-memory graph database is only useful if it uses memory carefully.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Architecture</category>
            <category>Design</category>
        </item>
        <item>
            <title><![CDATA[Why I started LoraDB]]></title>
            <link>https://loradb.com/blog/why-i-started-loradb</link>
            <guid>https://loradb.com/blog/why-i-started-loradb</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The first reason was simple: I needed a graph database that felt fast enough to keep in the hot path.]]></description>
            <content:encoded><![CDATA[<p>I did not start LoraDB because the world was missing another database with a
logo and a query language. I started it because I kept reaching for a graph
database in places where the existing choices felt too heavy for the job.</p>
<p>The shape of the problem was clear: I needed a really fast in-memory graph
database. Not a graph feature bolted onto a document store. Not a large server
that needed its own operational plan before I could answer a product question.
Not a database that looked elegant in a demo but became expensive once the
working set, query fan-out, and deployment model got real.</p>
<p>I needed something smaller, sharper, and more efficient.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-workload-that-kept-coming-back">The Workload That Kept Coming Back<a href="https://loradb.com/blog/why-i-started-loradb#the-workload-that-kept-coming-back" class="hash-link" aria-label="Direct link to The Workload That Kept Coming Back" title="Direct link to The Workload That Kept Coming Back">​</a></h2>
<p>The same workload showed up in different clothes:</p>
<ul>
<li>entities connected by typed relationships;</li>
<li>metadata that mattered at query time;</li>
<li>short multi-hop traversals;</li>
<li>ranking, filtering, and projection over neighborhoods;</li>
<li>state that should be close to the application, not across a slow boundary.</li>
</ul>
<p>Sometimes the domain was product data. Sometimes it was memory for agents.
Sometimes it was internal tooling. The model kept becoming a graph because the
real world kept refusing to be a table.</p>
<p>The annoying part was not the graph model. The graph model was the relief. The
annoying part was the machinery around it.</p>
<p>I wanted to ask questions like:</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line"><span class="token keyword">MATCH</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token plain">u</span><span class="token operator">:</span><span class="token class-name">User</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">WORKED_ON</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">p</span><span class="token operator">:</span><span class="token class-name">Project</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">[</span><span class="token operator">:</span><span class="token relationship property">USES</span><span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token punctuation">(</span><span class="token plain">t</span><span class="token operator">:</span><span class="token class-name">Technology</span><span class="token punctuation">)</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">WHERE</span><span class="token plain"> u</span><span class="token punctuation">.</span><span class="token plain">id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable">$user_id</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">RETURN</span><span class="token plain"> t</span><span class="token punctuation">.</span><span class="token plain">name</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token function">count</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token keyword">AS</span><span class="token plain"> weight</span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">ORDER</span><span class="token plain"> </span><span class="token keyword">BY</span><span class="token plain"> weight </span><span class="token keyword">DESC</span><span class="token plain"></span><br></span><span class="token-line"><span class="token plain"></span><span class="token keyword">LIMIT</span><span class="token plain"> </span><span class="token number">20</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>And I wanted that query to be cheap enough that I did not have to build a cache
around the database on day one.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="respect-for-existing-systems-frustration-with-the-fit">Respect For Existing Systems, Frustration With The Fit<a href="https://loradb.com/blog/why-i-started-loradb#respect-for-existing-systems-frustration-with-the-fit" class="hash-link" aria-label="Direct link to Respect For Existing Systems, Frustration With The Fit" title="Direct link to Respect For Existing Systems, Frustration With The Fit">​</a></h2>
<p>Neo4j did a lot for the graph database category. It made Cypher feel normal.
It made graph queries approachable. It gave developers a mental model that is
still useful.</p>
<p>But for the systems I wanted to build, I did not like the efficiency tradeoff.
The operational footprint was bigger than the product surface I needed. The
runtime model was not shaped around embedding. The storage model was not
something I could easily reason about from inside a small application. Other
graph databases had their own strengths, but I kept seeing the same tension:
great ideas wrapped in systems that were too large, too remote, or too costly
for the hot path.</p>
<p>That does not make those systems bad. It means they were solving a broader
problem than mine.</p>
<p>My problem was narrower:</p>
<blockquote>
<p>Give me a graph database I can understand, run locally, keep in memory, query
with a familiar language, and make efficient enough that I trust it in the
product loop.</p>
</blockquote>
<p>LoraDB started there.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-in-memory-first">Why In-Memory First<a href="https://loradb.com/blog/why-i-started-loradb#why-in-memory-first" class="hash-link" aria-label="Direct link to Why In-Memory First" title="Direct link to Why In-Memory First">​</a></h2>
<p>In-memory is not a shortcut. It is a product decision.</p>
<p>A database that begins in memory can optimize for a different feeling:</p>
<ul>
<li>startup should be quick;</li>
<li>tests should be cheap;</li>
<li>queries should be predictable;</li>
<li>the storage layout should be easy to inspect;</li>
<li>the developer should not need a cluster before they have a prototype.</li>
</ul>
<p>For the first version of LoraDB, persistence was less important than making the
core query engine honest. If the parser, analyzer, planner, executor, and graph
store are clean in memory, then durability can be added from a strong base. If
the core is slow or unclear, persistence only preserves the wrong shape.</p>
<p>The first principle was speed in the loop. Can you model a graph, load it, run
queries, and understand what happened without ceremony?</p>
<p>That is what I wanted as a developer. That is also what I think creates the
right customer journey: adopt locally, trust the engine, then choose hosted
operations when the product deserves it.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-customer-journey-i-wanted">The Customer Journey I Wanted<a href="https://loradb.com/blog/why-i-started-loradb#the-customer-journey-i-wanted" class="hash-link" aria-label="Direct link to The Customer Journey I Wanted" title="Direct link to The Customer Journey I Wanted">​</a></h2>
<p>Most infrastructure products ask for trust too early.</p>
<p>They ask you to sign up, provision, configure, connect, migrate, monitor, and
only then discover whether the data model even fits your problem. For a graph
database, that is backwards. Developers need to feel the model first.</p>
<p>The journey should be:</p>
<ol>
<li>Run LoraDB locally.</li>
<li>Load a small graph.</li>
<li>Write a Cypher query that feels like the problem.</li>
<li>See that it is fast enough to stay in the application path.</li>
<li>Build something real.</li>
<li>Move to managed infrastructure when operations become the expensive part.</li>
</ol>
<p>That is the company model behind LoraDB: developer-first core, hosted platform
later. The database has to earn adoption before the platform can earn revenue.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="what-i-refused-to-hide">What I Refused To Hide<a href="https://loradb.com/blog/why-i-started-loradb#what-i-refused-to-hide" class="hash-link" aria-label="Direct link to What I Refused To Hide" title="Direct link to What I Refused To Hide">​</a></h2>
<p>The first versions of LoraDB are intentionally explicit about limitations. It
is in-memory. It does not pretend to be a distributed system. It does not hide
missing pieces behind enterprise language. It is a graph database engine with a
clear query pipeline and a storage model that can be read.</p>
<p>That matters because efficiency is not only a benchmark result. Efficiency is
also whether a developer can answer:</p>
<ul>
<li>Where did this allocation come from?</li>
<li>Why did the planner choose this direction?</li>
<li>How many rows does this operation materialize?</li>
<li>What does this value look like in storage?</li>
<li>Can I debug this without becoming an archaeologist?</li>
</ul>
<p>I started LoraDB because I wanted those answers to be reachable.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="the-bet">The Bet<a href="https://loradb.com/blog/why-i-started-loradb#the-bet" class="hash-link" aria-label="Direct link to The Bet" title="Direct link to The Bet">​</a></h2>
<p>The bet is that there is room for a graph database that is smaller, faster to
adopt, easier to embed, and honest about the path from open development to a
hosted business.</p>
<p>Not every workload needs LoraDB. Some need a mature cluster with years of
operational tooling. Some need disk-first durability from the first write. Some
need distributed graph processing.</p>
<p>But many teams need a fast graph engine close to the application. Many agent
systems need structured memory. Many products need relationship queries before
they need a database department.</p>
<p>That is why I started LoraDB.</p>
<p>The next post is about the first technical constraint that shaped everything:
the database had to be fast enough to live in memory without becoming wasteful.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Design</category>
            <category>Announcement</category>
        </item>
    </channel>
</rss>