<feed
    xmlns="http://www.w3.org/2005/Atom"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xml:lang="en">
  <link rel="self"      type="application/atom+xml" href="https://euandre.org/en/blog/feed.mediator.xml" />
  <link rel="alternate" type="text/html"            href="https://euandre.org/en/blog/" hreflang="en" />
  <title>EuAndreh&apos;s website</title>
  <subtitle>Blog</subtitle>
  <id>https://euandre.org/en/blog/feed.mediator.xml</id>
  <updated>2026-09-13T12:14:51+00:00</updated>
  <author>
    <name>EuAndreh</name>
    <email>eu@euandre.org</email>
  </author>
  <entry xml:lang="en">
    <title type="html">
      Durable persistent trees and parser combinators - building a database
    </title>
    <link type="text/html" rel="alternative" href="https://euandre.org/en/blog/2020/11/12/database-parsers-trees.html" title="Durable persistent trees and parser combinators - building a database" />
    <published>
      2020-11-12T00:00:00+00:00
    </published>
    <updated>
      2021-02-09T00:00:00+00:00
    </updated>
    <id>
      https://euandre.org/en/blog/2020/11/12/database-parsers-trees.html
    </id>
    <author>
      <name>
        EuAndreh
      </name>
      <email>
        eu@euandre.org
      </email>
    </author>
    <summary type="html">
        &lt;div id=&quot;preamble&quot;&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I’ve received with certain frequency messages from people wanting to know if I’ve made any progress on
              the database project &lt;a href=&quot;../../08/31/database-i-wish-i-had.html&quot;&gt;I’ve written about&lt;/a&gt;.&lt;/p&gt;
    </summary>
    <content type="html" xml:base="en/blog/2020/11/12/database-parsers-trees.html">
        &lt;div id=&quot;preamble&quot;&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I’ve received with certain frequency messages from people wanting to know if I’ve made any progress on
              the database project &lt;a href=&quot;../../08/31/database-i-wish-i-had.html&quot;&gt;I’ve written about&lt;/a&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;There are a few areas where I’ve made progress, and here’s a public post on it.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;proof-of-concept-dag-log&quot;&gt;Proof-of-concept: DAG log&lt;a class=&quot;anchor&quot;
             href=&quot;#proof-of-concept-dag-log&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The main thing I wanted to validate with a concrete implementation was the concept of modeling a DAG
              on a sequence of datoms.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The notion of a &lt;em&gt;datom&lt;/em&gt; is a rip-off from Datomic, which models data with time aware
              &lt;em&gt;facts&lt;/em&gt;, which come from RDF. RDF’s fact is a triple of subject-predicate-object, and Datomic’s
              datoms add a time component to it: subject-predicate-object-time, A.K.A.
              entity-attribute-value-transaction:&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
              &lt;div class=&quot;content&quot;&gt;
                &lt;table&gt;
                  &lt;tbody&gt;
                    &lt;tr&gt;
                      &lt;td&gt;
                        &lt;pre class=&quot;lineno&quot;&gt;&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B1-L1&quot;
     href=&quot;#B1-L1&quot;&gt;1&lt;/a&gt;
&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B1-L2&quot;
     href=&quot;#B1-L2&quot;&gt;2&lt;/a&gt;
&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B1-L3&quot;
     href=&quot;#B1-L3&quot;&gt;3&lt;/a&gt;&lt;/pre&gt;
                      &lt;/td&gt;
                      &lt;td&gt;
                        &lt;pre&gt;[[person :likes &quot;pizza&quot; 0 true]
 [person :likes &quot;bread&quot; 1 true]
 [person :likes &quot;pizza&quot; 1 false]]&lt;/pre&gt;
                      &lt;/td&gt;
                    &lt;/tr&gt;
                  &lt;/tbody&gt;
                &lt;/table&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph plaintext&quot;&gt;
              &lt;p&gt;&lt;a href=&quot;database-parsers-trees.html.0.txt&quot;&gt;plaintext&lt;/a&gt;&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The above datoms say: - at time 0, &lt;code&gt;person&lt;/code&gt; like pizza; - at time 1, &lt;code&gt;person&lt;/code&gt;
              stopped liking pizza, and started to like bread.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;Datomic ensures total consistency of this ever growing log by having a single writer, the transactor,
              that will enforce it when writing.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;In order to support disconnected clients, I needed a way to allow multiple writers, and I chose to do
              it by making the log not a list, but a directed acyclic graph (DAG):&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
              &lt;div class=&quot;content&quot;&gt;
                &lt;table&gt;
                  &lt;tbody&gt;
                    &lt;tr&gt;
                      &lt;td&gt;
                        &lt;pre class=&quot;lineno&quot;&gt;&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B2-L1&quot;
     href=&quot;#B2-L1&quot;&gt;1&lt;/a&gt;
&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B2-L2&quot;
     href=&quot;#B2-L2&quot;&gt;2&lt;/a&gt;
&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B2-L3&quot;
     href=&quot;#B2-L3&quot;&gt;3&lt;/a&gt;
&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B2-L4&quot;
     href=&quot;#B2-L4&quot;&gt;4&lt;/a&gt;
&lt;a class=&quot;code-line-anchor&quot;
     id=&quot;B2-L5&quot;
     href=&quot;#B2-L5&quot;&gt;5&lt;/a&gt;&lt;/pre&gt;
                      &lt;/td&gt;
                      &lt;td&gt;
                        &lt;pre&gt;[[person :likes  &quot;pizza&quot;  0 true]
 [0      :parent :db/root 0 true]
 [person :likes  &quot;bread&quot;  1 true]
 [person :likes  &quot;pizza&quot;  1 false]
 [1      :parent 0        1 true]]&lt;/pre&gt;
                      &lt;/td&gt;
                    &lt;/tr&gt;
                  &lt;/tbody&gt;
                &lt;/table&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph plaintext&quot;&gt;
              &lt;p&gt;&lt;a href=&quot;database-parsers-trees.html.1.txt&quot;&gt;plaintext&lt;/a&gt;&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The extra datoms above add more information to build the directionality to the log, and instead of a
              single consistent log, the DAG could have multiple leaves that coexist, much like how different Git
              branches can have different &quot;latest&quot; commits.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;In order to validate this idea, I started with a Clojure implementation. The goal was not to write the
              actual final code, but to make a proof-of-concept that would allow me to test and stretch the idea
              itself.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;This code &lt;a href=
              &quot;https://euandre.org/git/mediator/tree/src/core/clojure/src/mediator.clj?id=db4a727bc24b54b50158827b34502de21dbf8948#n1&quot;&gt;
              already exists&lt;/a&gt;, but is yet fairly incomplete:&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;ulist&quot;&gt;
              &lt;ul&gt;
                &lt;li&gt;
                  &lt;p&gt;the building of the index isn’t done yet (with some &lt;a href=
                  &quot;https://euandre.org/git/mediator/tree/src/core/clojure/src/mediator.clj?id=db4a727bc24b54b50158827b34502de21dbf8948#n295&quot;&gt;
                  commented code&lt;/a&gt; on the next step to be implemented)&lt;/p&gt;
                &lt;/li&gt;
                &lt;li&gt;
                  &lt;p&gt;the indexing is extremely inefficient, with &lt;a href=
                  &quot;https://euandre.org/git/mediator/tree/src/core/clojure/src/mediator.clj?id=db4a727bc24b54b50158827b34502de21dbf8948#n130&quot;&gt;
                  more&lt;/a&gt; &lt;a href=
                  &quot;https://euandre.org/git/mediator/tree/src/core/clojure/src/mediator.clj?id=db4a727bc24b54b50158827b34502de21dbf8948#n146&quot;&gt;
                  than&lt;/a&gt; &lt;a href=
                  &quot;https://euandre.org/git/mediator/tree/src/core/clojure/src/mediator.clj?id=db4a727bc24b54b50158827b34502de21dbf8948#n253&quot;&gt;
                  one&lt;/a&gt; occurrence of &lt;code&gt;O²&lt;/code&gt; functions;&lt;/p&gt;
                &lt;/li&gt;
                &lt;li&gt;
                  &lt;p&gt;no query support yet.&lt;/p&gt;
                &lt;/li&gt;
              &lt;/ul&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;top-down-and-bottom-up&quot;&gt;Top-down &lt;em&gt;and&lt;/em&gt; bottom-up&lt;a class=&quot;anchor&quot;
             href=&quot;#top-down-and-bottom-up&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;However, as time passed and I started looking at what the final implementation would look like, I
              started to consider keeping the PoC around.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The top-down approach (Clojure PoC) was in fact helping guide me with the bottom-up, and I now have
              &quot;promoted&quot; the Clojure PoC into a &quot;reference implementation&quot;. It should now be a finished implementation
              that says what the expected behaviour is, and the actual code should match the behaviour.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The good thing about a reference implementation is that it has no performance of resources boundary,
              so if it ends up being 1000× slower and using 500× more memory, it should be find. The code can be also
              10× or 100× simpler, too.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;top-down-durable-persistent-trees&quot;&gt;Top-down: durable persistent trees&lt;a class=&quot;anchor&quot;
             href=&quot;#top-down-durable-persistent-trees&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;In promoting the PoC into a reference implementation, this top-down approach now needs to go beyond
              doing everything in memory, and the index data structure now needs to be disk-based.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;Roughly speaking, most storage engines out there are based either on B-Trees or LSM Trees, or some
              variations of those.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;But when building an immutable database, update-in-place B-Trees aren’t an option, as it doesn’t
              accommodate keeping historical views of the tree. LSM Trees may seem a better alternative, but
              duplication on the files with compaction are also ways to delete old data which is indeed useful for a
              historical view.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I think the thing I’m after is a mix of a Copy-on-Write B-Tree, which would keep historical versions
              with the write IO cost amortization of memtables of LSM Trees. I don’t know of any B-Tree variant out
              there that resembles this, so I’ll call it &quot;Flushing Copy-on-Write B-Tree&quot;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I haven’t written any code for this yet, so all I have is a high-level view of what it will look
              like:&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;olist arabic&quot;&gt;
              &lt;ol class=&quot;arabic&quot;&gt;
                &lt;li&gt;
                  &lt;p&gt;like Copy-on-Write B-Trees, changing a leaf involves creating a new leaf and building a new path
                  from root to the leaf. The upside is that writes a lock free, and no coordination is needed between
                  readers and writers, ever;&lt;/p&gt;
                &lt;/li&gt;
                &lt;li&gt;
                  &lt;p&gt;the downside is that a single leaf update means at least &lt;code&gt;H&lt;/code&gt; new nodes that will have
                  to be flushed to disk, where &lt;code&gt;H&lt;/code&gt; is the height of the tree. To avoid that, the writer
                  creates these nodes exclusively on the in-memory memtable, to avoid flushing to disk on every leaf
                  update;&lt;/p&gt;
                &lt;/li&gt;
                &lt;li&gt;
                  &lt;p&gt;a background job will consolidate the memtable data every time it hits X MB, and persist it to
                  disk, amortizing the cost of the Copy-on-Write B-Tree;&lt;/p&gt;
                &lt;/li&gt;
                &lt;li&gt;
                  &lt;p&gt;readers than will have the extra job of getting the latest relevant disk-resident value and merge
                  it with the memtable data.&lt;/p&gt;
                &lt;/li&gt;
              &lt;/ol&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The key difference to existing Copy-on-Write B-Trees is that the new trees are only periodically
              written to disk, and the intermediate values are kept in memory. Since no node is ever updated, the page
              utilization is maximum as it doesn’t need to keep space for future inserts and updates.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;And the key difference to existing LSM Trees is that no compaction is run: intermediate values are
              still relevant as the database grows. So this leaves out tombstones and value duplication done for write
              performance.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;One can delete intermediate index values to reclaim space, but no data is lost on the process, only
              old B-Tree values. And if the database ever comes back to that point (like when doing a historical
              query), the B-Tree will have to be rebuilt from a previous value. After all, the database &lt;em&gt;is&lt;/em&gt; a
              set of datoms, and everything else is just derived data.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;Right now I’m still reading about other data structures that storage engines use, and I’ll start
              implementing the &quot;Flushing Copy-on-Write B-Tree&quot; as I learn morefootnote:learn-more-db[ If you are
              interested in learning more about this too, the very best two resources on this subject are Andy Pavlo’s
              &quot;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLSE8ODhjZXjbohkNBWQs_otTrBTrjyohi&quot;&gt;Intro to Database
              Systems&lt;/a&gt;&quot; course and Alex Petrov’s &quot;&lt;a href=&quot;https://www.databass.dev/&quot;&gt;Database Internals&lt;/a&gt;&quot; book.
              ] and mature it more.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;bottom-up-parser-combinators-and-ffi&quot;&gt;Bottom-up: parser combinators and FFI&lt;a class=&quot;anchor&quot;
             href=&quot;#bottom-up-parser-combinators-and-ffi&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I chose Rust as it has the best WebAssembly tooling support.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;My goal is not to build a Rust database, but a database that happens to be in Rust. In order to reach
              client platforms, the primary API is the FFI one.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I’m not very happy with current tools for exposing Rust code via FFI to the external world: they
              either mix C with C++, which I don’t want to do, or provide no access to the intermediate representation
              of the FFI, which would be useful for generating binding for any language that speaks FFI.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I like better the path that the author of &lt;a href=&quot;https://github.com/eqrion/cbindgen&quot;&gt;cbindgen&lt;/a&gt;
              crate &lt;a href=&quot;https://blog.eqrion.net/future-directions-for-cbindgen/&quot;&gt;proposes&lt;/a&gt;: emitting an data
              representation of the Rust C API (the author calls is a &lt;code&gt;ffi.json&lt;/code&gt; file), and than building
              transformers from the data representation to the target language. This way you could generate a C API
              &lt;em&gt;and&lt;/em&gt; the node-ffi bindings for JavaScript automatically from the Rust code.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;So the first thing to be done before moving on is an FFI exporter that doesn’t mix C and C++, and
              generates said &lt;code&gt;ffi.json&lt;/code&gt;, and than build a few transformers that take this
              &lt;code&gt;ffi.json&lt;/code&gt; and generate the language bindings, be it C, C++, JavaScript, TypeScript, Kotlin,
              Swift, Dart, _etc_footnote:ffi-langs[ Those are, specifically, the languages I’m more interested on. My
              goal is supporting client applications, and those languages are the most relevant for doing so: C for
              GTK, C++ for Qt, JavaScript and TypeScript for Node.js and browser, Kotlin for Android and Swing, Swift
              for iOS, and Dart for Flutter. ].&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I think the best way to get there is by taking the existing code for cbindgen, which uses the &lt;a href=
              &quot;https://github.com/dtolnay/syn&quot;&gt;syn&lt;/a&gt; crate to parse the Rust codefootnote:rust-syn[ The fact that syn
              is an external crate to the Rust compiler points to a big warning: procedural macros are not first class
              in Rust. They are just like Babel plugins in JavaScript land, with the extra shortcoming that there is no
              specification for the Rust syntax, unlike JavaScript.&lt;/p&gt;
              &lt;p&gt;As flawed as this may be, it seems to be generally acceptable and adopted, which works against
              building a solid ecosystem for Rust.&lt;/p&gt;
              &lt;p&gt;The alternative that rust-ffi implements relies on internals of the Rust compiler, which isn’t
              actually worst, just less common and less accepted. ], and adapt it to emit the metadata.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I’ve started a fork of cbindgen: &lt;span class=&quot;line-through&quot;&gt;x-bindgen&lt;/span&gt;footnote:x-bindgen[
              &lt;em&gt;EDIT&lt;/em&gt;: now archived, the experimentation was fun. I’ve started to move more towards C, so this
              effort became deprecated. ]. Right now it is just a copy of cbindgen verbatim, and I plan to remove all C
              and C++ emitting code from it, and add a IR emitting code instead.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;When starting working on x-bindgen, I realized I didn’t know what to look for in a header file, as I
              haven’t written any C code in many years. So as I was writing &lt;a href=
              &quot;https://euandre.org/git/libedn/&quot;&gt;libedn&lt;/a&gt;, I didn’t know how to build a good C API to expose. So I
              tried porting the code to C, and right now I’m working on building a &lt;em&gt;good&lt;/em&gt; C API for a JSON
              parser using parser combinators: &lt;span class=&quot;line-through&quot;&gt;ParsecC&lt;/span&gt;footnote:parsecc[
              &lt;em&gt;EDIT&lt;/em&gt;: now also archived. ].&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;After &quot;finishing&quot; ParsecC I’ll have a good notion of what a good C API is, and I’ll have a better
              direction towards how to expose code from libedn to other languages, and work on x-bindgen then.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;What both libedn and ParsecC are missing right now are proper error reporting, and property-based
              testing for libedn.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;anchor&quot;
             href=&quot;#conclusion&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I’ve learned a lot already, and I feel the journey I’m on is worth going through.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;If any of those topics interest you, message me to discuss more or contribute! Patches welcome!&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
    </content>
  </entry>
  <entry xml:lang="en">
    <title type="html">
      The database I wish I had
    </title>
    <link type="text/html" rel="alternative" href="https://euandre.org/en/blog/2020/08/31/database-i-wish-i-had.html" title="The database I wish I had" />
    <published>
      2020-08-31T00:00:00+00:00
    </published>
    <updated>
      2020-09-03T00:00:00+00:00
    </updated>
    <id>
      https://euandre.org/en/blog/2020/08/31/database-i-wish-i-had.html
    </id>
    <author>
      <name>
        EuAndreh
      </name>
      <email>
        eu@euandre.org
      </email>
    </author>
    <summary type="html">
        &lt;div id=&quot;preamble&quot;&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I watched the talk &quot;&lt;a href=&quot;https://vimeo.com/230142234&quot;&gt;Platform as a Reflection of Values: Joyent,
              Node.js and beyond&lt;/a&gt;&quot; by Bryan Cantrill, and I think he was able to put into words something I already
    </summary>
    <content type="html" xml:base="en/blog/2020/08/31/database-i-wish-i-had.html">
        &lt;div id=&quot;preamble&quot;&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I watched the talk &quot;&lt;a href=&quot;https://vimeo.com/230142234&quot;&gt;Platform as a Reflection of Values: Joyent,
              Node.js and beyond&lt;/a&gt;&quot; by Bryan Cantrill, and I think he was able to put into words something I already
              felt for some time: if there’s no piece of software out there that reflects your values, it’s time for
              you to build that softwarefootnote:talk-time[ At the very end, at time 29:49. When talking about the
              draft of this article with a friend, he noted that Bryan O’Sullivan (a different Bryan) says a similar
              thing on his talk &quot;&lt;a href=&quot;https://www.youtube.com/watch?v=ZR3Jirqk6W8&quot;&gt;Running a startup on
              Haskell&lt;/a&gt;&quot;, at time 4:15. ].&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I kind of agree with what he said, because this is already happening to me. I long for a database with
              a certain set of values, and for a few years I was just waiting for someone to finally write it. After
              watching his talk, Bryan is saying to me: &quot;time to stop waiting, and start writing it yourself&quot;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;So let me try to give an overview of such database, and go over its values.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;overview&quot;&gt;Overview&lt;a class=&quot;anchor&quot;
             href=&quot;#overview&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I want a database that allows me to create decentralized client-side applications that can sync
              data.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;The best one-line description I can give right now is:&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;quoteblock&quot;&gt;
              &lt;blockquote&gt;
                &lt;div class=&quot;paragraph&quot;&gt;
                  &lt;p&gt;It’s sort of like PouchDB, Git, Datomic, SQLite and Mentat.&lt;/p&gt;
                &lt;/div&gt;
              &lt;/blockquote&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;A more descriptive version could be:&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;quoteblock&quot;&gt;
              &lt;blockquote&gt;
                &lt;div class=&quot;paragraph&quot;&gt;
                  &lt;p&gt;An embedded, immutable, syncable relational database.&lt;/p&gt;
                &lt;/div&gt;
              &lt;/blockquote&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;Let’s go over what I mean by each of those aspects one by one.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;embedded&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#embedded&quot;&gt;&lt;/a&gt;Embedded&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;I think the server-side database landscape is diverse and mature enough for my needs (even though I
                end up choosing SQLite most of the time), and what I’m after is a database to be embedded on
                client-side applications itself, be it desktop, browser, mobile, &lt;em&gt;etc.&lt;/em&gt;&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The purpose of such database is not to keep some local cache of data in case of lost connectivity:
                we have good solutions for that already. It should serve as the source of truth, and allow the
                application to work on top of it.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;&lt;a href=&quot;https://sqlite.org/index.html&quot;&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/a&gt; is a great example of that: it
                is a very powerful relational database that runs &lt;a href=&quot;https://sqlite.org/whentouse.html&quot;&gt;almost
                anywhere&lt;/a&gt;. What I miss from it that SQLite doesn’t provide is the ability to run it on the browser:
                even though you could compile it to WebAssembly, &lt;span class=&quot;line-through&quot;&gt;it assumes a POSIX
                filesystem that would have to be emulated&lt;/span&gt;footnote:posix-sqlite[ It was &lt;a href=
                &quot;https://news.ycombinator.com/item?id=24338881&quot;&gt;pointed out to me&lt;/a&gt; that SQLite doesn’t assume the
                existence of a POSIX filesystem, as I wrongly stated. Thanks for the correction.&lt;/p&gt;
                &lt;p&gt;This makes me consider it as a storage backend all by itself. I initially considered having an
                SQLite storage backend as one implementation of the POSIX filesystem storage API that I mentioned. My
                goal was to rely on it so I could validate the correctness of the actual implementation, given SQLite’s
                robustness.&lt;/p&gt;
                &lt;p&gt;However it may even better to just use SQLite, and get an ACID backend without recreating a big part
                of SQLite from scratch. In fact, both Datomic and PouchDB didn’t create an storage backend for
                themselves, they just plugged on what already existed and already worked. I’m beginning to think that
                it would be wiser to just do the same, and drop entirely the from scratch implementation that I
                mentioned.&lt;/p&gt;
                &lt;p&gt;That’s not to say that adding an IndexedDB compatibility layer to SQLite would be enough to make it
                fit the other requirements I mention on this page. SQLite still is an implementation of a
                update-in-place, SQL, table-oriented database. It is probably true that cherry-picking the relevant
                parts of SQLite (like storage access, consistency, crash recovery, parser generator,
                &lt;strong&gt;etc.&lt;/strong&gt;) and leaving out the unwanted parts (SQL, tables, threading,
                &lt;strong&gt;etc.&lt;/strong&gt;) would be better than including the full SQLite stack, that’s simply an
                optimization. Both could even coexist, if desired.&lt;/p&gt;
                &lt;p&gt;SQLite would have to be treated similarly to how Datomic treats SQL databases: instead of having a
                table for each entities, spread attributes over the tables, &lt;strong&gt;etc.&lt;/strong&gt;, it treats SQL
                databases as a key-value storage so it doesn’t have to re-implement interacting with the disk that
                other databases do well.&lt;/p&gt;
                &lt;p&gt;The tables would contain blocks of binary data, so there isn’t a difference on how the SQLite
                storage backend behaves and how the IndexedDB storage backend behaves, much like how Datomic works the
                same regardless of the storage backend, same for PouchDB.&lt;/p&gt;
                &lt;p&gt;I welcome corrections on what I said above, too. ].&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;&lt;a href=&quot;https://pouchdb.com/&quot;&gt;&lt;strong&gt;PouchDB&lt;/strong&gt;&lt;/a&gt; is another great example: it’s a full
                reimplementation of &lt;a href=&quot;https://couchdb.apache.org/&quot;&gt;CouchDB&lt;/a&gt; that targets JavaScript
                environments, mainly the browser and Node.js. However I want a tool that can be deployed anywhere, and
                not limit its applications to places that already have a JavaScript runtime environment, or force the
                developer to bundle a JavaScript runtime environment with their application. This is true for GTK+
                applications, command line programs, Android apps, &lt;em&gt;etc.&lt;/em&gt;&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;&lt;a href=&quot;https://github.com/mozilla/mentat&quot;&gt;&lt;strong&gt;Mentat&lt;/strong&gt;&lt;/a&gt; was an interesting project,
                but its reliance on SQLite makes it inherit most of the downsides (and benefits too) of SQLite
                itself.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Having such a requirement imposes a different approach to storage: we have to decouple the knowledge
                about the intricacies of storage from the usage of storage itself, so that a module (say query
                processing) can access storage through an API without needing to know about its implementation. This
                allows the database to target a POSIX filesystems storage API and an IndexedDB storage API, and make
                the rest of the code agnostic about storage. PouchDB has such mechanism (called &lt;a href=
                &quot;https://pouchdb.com/adapters.html&quot;&gt;adapters&lt;/a&gt;) and Datomic has them too (called &lt;a href=
                &quot;https://docs.datomic.com/on-prem/storage.html&quot;&gt;storage services&lt;/a&gt;).&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This would allow the database to adapt to where it is embedded: when targeting the browser the
                IndexedDB storage API would provide the persistence layer that the database requires, and similarly the
                POSIX filesystem storage API would provide the persistence layer when targeting POSIX systems (like
                desktops, mobile, &lt;em&gt;etc.&lt;/em&gt;).&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;But there’s also an extra restriction that comes from by being embedded: it needs to provide and
                embeddable artifact, most likely a binary library object that exposes a C compatible FFI, similar to
                &lt;a href=&quot;https://www.sqlite.org/amalgamation.html&quot;&gt;how SQLite does&lt;/a&gt;. Bundling a full runtime
                environment is possible, but doesn’t make it a compelling solution for embedding. This rules out most
                languages, and leaves us with C, Rust, Zig, and similar options that can target POSIX systems and
                WebAssembly.&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;immutable&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#immutable&quot;&gt;&lt;/a&gt;Immutable&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Being immutable means that only new information is added, no in-place update ever happens, and
                nothing is ever deleted.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Having an immutable database presents us with similar trade-offs found in persistent data
                structures, like lack of coordination when doing reads, caches being always coherent, and more usage of
                space.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;&lt;a href=&quot;https://www.datomic.com/&quot;&gt;&lt;strong&gt;Datomic&lt;/strong&gt;&lt;/a&gt; is the go to database example of
                this: it will only add information (datoms) and allows you to query them in a multitude of ways. Stuart
                Halloway calls it &quot;accumulate-only&quot; over &quot;append-only&quot;footnote:accumulate-only[ Video &quot;&lt;a href=
                &quot;https://vimeo.com/116315075&quot;&gt;Day of Datomic Part 2&lt;/a&gt;&quot; on Datomic’s information model, at time 12:28.
                ]:&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;quoteblock&quot;&gt;
                &lt;blockquote&gt;
                  &lt;div class=&quot;paragraph&quot;&gt;
                    &lt;p&gt;It’s accumulate-only, it is not append-only. So append-only, most people when they say that
                    they’re implying something physical about what happens.&lt;/p&gt;
                  &lt;/div&gt;
                &lt;/blockquote&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Also a database can be append-only and overwrite existing information with new information, by doing
                clean-ups of &quot;stale&quot; data. I prefer to adopt the &quot;accumulate-only&quot; naming and approach.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/a&gt; is another example of this: new commits are
                always added on top of the previous data, and it grows by adding commits instead of replacing existing
                ones.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Git repositories can only grow in size, and that is not only an acceptable condition, but also one
                of the reasons to use it.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;All this means that no in-place updates happens on data, and the database will be much more
                concerned about how compact and efficiently it stores data than how fast it does writes to disk. Being
                embedded, the storage limitation is either a) how much storage the device has or b) how much storage
                was designed for the application to consume. So even though the database could theoretically operate
                with hundreds of TBs, a browser page or mobile application wouldn’t have access to this amount of
                storage. SQLite even &lt;a href=&quot;https://sqlite.org/limits.html&quot;&gt;says&lt;/a&gt; that it does support
                approximately 280 TBs of data, but those limits are untested.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The upside of keeping everything is that you can have historical views of your data, which is very
                powerful. This also means that applications should turn this off when not relevantfootnote:no-history[
                Similar to &lt;a href=&quot;https://docs.datomic.com/cloud/best.html#nohistory-for-high-churn&quot;&gt;Datomic’s
                &lt;code&gt;:db/noHistory&lt;/code&gt;&lt;/a&gt;. ].&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;syncable&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#syncable&quot;&gt;&lt;/a&gt;Syncable&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This is a frequent topic when talking about offline-first solutions. When building applications
                that:&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;ulist&quot;&gt;
                &lt;ul&gt;
                  &lt;li&gt;
                    &lt;p&gt;can fully work offline,&lt;/p&gt;
                  &lt;/li&gt;
                  &lt;li&gt;
                    &lt;p&gt;stores data,&lt;/p&gt;
                  &lt;/li&gt;
                  &lt;li&gt;
                    &lt;p&gt;propagates that data to other application instances,&lt;/p&gt;
                  &lt;/li&gt;
                &lt;/ul&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;then you’ll need a conflict resolution strategy to handle all the situations where different
                application instances disagree. Those application instances could be a desktop and a browser version of
                the same application, or the same mobile app in different devices.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;A three-way merge seems to be the best approach, on top of which you could add application specific
                conflict resolution functions, like:&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;ulist&quot;&gt;
                &lt;ul&gt;
                  &lt;li&gt;
                    &lt;p&gt;pick the change with higher timestamp;&lt;/p&gt;
                  &lt;/li&gt;
                  &lt;li&gt;
                    &lt;p&gt;if one change is a delete, pick it;&lt;/p&gt;
                  &lt;/li&gt;
                  &lt;li&gt;
                    &lt;p&gt;present the diff on the screen and allow the user to merge them.&lt;/p&gt;
                  &lt;/li&gt;
                &lt;/ul&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Some databases try to make this &quot;easy&quot;, by choosing a strategy for you, but I’ve found that
                different applications require different conflict resolution strategies. Instead, the database should
                leave this up to the user to decide, and provide tools for them to do it.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Merge_(version_control)&quot;&gt;&lt;strong&gt;Three-way merges in version
                control&lt;/strong&gt;&lt;/a&gt; are the best example, performing automatic merges when possible and asking the
                user to resolve conflicts when they appear.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The unit of conflict for a version control system is a line of text. The database equivalent would
                probably be a single attribute, not a full entity or a full row.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Making all the conflict resolution logic be local should allow the database to have encrypted
                remotes similar to how &lt;a href=
                &quot;https://spwhitton.name/tech/code/git-remote-gcrypt/&quot;&gt;git-remote-gcrypt&lt;/a&gt; adds this functionality to
                Git. This would enable users to sync the application data across devices using an untrusted
                intermediary.&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;relational&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#relational&quot;&gt;&lt;/a&gt;Relational&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;I want the power of relational queries on the client applications.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Most of the arguments against traditional table-oriented relational databases are related to write
                performance, but those don’t apply here. The bottlenecks for client applications usually aren’t write
                throughput. Nobody is interested in differentiating between 1 MB/s or 10 MB/s when you’re limited to
                500 MB total.&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The relational model of the database could either be based on SQL and tables like in SQLite, or
                maybe &lt;a href=&quot;https://docs.datomic.com/on-prem/query.html&quot;&gt;datalog&lt;/a&gt; and &lt;a href=
                &quot;https://docs.datomic.com/cloud/whatis/data-model.html#datoms&quot;&gt;datoms&lt;/a&gt; like in Datomic.&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;from-aspects-to-values&quot;&gt;From aspects to values&lt;a class=&quot;anchor&quot;
             href=&quot;#from-aspects-to-values&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;Now let’s try to translate the aspects above into values, as suggested by Bryan Cantrill.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;portability&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#portability&quot;&gt;&lt;/a&gt;Portability&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Being able to target so many different platforms is a bold goal, and the embedded nature of the
                database demands portability to be a core value.&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;integrity&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#integrity&quot;&gt;&lt;/a&gt;Integrity&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;When the local database becomes the source of truth of the application, it must provide consistency
                guarantees that enables applications to rely on it.&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;sect2&quot;&gt;
              &lt;h3 id=&quot;expressiveness&quot;&gt;&lt;a class=&quot;anchor&quot;
                 href=&quot;#expressiveness&quot;&gt;&lt;/a&gt;Expressiveness&lt;/h3&gt;
              &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The database should empower applications to slice and dice the data in any way it wants to.&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;a class=&quot;anchor&quot;
             href=&quot;#next-steps&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;Since I can’t find any database that fits these requirements, I’ve finally come to terms with doing it
              myself.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;It’s probably going to take me a few years to do it, and making it portable between POSIX and
              IndexedDB will probably be the biggest challenge. I got myself a few books on databases to start.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;I wonder if I’ll ever be able to get this done.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect1&quot;&gt;
          &lt;h2 id=&quot;external-links&quot;&gt;External links&lt;a class=&quot;anchor&quot;
             href=&quot;#external-links&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
          &lt;div class=&quot;sectionbody&quot;&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
              &lt;p&gt;See discussions on &lt;a href=
              &quot;https://old.reddit.com/r/programming/comments/ijwz5b/the_database_i_wish_i_had/&quot;&gt;Reddit&lt;/a&gt;, &lt;a href=
              &quot;https://lobste.rs/s/m9vkg4/database_i_wish_i_had&quot;&gt;lobsters&lt;/a&gt;, &lt;a href=
              &quot;https://news.ycombinator.com/item?id=24337244&quot;&gt;HN&lt;/a&gt; and &lt;a href=
              &quot;https://lists.sr.ht/~euandreh/public-inbox/%3C010101744a592b75-1dce9281-f0b8-4226-9d50-fd2c7901fa72-000000%40us-west-2.amazonses.com%3E&quot;&gt;
              a lengthy email exchange&lt;/a&gt;.&lt;/p&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
    </content>
  </entry>
</feed>
