<?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>Encryption on MongrelDB</title><link>https://www.mongreldb.com/articles/tags/encryption/</link><description>Recent content in Encryption 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>Mon, 13 Jul 2026 09:00:00 -0500</lastBuildDate><atom:link href="https://www.mongreldb.com/articles/tags/encryption/index.xml" rel="self" type="application/rss+xml"/><item><title>Encryption at Rest Without Paying the SQLite SEE Tax</title><link>https://www.mongreldb.com/articles/2026/07/encryption-at-rest-without-paying-the-sqlite-see-tax/</link><pubDate>Mon, 13 Jul 2026 09:00:00 -0500</pubDate><guid>https://www.mongreldb.com/articles/2026/07/encryption-at-rest-without-paying-the-sqlite-see-tax/</guid><description>SQLite SEE is the paid benchmark for database encryption at rest. Here is what building your own encryption layer costs in throughput and what the tradeoffs look like in production.</description><content:encoded><![CDATA[<p>If you are shipping a database that handles anything adjacent to personal data, regulated data, or anything a compliance auditor will eventually ask about, encryption at rest stops being a feature and starts being a checkbox you fail without it. SQLite has an answer for this: SQLite SEE, the SQLite Encryption Extension, which is the reference implementation that every comparison in the space gets measured against. It is also a paid product with a perpetual source-code license, royalty-free for use in your own products, but with a closed-source distribution model that makes the source auditability story harder for a team shipping an open-source engine. We needed encryption at rest, we did not want to route money to a third party for a component we could build, and the honest story of what we built and what it costs is worth telling.</p>
<h2 id="the-shape-of-the-problem">The shape of the problem</h2>
<p>SQLite SEE works by presenting the database file as encrypted pages on disk and decrypting them into the page cache on read, which means the WAL, the shared memory region, and every tempfile that hits the filesystem are all covered by the same symmetric key. The encryption boundary is the entire file-level storage surface, and the key is held in memory for the lifetime of the process. That is the right model. Any encryption that does not cover the WAL is not encryption at rest, it is encryption at rest for the parts of the database that are not being actively written, and anyone who has watched a production incident knows that the interesting data is usually in the WAL during the incident.</p>
<p>The hard part is not the encryption itself; AES-256 in an appropriate mode is solved, and you reach for a library rather than rolling your own. The hard part is the key lifecycle, the page-level IO pattern that a database engine uses, and the throughput cost when every read and write hits the crypto layer before anything else.</p>
<h2 id="what-we-built-instead">What we built instead</h2>
<p>MongrelDB uses a two-layer encryption surface: the page layer and the WAL layer share a derived key that is derived from a master key via HKDF-SHA256 with a per-table salt, and the master key itself is injected at startup from an environment variable or a plugin hook rather than stored on disk anywhere. The page encryption operates on 4KB page boundaries with AES-256-GCM, which gives us authenticated encryption so that a corrupted or tampered page on disk is detectable rather than silently misread, and page-layer key rotation is handled by re-encrypting pages in-place on a background thread during a controlled maintenance window, with the next-write rewrite path covering any pages that were not caught by the re-encryption pass. The WAL gets its own per-segment key derivation so that a WAL segment written at time T is not decryptable with a key derived after time T if the master key has been rotated in the interim.</p>
<p>The reason for the plugin hook rather than a fixed key storage mechanism is that different deployment shapes have different constraints around secret distribution, and the plugin hook keeps the engine honest about the abstraction boundary rather than baking in assumptions about where keys come from.</p>
<h2 id="what-it-costs">What it costs</h2>
<p>The measured number is between 8 and 15 percent throughput reduction on write-heavy workloads at the storage layer, measured on the same hardware and same fsync profile as the unencrypted baseline. Read-heavy workloads see less, closer to 3 to 7 percent, because the page cache absorbs a portion of the read path and encrypted pages in cache cost nothing extra to serve. The GCM authentication tag adds 16 bytes per page, which increases WAL segment size by a small percentage on write-heavy workloads, and the per-segment WAL key derivation adds a small constant cost to each WAL segment open.</p>
<p>The number that matters more than the percentage is the shape of the cost curve. Encryption at rest is a constant factor on every IO operation, which means the overhead is proportionally largest on small transactions with many fsync calls and proportionally smallest on large sequential writes where the crypto cost is amortized over many rows per fsync. If your workload is dominated by bulk import, the measured overhead will be lower than if your workload is dominated by single-row commits with synchronous durability requirements.</p>
<h2 id="the-comparison-that-gets-asked-for">The comparison that gets asked for</h2>
<p>SQLite SEE uses a similar page-level encryption model with AES-128-CBC by default, and the performance profile is close enough that the real differentiator is not the crypto algorithm but the authentication property and the key management surface. CBC mode is unauthenticated, which means a bit-flip attack on an encrypted page produces modified plaintext with no checksum to catch it; GCM is authenticated, which means MongrelDB encrypted pages detect tampering rather than returning silently corrupted data, and that property is the engineering argument worth making explicitly rather than leaving it as an implicit difference. We did not build our own crypto to beat SEE on throughput, we built it because the closed-source distribution model does not fit a database that ships as a single open-source binary with no vendor relationship, and if your threat model includes the database vendor as a potential adversary, you should be using hardware-enforced encryption anyway, but if your threat model is &ldquo;the disk gets pulled or the machine gets stolen,&rdquo; a software encryption layer at the page level covers the practical attack surface for most threat models short of nation-state adversaries with physical disk access and a budget for hardware forensics.</p>
<p>The tradeoff is what it always is: you are trading a known-cost constant factor on every IO operation for protection against a class of incidents that are rare, expensive when they happen, and impossible to retrofit after the fact. Whether that tradeoff is worth it for your workload is not a question we can answer for you, but the numbers above are the inputs to that calculation.</p>
]]></content:encoded></item></channel></rss>