<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>D on MongrelDB</title><link>https://www.mongreldb.com/articles/tags/d/</link><description>Recent content in D on MongrelDB</description><image><title>MongrelDB</title><url>https://www.mongreldb.com/logo.png</url><link>https://www.mongreldb.com/logo.png</link></image><generator>Hugo</generator><language>en-US</language><lastBuildDate>Wed, 08 Jul 2026 18:30:00 -0500</lastBuildDate><atom:link href="https://www.mongreldb.com/articles/tags/d/index.xml" rel="self" type="application/rss+xml"/><item><title>Twelve Languages, Two Tiers, One C ABI: MongrelDB Client Architecture</title><link>https://www.mongreldb.com/articles/2026/07/mongreldb-client-architecture/</link><pubDate>Wed, 08 Jul 2026 18:30:00 -0500</pubDate><guid>https://www.mongreldb.com/articles/2026/07/mongreldb-client-architecture/</guid><description>MongrelDB today ships in twelve languages. As of this week, eight are brand new. This article walks through the two-tier architecture that produced them, why each language ended up where it did, and what the new mongreldb-ffi C ABI enables next.</description><content:encoded><![CDATA[<p>MongrelDB today ships in twelve languages. As of this week, eight of those are brand new: Go, Java/Kotlin, C#/.NET, Ruby, Swift, Zig, D, and Nim. The other four, Rust, TypeScript, Python, and PHP, landed earlier.</p>
<p>It reflects an architectural decision we made before writing the eighth client, and it has visible tradeoffs.</p>
<h2 id="the-two-tier-model">The two-tier model</h2>
<p>MongrelDB clients come in two flavors:</p>
<ol>
<li><strong>Tier 1 (in-process native):</strong> the engine runs in your process. No daemon, no serialization, no network hop. You get the same sub-10µs single-row write path as the embedded Rust API itself.</li>
<li><strong>Tier 2 (HTTP):</strong> a pure-language client connects to a running <code>mongreldb-server</code> over HTTP. There is one round-trip per query, in the low-millisecond range.</li>
</ol>
<p>We have three Tier 1 entry points today: <strong>Rust</strong> (direct, native), <strong>Python</strong> (PyO3, via MongrelDB Kit), and <strong>TypeScript / Node</strong> (NAPI addon, via either Kit or <code>crates/mongreldb-node</code>). The other nine are Tier 2: PHP, Go, Java/Kotlin, C#/.NET, Ruby, Swift, Zig, D, and Nim.</p>
<p>This is not because we could not write native bindings for the other nine. It is because we chose not to.</p>
<h2 id="why-two-tiers-exist">Why two tiers exist</h2>
<p>Twenty-five years of SQLite have produced one clear lesson: the embedded path beats the network path every time, when you have a choice. Native bindings for an embedded database are the better-sqlite3 model, and it is hard to argue with. We picked that model for Rust, TypeScript, and Python.</p>
<p>The other nine do not fit cleanly. The C toolchain story in each of them has, at best, one platform with first-class support and a long tail of platforms with rough edges:</p>
<ul>
<li><strong>Ruby</strong> has <code>gem build</code> and Fiddle. Native extensions work on MRI/YARV, less cleanly on JRuby and TruffleRuby.</li>
<li><strong>PHP</strong> has PECL. PECL extensions work everywhere PHP installs, but every install means a separate Makefile, an <code>phpize</code>, and a <code>php-config</code> check. The pure-PHP client we shipped this month exists precisely because that build matrix is painful.</li>
<li><strong>Swift</strong> has SwiftPM on macOS. On Linux, the Swift toolchain is real but second-tier. The cost is per-platform build scripts.</li>
<li><strong>Java</strong> has JNI. JNI still works in 2026, but it is a generation-old API, and every JVM in the install path is another moving part.</li>
<li><strong>C#</strong> has P/Invoke. That part is fine. The complications come from cross-platform packaging, RID resolution, and what to do about self-contained versus framework-dependent deployments.</li>
<li><strong>Go, Zig, D, Nim</strong> all have C FFI stories that work, but each brings an extra build-time dependency on a Rust compiler or C toolchain that the language&rsquo;s users did not sign up for.</li>
</ul>
<p>Native bindings for those nine would mean nine more CI matrices, nine more sets of release artifacts, and a permanent maintenance cost on every engine change. We chose to ship pure-language HTTP clients instead, with a real latency tradeoff we will not pretend away.</p>
<h2 id="what-the-http-tier-looks-like">What the HTTP tier looks like</h2>
<p>A Tier 2 client speaks a JSON-over-HTTP protocol to <code>mongreldb-server</code>. The protocol mirrors the native API surface: writes land at <code>PUT /tables/{name}/put</code> (or batch through <code>POST /kit/txn</code>), hybrid queries at <code>POST /kit/query</code>. Encoding is plain UTF-8 JSON for most requests; binary values go over base64. It is not exotic.</p>
<p>Latency for a single-row write over HTTP on loopback is dominated by <code>axum</code>&rsquo;s handler dispatch, JSON parse/serialize, and the TCP round trip, not the engine.</p>
<p>To put a number on it: on a release build over loopback HTTP, single-row <code>PUT /tables/bench/put</code> measured p50 ~0.22 ms, p99 ~0.62 ms, with ~4,000 ops/s sustained. Single-row <code>commit</code> was nearly identical, p50 ~0.21 ms, p99 ~0.53 ms. <code>fsync</code> is in the commit path but does not dominate on this hardware.</p>
<p>That is fine for most PHP, Ruby, Java, and Go workloads, where the unit of work is rarely one row. It is not fine for Python and Node code that wants the sub-10µs native path, which is exactly why Tier 1 exists for those two.</p>
<h2 id="the-c-abi-and-why-it-exists-separately">The C ABI and why it exists separately</h2>
<p>The newest crate in the repository is <code>mongreldb-ffi</code>. It exposes a stable C ABI over the engine: a fixed set of <code>extern &quot;C&quot;</code> functions for opening a database, beginning a transaction, putting a row, committing, and a handful of query helpers. Public structs and enums are <code>#[repr(C)]</code>. Opaque handles (<code>mongreldb_database_t</code>, <code>mongreldb_transaction_t</code>) wrap <code>Arc&lt;Database&gt;</code> and friends so language runtimes can hold them by pointer.</p>
<p>It is not yet what the Tier 1 clients call into. Rust and PyO3 call into <code>mongreldb-core</code> directly. The NAPI addon also calls into <code>mongreldb-core</code> directly. The C ABI exists so that future Tier 1 clients can land on top of it.</p>
<p>Three reasons we did this:</p>
<ol>
<li><strong>Stability.</strong> A C ABI is a contract you can hold to across major versions of the engine. Rust API surfaces drift; the C ABI does not.</li>
<li><strong>Reuse across FFI stacks.</strong> Swift (<code>@_cdecl</code>), Ruby (Fiddle on MRI), Go (cgo), Java (JNI), Zig (<code>@cImport</code>), Nim (direct C import), and every other language with a working C FFI all consume C headers. One crate, many targets.</li>
<li><strong>Decoupling.</strong> The C ABI can change without the engine changing, and vice versa. That means we can ship and version the FFI surface independently of the engine, which we will need to do.</li>
</ol>
<p>The current <code>mongreldb-ffi</code> is small on purpose. It exposes the minimum surface needed for an &ldquo;open, write a few thousand rows, commit, query, close&rdquo; workflow, plus error and panic-safety plumbing (<code>mongreldb_last_error</code>, <code>mongreldb_last_error_code</code>, <code>mongreldb_free_error_string</code>, free functions for every allocated handle). We will extend it as we add Tier 1 bindings for more languages.</p>
<h2 id="picking-the-right-tier">Picking the right tier</h2>
<p>If you are writing Rust, Python, or TypeScript / Node against MongrelDB, use the <strong>Tier 1</strong> binding. You get in-process speed, no daemon to run, and the same <code>mongreldb-core</code> API surface used by our own server. The NAPI and PyO3 clients are not wrappers; they are first-class Rust compiled to a shared library and bound through standard FFI machinery. For Node specifically, the raw <code>mongreldb-node</code> NAPI addon (the better-sqlite3 model, <code>cd crates/mongreldb-node &amp;&amp; npm install &amp;&amp; npm run build</code>) is the lowest-overhead path. Kit wraps the same engine behind a typed object API if you would rather skip the build step.</p>
<p>If you are writing PHP, Go, Java/Kotlin, C#/.NET, Ruby, Swift, Zig, D, or Nim, use the <strong>Tier 2</strong> HTTP client. Run <code>mongreldb-server</code> alongside your app (systemd, container, sidecar), point the client at it, and you get the same engine underneath with a network in the middle.</p>
<p>If your language is not on the list, the C ABI is the on-ramp. Either we already have a Tier 1 prototype and we can wire you up, or you can ship your own binding on top of <code>mongreldb-ffi</code> and we will help you land it on the org.</p>
<h2 id="what-we-are-shipping-next">What we are shipping next</h2>
<p>We will keep extending Tier 2 to languages where maintaining a pure-language HTTP client is the right call, including maintenance and small feature work on the eight new clients.</p>
<p>We have no specific Tier 1 language conversion committed. <code>mongreldb-ffi</code> is the on-ramp; what we land on top of it will be picked per language when the engineering case is clear.</p>
<h2 id="where-to-find-each-client">Where to find each client</h2>
<table>
	<thead>
			<tr>
					<th>Language</th>
					<th>Tier</th>
					<th>Install</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Rust</td>
					<td>1</td>
					<td><code>cargo add mongreldb-core</code></td>
			</tr>
			<tr>
					<td>TypeScript / Node</td>
					<td>1</td>
					<td>Kit: <code>npm install @visorcraft/mongreldb-kit</code> · raw NAPI: build from <code>crates/mongreldb-node</code></td>
			</tr>
			<tr>
					<td>Python</td>
					<td>1</td>
					<td><code>pip install mongreldb-kit</code></td>
			</tr>
			<tr>
					<td>PHP</td>
					<td>2</td>
					<td><code>composer require visorcraft/mongreldb-php</code></td>
			</tr>
			<tr>
					<td>Go</td>
					<td>2</td>
					<td><code>go get github.com/visorcraft/mongreldb-go</code></td>
			</tr>
			<tr>
					<td>Java / Kotlin</td>
					<td>2</td>
					<td>Maven / Gradle: <code>com.visorcraft:mongreldb</code></td>
			</tr>
			<tr>
					<td>C# / .NET</td>
					<td>2</td>
					<td><code>dotnet add package Visorcraft.MongrelDB</code></td>
			</tr>
			<tr>
					<td>Ruby</td>
					<td>2</td>
					<td><code>gem install mongreldb</code></td>
			</tr>
			<tr>
					<td>Swift</td>
					<td>2</td>
					<td>SwiftPM: <code>https://github.com/visorcraft/MongrelDB-Swift</code></td>
			</tr>
			<tr>
					<td>Zig</td>
					<td>2</td>
					<td><code>zig fetch</code> from the repo</td>
			</tr>
			<tr>
					<td>D</td>
					<td>2</td>
					<td><code>dub add mongreldb</code></td>
			</tr>
			<tr>
					<td>Nim</td>
					<td>2</td>
					<td><code>nimble install mongreldb</code></td>
			</tr>
	</tbody>
</table>
<p>The wire protocol for Tier 2 is documented at <a href="https://www.mongreldb.com/docs/08-daemon/">docs/08-daemon</a>. The C ABI surface lives in <a href="https://github.com/visorcraft/MongrelDB/tree/main/crates/mongreldb-ffi"><code>crates/mongreldb-ffi</code></a>. For each client&rsquo;s install command and examples, see its repository.</p>
<p>If you maintain a language we do not yet support, file an issue with the language name. The C ABI makes the answer shorter than it used to be. For the release announcement and install commands for the eight new HTTP clients, see the <a href="/2026/07/eight-new-mongreldb-clients/">companion post</a>.</p>
]]></content:encoded></item></channel></rss>