<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Claudiu Dascalescu</title>
    <description>The latest articles on DEV Community by Claudiu Dascalescu (@claudiud).</description>
    <link>https://dev.to/claudiud</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F697237%2F733e26db-344c-49ce-8174-b4144e1a258d.png</url>
      <title>DEV Community: Claudiu Dascalescu</title>
      <link>https://dev.to/claudiud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/claudiud"/>
    <language>en</language>
    <item>
      <title>Inside Xatastor: ZFS + NVMe-oF for millions of Postgres databases</title>
      <dc:creator>Claudiu Dascalescu</dc:creator>
      <pubDate>Sun, 03 May 2026 21:00:00 +0000</pubDate>
      <link>https://dev.to/xata/inside-xatastor-zfs-nvme-of-for-millions-of-postgres-databases-3h3p</link>
      <guid>https://dev.to/xata/inside-xatastor-zfs-nvme-of-for-millions-of-postgres-databases-3h3p</guid>
      <description>&lt;p&gt;At &lt;a href="https://xata.io" rel="noopener noreferrer"&gt;Xata&lt;/a&gt; we run a Postgres service offering fast database branching and scale-to-zero. It happens to be great for enabling coding agents to work with realistic data without needing direct access to the prod DB. This way LLM hallucinations can’t oops the prod database, like we’ve seen happen &lt;a href="https://x.com/lifeof_jer/status/2048103471019434248" rel="noopener noreferrer"&gt;more&lt;/a&gt; and &lt;a href="https://x.com/jasonlk/status/1946069562723897802" rel="noopener noreferrer"&gt;more&lt;/a&gt; &lt;a href="https://alexeyondata.substack.com/p/how-i-dropped-our-production-database" rel="noopener noreferrer"&gt;often&lt;/a&gt; lately.&lt;/p&gt;

&lt;p&gt;There is also a new generation of dev, cloud, and AI platforms that require a database-per-tenant architecture. They often offer free tiers or inexpensive plans for which the cost of “ephemeral” (scale-to-zero) databases are a critical consideration.&lt;/p&gt;

&lt;p&gt;This new level of demand put pressure on us to support very large numbers of databases and branches in the most cost efficient way possible. It is why, after having worked and operated several other distributed storage systems, we decided to create our own.&lt;/p&gt;

&lt;p&gt;The new storage system is called Xatastor, and is the secret sauce of the Xata Cloud platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our storage journey and learnings so far
&lt;/h2&gt;

&lt;p&gt;When we started working on what became the current iteration of &lt;a href="https://github.com/xataio/xata" rel="noopener noreferrer"&gt;Xata&lt;/a&gt;, we knew we wanted copy-on-write branching and to use vanilla PostgreSQL without modifications. These two requirements together &lt;a href="https://xata.io/blog/open-source-postgres-branching-copy-on-write" rel="noopener noreferrer"&gt;pretty much meant&lt;/a&gt; that we needed some sort of separation of storage from compute at the block device layer. In other words, some sort of distributed, software-defined, storage.&lt;/p&gt;

&lt;p&gt;We’ve evaluated a bunch of options in the space, both open source and commercial: Ceph, LongHorn, OpenEBS, SimplyBlock, Portworx, Lightbits, and more. We initially chose a commercial solution but eventually we wanted more control over the storage system so we switched to an open-source project: OpenEBS with Mayastor.&lt;/p&gt;

&lt;p&gt;Small side-note: even with the release of Xatastor, we continue to use OpenEBS / Mayastor in production and it is what we usually recommend for open source and on-prem installs. We found it reliable and easy to operate and I would generally recommend it for K8s native storage.&lt;/p&gt;

&lt;p&gt;What multiple of these projects have in common, is that they use &lt;strong&gt;SPDK&lt;/strong&gt; internally. SPDK is a development kit from Intel and it’s one of those rare pieces of software that you would describe with a single word: a &lt;em&gt;beast&lt;/em&gt;. Skim through their &lt;a href="https://spdk.io/doc/performance_reports.html" rel="noopener noreferrer"&gt;performance reports&lt;/a&gt; and see that it’s capable of transporting millions of IOPS over the network with latencies measured in micro-seconds. It is what enabled us to compete with local storage in the &lt;a href="https://xata.io/blog/reaction-to-the-planetscale-postgresql-benchmarks" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt; we did last year.&lt;/p&gt;

&lt;p&gt;To transport bytes over the network at those speeds, SPDK uses a protocol called NVMe-over-fabrics (&lt;strong&gt;NVMe-oF&lt;/strong&gt;). It is a protocol specifically designed to keep the advantages of NVMe (massive parallelism, command set matching SSDs) and export them over the network. It is quite the beast by itself. The SPDK provides its own user-space implementation of it, which enables these fancy storage solutions.&lt;/p&gt;

&lt;p&gt;While SPDK-based solution are impressive, we’ve learned that SPDK is optimized for very high performance on a relatively small number of busy volumes. A lot of its defaults and design decisions are oriented towards that use case. Unfortunately, they tend to have issues when dealing with a very large number of volumes. A few hundreds volumes per node is perfectly fine, but reaching thousands per node is already a challenge. This ultimately results in a costly system to operate.&lt;/p&gt;

&lt;p&gt;In short, SPDK-based solutions optimize for a small number of very busy volumes. Our main use case is the opposite: &lt;strong&gt;a very large number of mostly idle volumes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Xatastor - goals
&lt;/h2&gt;

&lt;p&gt;Given the learnings above, it became clear that if we were to scale to millions of databases and branches, we needed to work on a custom storage solution. With the knowledge we gained by operating the other storage engines, we had a good handle on the main tradeoffs, the failure modes, the cost implications, and the operational aspects.&lt;/p&gt;

&lt;p&gt;We set for ourselves the following goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highly scalable in terms of volumes per storage node. We were targeting over 100K volumes per storage node.&lt;/li&gt;
&lt;li&gt;An inactive volume (from a scaled to zero Postgres) should take no resources at all except for disk space.&lt;/li&gt;
&lt;li&gt;Copy-on-write snapshots and clones features. Thin provisioning so we can charge only for actual usage.&lt;/li&gt;
&lt;li&gt;Built on top of proven technologies with mature tooling. Simple architecture with the minimum amount of moving parts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmyybsqiraje9cf3lohy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmyybsqiraje9cf3lohy.png" alt="Xatastor - exposing ZFS zvols over NVMe-oF" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xatastor - exposing ZFS zvols over NVMe-oF&lt;/p&gt;

&lt;p&gt;In short, Xatastor is implemented like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ZFS pools of volumes (zvols) exist on the storage nodes.&lt;/li&gt;
&lt;li&gt;Our own user-space implementation of NVMe-oF exposes them over the network.&lt;/li&gt;
&lt;li&gt;Our Xatastor Kubernetes operator serves as the control plane.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s go through each of the key components.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZFS pools of volumes (zvols)
&lt;/h2&gt;

&lt;p&gt;Interestingly, we don’t actually use ZFS &lt;em&gt;as a filesystem&lt;/em&gt; , but rather only the bottom layer of zvols. A zvol exposes a ZFS dataset as a block device (e.g. &lt;code&gt;/dev/zvol/pool/name&lt;/code&gt;). Since we mount these over the network, the client is responsible for the filesystem and mounting. We happen to use XFS as the filesystem. This means that Postgres and the compute pods are completely unaware of the ZFS layer.&lt;/p&gt;

&lt;p&gt;Zvols offer a lot of the key functionality that we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snapshots and clones&lt;/li&gt;
&lt;li&gt;Thin provisioning&lt;/li&gt;
&lt;li&gt;Data integrity via checksums&lt;/li&gt;
&lt;li&gt;Compression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Equally important, ZFS is highly scalable when it comes to the number of volumes. A single node can store hundreds of thousands of volumes, snapshots, and clones.&lt;/p&gt;

&lt;p&gt;At high scale, a few operations need care. For example, &lt;code&gt;zfs list&lt;/code&gt; becomes slow with many datasets, and we need to run it (or its equivalent) on startup. Because we need the startup to be as fast as possible so we can do zero-downtime upgrades, we work around this by maintaining our own metadata store.&lt;/p&gt;

&lt;p&gt;ZFS also has very mature tooling for administration, monitoring, moving volumes between storage nodes, and so on. This simplifies our operations significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of the NVMe-oF protocol
&lt;/h2&gt;

&lt;p&gt;Since we decided not to use SPDK, we had to create our own user-space implementation of the NVMe-oF protocol. It’s perfectly compatible with the Linux client implementation, so nothing needs to change on the client side.&lt;/p&gt;

&lt;p&gt;From the implementation point of view, it is a Rust daemon using monoio (a thread per core) and an &lt;code&gt;io_uring&lt;/code&gt;-based async runtime. Each worker thread owns its own TCP listener via &lt;code&gt;SO_REUSEPORT&lt;/code&gt;, its own connection state machines, and its own buffer pool. So a connection's PDU parsing, NVMe command decode, ZFS read/write, and response framing all happen on one core with very little cross-thread synchronization on the hot path.&lt;/p&gt;

&lt;p&gt;The little coordination that we need between the admin and the IO queues is done completely mutex-free, in order to avoid adding latency spikes in the IO queues.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Xatastor operator
&lt;/h2&gt;

&lt;p&gt;For the control plane we wanted to be as Kubernetes native as possible. There's no external metadata store: every volume, its placement, its NVMe-oF connect parameters, and its lifecycle state live in a cluster-scoped CRD (&lt;code&gt;xvol&lt;/code&gt;) that you can kubectl get, watch, or describe like any other resource. Create, clone, snapshot, delete, and health monitoring all flow through standard Kubernetes APIs (PVCs, VolumeSnapshots, and the Xvol CR underneath).&lt;/p&gt;

&lt;p&gt;Following the Kubernetes standards makes Xatastor fit well among the OpenEBS storage engines. From the user point of view, it is a drop-in replacement for Mayastor, which makes it easy to switch from one to another, or run them in parallel.&lt;/p&gt;

&lt;p&gt;At the same time, having our CSI implementation allows us to implement custom workflows and a set of optimizations that enable very fast wake-up (scale to one) and branch times. But about that, we’ll have a future blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;The goal of Xatastor is to be a storage system that scales to a huge number of volumes with minimal cost. Our design didn’t optimize for single-volume benchmarks. We expected some performance penalties compared to SPDK-based solutions, because of ZFS overhead and the simpler NVMe-oF implementation.&lt;/p&gt;

&lt;p&gt;However, in practice, the simpler architecture and clean implementation offset the expected overhead.&lt;/p&gt;

&lt;p&gt;We’ll have a follow up blog post covering benchmark results, but in our tests so far Xatastor matches SPDK-based solutions on Postgres benchmarks, while requiring only a small fraction of the hardware resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redundancy
&lt;/h2&gt;

&lt;p&gt;So far in this blog post we haven’t written anything about redundancy. Mayastor, for example, allows storing multiple replicas of the same volume, with sync replication between them. The way this works is that the storage nodes run NVMe-oF proxies (called nexuses) to route writes between them. The nexus needs to wait for the acknowledgement from the replicas before acknowledging the write as successful. If a node is lost, a rebuild process needs to copy the data from a healthy replica to a new node.&lt;/p&gt;

&lt;p&gt;We decided to skip all of this for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our workload is Postgres, which has its own replication mechanism via read-replicas. Our system has automatic failover in case of primary failures. This is the most trustworthy HA solution, and the right level to solve redundancy. We highly recommend all production workloads to add at least one read-replica, for more reasons than just redundancy: with replicas updates and configuration changes are rolled out via switchover, which mean significant less downtime.&lt;/li&gt;
&lt;li&gt;On our cloud regions, we use EBS in AWS and Persistent Disks in GCP, which provide their own internal redundancy. For non-production instances, or for prod instances that scale-to-zero, this means you get redundancy even without read-replicas.&lt;/li&gt;
&lt;li&gt;Write performance and cost. Redundancy at the storage layer via sync writes doesn’t come for free. It introduces write amplification, basically doubling the cost. Also, often the network bandwidth is the main constraint that we deal with, and the replication means more traffic over the network.&lt;/li&gt;
&lt;li&gt;Simplicity. This is maybe the biggest reason right now. Having replicas would mean a significantly more complex system, which for now we don’t think is justified for our particular use case.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compression
&lt;/h2&gt;

&lt;p&gt;We have many customers storing non-prod databases with multiple terabyte of data. An unexpected benefit of ZFS is that we can enable compression on a per volume basis. We’re seeing zvol compression of 40-50% in some cases, which translate in significant cost savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Xatastor is a cloud native software-defined storage engine that enables the level of scalability that is required in the agentic era.&lt;/p&gt;

&lt;p&gt;If you are working on a web, cloud, or AI platform that would benefit from offering a Postgres instance (or more) per tenant, &lt;a href="https://xata.io/onboarding" rel="noopener noreferrer"&gt;don’t hesitate to reach out&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also: if you are an engineer that enjoys this type of system-level challenges, &lt;a href="https://xata.io/careers#open-positions" rel="noopener noreferrer"&gt;we are hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>rust</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Introducing Xata OSS: Postgres platform with branching, now Apache 2.0</title>
      <dc:creator>Claudiu Dascalescu</dc:creator>
      <pubDate>Wed, 22 Apr 2026 13:13:00 +0000</pubDate>
      <link>https://dev.to/xata/introducing-xata-oss-postgres-platform-with-branching-now-apache-20-52do</link>
      <guid>https://dev.to/xata/introducing-xata-oss-postgres-platform-with-branching-now-apache-20-52do</guid>
      <description>&lt;p&gt;Xata is a cloud-native Postgres platform with the following highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast branching&lt;/strong&gt; using Copy-on-Write at the storage level. You can “copy” TB of data in a matter of seconds. Except they are lightweight copies that happen instantly and take very little extra disk space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale-to-zero&lt;/strong&gt; functionality, so inactive databases don’t cost you compute. Together with the previous point, this makes Postgres branches almost free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% Vanilla Postgres&lt;/strong&gt;. We took a bit of a different path compared with Neon or Aurora: we run vanilla Postgres without any modifications. The branching functionality happens at the underlying storage layer (tech details below).&lt;/li&gt;
&lt;li&gt;All the &lt;strong&gt;production grade&lt;/strong&gt; requirements: high availability, read replicas, automatic failover/switchover, upgrades, backups with PITR, IP filtering, etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Xata’s customers use it in two major ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For creating preview and testing environments quickly and with real data.&lt;/li&gt;
&lt;li&gt;As a fully-managed PostgreSQL service to run the production database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first use case is becoming particularly more popular with the advent of coding agents. Producing the code is now fast and cheap, but validating it with realistic production workloads still takes a long time. Synthetic or seeded data just doesn’t cut it for finding performance issues and subtle corner-case data bugs. Copying production data with pg_dump is slow and risky. Keeping many dev instances up and running for a long time is very expensive.&lt;/p&gt;

&lt;p&gt;A solution with copy-on-write branching and scale-to-zero is the ideal solution for these types of issues.&lt;/p&gt;

&lt;p&gt;With the new open-source distribution of Xata, any engineer in any organization can deploy Xata and without concerns related to adding a new vendor or having to worry about licensing.&lt;/p&gt;

&lt;p&gt;For the second use case, organizations are now able to use Xata as their internal Postgres service. This is particularly useful if your organization needs a variety of instance sizes (e.g. a few large databases, many small ones, database per PR, etc.).&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy-on-write: a short intro
&lt;/h2&gt;

&lt;p&gt;To understand the Xata architecture, let’s start at the lowest layer and the go up.&lt;/p&gt;

&lt;p&gt;The idea of copy-on-write (CoW) is not new. It has been widely used in databases, file systems, and operating systems. It typically looks like something like this:&lt;/p&gt;

&lt;p&gt;Data is split into blocks and the location of each block is stored in a special metadata “index” block. Instead of accessing data directly, the program finds the location of the block in the index before accessing it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17os74y15p33h8xpx4kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17os74y15p33h8xpx4kz.png" alt="Copy-on-Write phase 1" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a copy is created, only the index block is copied initially. This new index block points to the same data blocks as the original. This is what makes the copy operation so fast, the index block is typically tiny compared to the full data size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh1qjwdezwy7ysztyg9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh1qjwdezwy7ysztyg9c.png" alt="Copy-on-Write phase 2" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As long as only reads are received by both copies, this arrangement works. As soon as either of the two copies receives write, the copy happens (hence the name copy-on-write). But instead of copying all of the data, only the block that received the write is copied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm87nd3fyot7grvk8rrfb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm87nd3fyot7grvk8rrfb.png" alt="Copy-on-Write phase 3" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the blocks are relatively small, the copy is almost invisible and induces no significant overhead. This is because, for example, Postgres only writes full pages, even if you only changed a single byte.&lt;/p&gt;

&lt;p&gt;If in the end all blocks are written to, all blocks will be eventually copied. However, in many practical cases, a lot of blocks are not written at all after the initial creation, which means that this scheme has the potential to save a lot of storage space.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key is in the storage system
&lt;/h2&gt;

&lt;p&gt;One thing to note is that the original and the copy need to be able to access the same data blocks. Otherwise, there’s no way to take advantage of CoW. The most simple way of achieving this is to just run both on the same server. For Postgres branching, you can do this with a CoW filesystem, like ZFS or btrfs, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu27ms4erlp57hnrhvil1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu27ms4erlp57hnrhvil1.png" alt=" " width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a good start, but it means that the parent and the child branches need to share the same CPU and RAM resources. For example, if the server has say 6 GB of RAM, and you need 2 GB for each Postgres instance, you can only run the parent and two other child branches.&lt;/p&gt;

&lt;p&gt;This leads us to the idea of mounting the storage over the network. This way we can break free of single-node limits:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi1q3w9nbi7x7bxud3z1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi1q3w9nbi7x7bxud3z1.png" alt=" " width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot can be written about the pros and cons of separating compute and storage for running databases. However, our primary use cases involve branching and scale-to-zero, and separating allows for a much more flexible compute layer.&lt;/p&gt;

&lt;p&gt;Freed up from the relatively static nature of bytes on disk, the compute layer can scale up and down with demand. We're efficiently bin-packing Postgres instances on the most optimal set of nodes.&lt;/p&gt;

&lt;p&gt;In practice, it might looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5g4kq8jngehrtr6rxcko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5g4kq8jngehrtr6rxcko.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many ways to mount storage over the network. From good old NFS, to sshfs or WebDAV. They each have their own ideal use cases. For running databases with high performance, NVMe over fabrics (NVMe-of) stands out.&lt;/p&gt;

&lt;p&gt;NVME-of has a stable implementation in the Linux kernel and is designed for high performance. It is capable of driving hundreds of thousands of IOPS over the network, which is exactly what Postgres needs.&lt;/p&gt;

&lt;p&gt;Because it was important to us to be 100% vanilla PostgreSQL, with no significant modifications, a storage engine exposed over NVMe-of was the clear choice for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the shoulder of giants
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitnmydidw0wbx4xfbyw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitnmydidw0wbx4xfbyw5.png" alt=" " width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking deeper into the technology behind this effort, Xata is built on top of two mature cloud-native open source projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/cloudnative-pg" rel="noopener noreferrer"&gt;CloudNativePG&lt;/a&gt; is a Postgres operator for Kubernetes. It handles most of the typical production concerns: high-availability, failover/switchover, upgrades, connection pooling, backups, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/openebs/openebs" rel="noopener noreferrer"&gt;OpenEBS&lt;/a&gt;, is a a cloud native storage project, offering both local storage (i.e. local NVMe disks) as well as a replicated storage engine (also called Mayastor).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Xata OSS project doesn’t just put these two technologies together, it builds a set of new components to work on top and along them.&lt;/p&gt;

&lt;p&gt;More precisely, Xata adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL gateway&lt;/strong&gt;, responsible for routing, IP filtering, waking up scaled-to-zero clusters, serving the serverless driver over HTTP / websockets, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch operator&lt;/strong&gt; managing all resources related to a branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clusters&lt;/strong&gt; and &lt;strong&gt;projects&lt;/strong&gt; services for the control-plane and REST APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth service&lt;/strong&gt;, based on Keycloack for API keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt; that makes use of the REST API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale-to-zero CNPG plugin&lt;/strong&gt; for automatically hibernating branches on inactivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these components are now open source in the &lt;a href="https://github.com/xataio/xata" rel="noopener noreferrer"&gt;Xata repository&lt;/a&gt;. We also remain committed to our popular and long standing OSS projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/xataio/pgstream" rel="noopener noreferrer"&gt;pgstream&lt;/a&gt; offers PostgreSQL replication with DDL statements and anonymization&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/xataio/pgroll" rel="noopener noreferrer"&gt;pgroll&lt;/a&gt; offers zero-downtime, undoable, schema migrations for PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When NOT to use Xata open source
&lt;/h2&gt;

&lt;p&gt;You might be wondering how will we be making money if we just open sourced our platform. First, we should say that we didn’t open source quite everything. We have kept some components in our private repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code for deploying and securing multi-organization, multi-region, and multi-cell installations.&lt;/li&gt;
&lt;li&gt;our own “Xatastor” storage engine. It similar to OpenEBS/Maystor but we developed it for the exact needs of the agentic workloads. We’ll be talking more about it in a future blog post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to technical features, it’s a matter of convenience: Even with a platform like Xata, operating a large fleet of Postgres clusters can be a challenge. Your time might be better utilized on your core business offerings.&lt;/p&gt;

&lt;p&gt;This is why there are some cases were we actually recommend against using Xata open-source, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You only need a single Postgres instance. Xata is running on top of a Kubernetes cluster and it would be overkill for a single instance. You can either self-host Postgres on your own, or use a managed Postgres service, like &lt;a href="https://xata.io/" rel="noopener noreferrer"&gt;Xata Cloud&lt;/a&gt; (coming soon).&lt;/li&gt;
&lt;li&gt;Offering a public Postgres-as-a-Service to offer your own users and customers. While the Apache 2 license allows this use case (nor do we have any intention to change it in the future), we don’t recommend using it like this because the OSS version does lack some security features related to adversarial multi-tenancy. If you are looking to offer PGaaS to your users and customers, likely our BYOC option is what you need. &lt;a href="https://xata.io/onboarding" rel="noopener noreferrer"&gt;Book some time with us&lt;/a&gt; to discuss it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Starting now, the core Xata platform is open-source under the Apache 2 license. If you are looking to deploy it inside your organization, we’d love to help, so don’t hesitate to reach out via GitHub issues or email.&lt;/p&gt;

&lt;p&gt;Also, this is only the beginning of what's new at Xata. We have some exciting announcements happening over the next few weeks to share all of the important Postgres offerings we've been working on this year. Keep up with the announcements on &lt;a href="https://x.com/xata" rel="noopener noreferrer"&gt;X/Twitter&lt;/a&gt;, &lt;a href="https://bsky.app/profile/xata.io" rel="noopener noreferrer"&gt;BlueSky&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/company/xataio/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>opensource</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>Karpathy's LLM wiki pattern is missing a data layer. Here's how to add one.</title>
      <dc:creator>Claudiu Dascalescu</dc:creator>
      <pubDate>Thu, 16 Apr 2026 09:57:12 +0000</pubDate>
      <link>https://dev.to/claudiud/karpathys-llm-wiki-pattern-is-missing-a-data-layer-heres-how-to-add-one-5bp6</link>
      <guid>https://dev.to/claudiud/karpathys-llm-wiki-pattern-is-missing-a-data-layer-heres-how-to-add-one-5bp6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on the Xata blog. Cross-posting here for the dev.to Postgres/AI crowd.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're running product, go-to-market, or operations, your work lives in two places. Traditionally, you’re comfortable writing things like strategy docs, competitive intel, research notes, and campaign briefs. But the decisions you make (hopefully) depend on data: activation rates by cohort, pipeline conversion, revenue trends, campaign ROI. Your context exists in writing, your decision relies on data. This is nothing new, but how well and how fast we can run these cycles has completely changed with AI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f" rel="noopener noreferrer"&gt;Andrej Karpathy recently shared a pattern&lt;/a&gt; for using LLMs to build personal knowledge bases, and it resonated with a LOT of people - 18M views, and what feels like an equal number of derivative pieces, as of this writing. If your work is pure research and idea generation, it's all you need. Markdown is the right container for concepts, connections, and synthesis.&lt;/p&gt;

&lt;p&gt;But if you need to make a quarterly planning decision, a wiki page will only get you half way there. You need activation rates, not a summary of what activation means. You need pipeline numbers, not a positioning doc. Karpathy's pattern only handles the first half. The missing piece is a database alongside your wiki, with the LLM maintaining both and the references between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Karpathy proposed
&lt;/h2&gt;

&lt;p&gt;You rarely touch the wiki directly. The LLM writes it, maintains it, and keeps it consistent. You handle the thinking, it handles the work.&lt;/p&gt;

&lt;p&gt;His first step is to index source documents (articles, papers, repos, datasets, images) into a &lt;code&gt;raw/&lt;/code&gt; directory. The LLM then incrementally "compiles" a wiki from those sources: summaries, concept pages, backlinks, entity articles, all cross-linked. The wiki lives as &lt;code&gt;.md&lt;/code&gt; files, ready to view in your editor of choice - his preference is Obsidian. But remember, in this case Obsidian is a viewer, not an editor. The LLM is the editor.&lt;/p&gt;

&lt;p&gt;Once the wiki reaches meaningful size (Karpathy's is around 100 articles and 400K words on some topics), you can ask the LLM complex research questions against it. The LLM reads the index files and summaries, pulls in the relevant articles, and synthesizes answers. At this scale, you don't need RAG infrastructure. The LLM's own index maintenance and the ability to read files on demand is enough.&lt;/p&gt;

&lt;p&gt;Most importantly, outputs get filed back into the wiki. Your explorations and queries accumulate in the knowledge base, making it richer for future queries. The wiki basically gets stronger with exercise (novel concept), not just from ingesting new sources. Periodic linting keeps the wiki improving over time. As the wiki grows, you build lightweight tools around it. Karpathy vibe-coded a small search engine over the wiki that he uses directly through a web UI and hands off to the LLM as a CLI tool for larger queries.&lt;/p&gt;

&lt;p&gt;Karpathy's closing observation: "I think there is room here for an incredible new product instead of a hacky collection of scripts." He's right - and several new products have already been shared. The challenge with them is that they’re all making decisions based on experience and professional principles, not data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two compilers, not one
&lt;/h2&gt;

&lt;p&gt;I spent an embarrassing amount of time trying to make markdown tables work for tracking weekly metrics before admitting what should have been obvious from the start: knowledge and data behave differently. A concept page in your wiki gets rewritten and refined over time. The LLM improves it as it learns more. But last week's signup numbers don't get rewritten. They're a fact. Next week adds another row. The wiki pattern is built for knowledge that evolves through revision. Your data evolves through accumulation. Markdown doesn't have a natural model for "append a row to an existing dataset." Databases do. That's literally what they are.&lt;/p&gt;

&lt;p&gt;Karpathy built a knowledge compiler. Raw text goes in, structured wiki comes out. What's missing is a data compiler that sits alongside it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw Sources
|
+-- Text sources (articles, papers, docs, transcripts)
|   |
|   +-&amp;gt; LLM compiles -&amp;gt; Wiki (.md files)
|       Concepts, summaries, backlinks, weekly reports
|
+-- Data sources (APIs, CSVs, event streams, logs)
    |
    +-&amp;gt; LLM compiles -&amp;gt; Database (Postgres)
        Schemas, tables, derived views, saved queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two layers serve different purposes and need different operations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Wiki (knowledge)&lt;/th&gt;
&lt;th&gt;Database (data)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shape&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Paragraphs, concepts, relationships&lt;/td&gt;
&lt;td&gt;Rows, columns, time-series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core operation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Summarize, link, synthesize&lt;/td&gt;
&lt;td&gt;Query, aggregate, compare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM role&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Write and maintain text&lt;/td&gt;
&lt;td&gt;Define schemas, write queries, maintain derived tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Update pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rewrite sections, add pages&lt;/td&gt;
&lt;td&gt;Insert rows, run migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scales by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;More pages&lt;/td&gt;
&lt;td&gt;More rows and tables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The interesting part: &lt;strong&gt;the two layers reference each other constantly&lt;/strong&gt;. A weekly analytics report (wiki) cites a SQL query that produced the numbers (database). A database table of experiment results gets interpreted in a wiki page that explains what the numbers mean and what to do about them. The LLM maintains both layers and the references between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop: database to markdown and back
&lt;/h2&gt;

&lt;p&gt;The questions you most want answered span both layers. "Why did activation drop?" is not a data question or a knowledge question. It's both. You need a database to compute the drop, and a wiki to explain it.&lt;/p&gt;

&lt;p&gt;Here's what this looks like if you run a weekly product analytics review. Say you're tracking signups, activation, branch creation, and revenue for a SaaS product.&lt;/p&gt;

&lt;p&gt;It starts with the database. The LLM runs SQL against Postgres to pull the raw numbers. These are relational queries that markdown can't answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;first_project&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_project_at&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;first_project&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;first_project_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-23'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;first_project_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-30'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revenue joins across billing tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount_micros&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;invoice_line_items&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'issued'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-23'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-30'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the LLM takes the query results, computes trends (week-over-week changes, cohort comparisons), and writes a report that combines the numbers with qualitative context: what you shipped that week, what might explain a spike or drop, what to watch next. That report is a markdown file that goes into your knowledge base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Week 14&lt;/span&gt;

&lt;span class="gu"&gt;## Product Metrics&lt;/span&gt;
&lt;span class="gs"&gt;**replaced with dummy data**&lt;/span&gt;

&lt;span class="gs"&gt;**New orgs that created branches:**&lt;/span&gt; 57 orgs (38.8% of new orgs)
Activation rate dropped from 79.5% last week. Worth investigating
whether the signup flow or onboarding changed.

&lt;span class="gs"&gt;**Total branches created:**&lt;/span&gt; 342 (-2.3% WoW)
Branch volume held roughly flat despite fewer activating orgs,
meaning the orgs that did activate created more branches per org.

&lt;span class="gu"&gt;## Key Insights&lt;/span&gt;

The headline this week is the activation rate drop to 38.8%,
down from 79.5%. Org creation held up (147, +11%) and
visit-to-org conversion reached a year high at 3.28%,
so top of funnel is working. The gap is between signup
and first branch.

&lt;span class="gu"&gt;## Execution This Week&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; launch plan and drumbeat campaign
&lt;span class="p"&gt;-&lt;/span&gt; push updated campaigns to staging
&lt;span class="p"&gt;-&lt;/span&gt; blog post refresh and traffic analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the two layers start compounding. The report says activation dropped from 79.5% to 38.8%. That raises a question: why? You ask the LLM. It goes back to the database and queries activation by signup source to check if a specific channel brought in low-intent users. It searches the wiki for last week's report and finds that the previous cohort included a batch of internal test accounts that inflated the baseline. It checks the execution notes and sees the onboarding flow wasn't changed.&lt;/p&gt;

&lt;p&gt;You get an answer that combines fresh data ("activation by channel shows organic signups activated at 52%, while Product Hunt signups activated at 12%") with knowledge from the wiki ("W13 baseline was inflated by internal testing, see weekly report W13. Onboarding flow unchanged.") Neither layer alone could have given you that answer.&lt;/p&gt;

&lt;p&gt;And now that answer gets filed back into your knowledge base too. Next week, when the LLM writes the W15 report, it has the full context chain: the drop in W14, the investigation, the root cause. Your knowledge base gets smarter every cycle, without you doing any of the bookkeeping.&lt;/p&gt;

&lt;p&gt;This is the loop: &lt;strong&gt;database produces the numbers, wiki captures the interpretation, follow-up questions drive deeper into both, and everything accumulates.&lt;/strong&gt; Each weekly report isn't just a snapshot. It's another layer of context that makes your next analysis better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together: structure and downstream decisions
&lt;/h2&gt;

&lt;p&gt;The product analytics example above shows the loop. But this pattern works for any domain where you mix text with numbers. Here's what the directory structure looks like when you set up a knowledge base with both layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;knowledge-base/
|
+-- raw/                          # immutable sources
|   +-- api-exports/              # raw data pulls (PostHog, billing)
|   +-- articles/                 # clipped web articles
|   +-- notes/                    # meeting notes, call transcripts
|
+-- wiki/                         # LLM-maintained markdown
|   +-- index.md                  # catalog of all pages
|   +-- weekly/                   # weekly reports with insights
|   |   +-- 2026-w14.md
|   |   +-- 2026-w13.md
|   +-- concepts/                 # evergreen concept pages
|   |   +-- activation-rate.md
|   |   +-- cohort-quality.md
|   +-- log.md                    # chronological ingest log
|
+-- data/                         # structured data layer
|   +-- schema.sql                # LLM-maintained schema
|   +-- migrations/               # schema evolution
|   +-- queries/                  # saved queries the LLM wrote
|   |   +-- new-orgs-by-week.sql
|   |   +-- activation-rate.sql
|   |   +-- revenue-by-week.sql
|
+-- outputs/                      # rendered artifacts
|   +-- charts/                   # matplotlib/observable plots
|   +-- reports/                  # formatted reports
|
+-- schema.md                     # instructions for both compilers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;schema.md&lt;/code&gt; file (what Karpathy calls the "schema layer") now has instructions for both compilers. It covers how to ingest text into the wiki and data into the database, what page structure the wiki follows and what table schemas the database uses.&lt;/p&gt;

&lt;p&gt;Your data layer is a Postgres database that mirrors whatever data sources you're working with - product analytics, campaign performance, pipeline stages, billing, customer data. We wrote about &lt;a href="https://xata.io/blog/postgres-data-warehouse" rel="noopener noreferrer"&gt;how to build a product analytics warehouse in Postgres&lt;/a&gt; using materialized views and pg_cron, and that's exactly the kind of setup that works well as the data layer here. The LLM queries it directly via SQL whenever it needs to produce a report, answer a question, or validate a claim in a strategy doc.&lt;/p&gt;

&lt;p&gt;The database is where the numbers live. The wiki is where the context lives. And because everything ends up in the same knowledge base, the LLM can move between the two without you having to point it at the right source every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the LLM useful with your data
&lt;/h3&gt;

&lt;p&gt;Getting the LLM to work with your wiki is trivial since it reads and writes markdown. The database side takes a bit more setup, but it comes down to three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Give it a schema&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any ORM schema or plain &lt;code&gt;schema.sql&lt;/code&gt; works, since Xata is vanilla Postgres. &lt;a href="https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases-typescript-postgresql" rel="noopener noreferrer"&gt;Prisma's PostgreSQL quickstart&lt;/a&gt; is a good starting point if you want typed queries.&lt;/p&gt;

&lt;p&gt;The key is describing what tables exist, what columns they have, and how they relate. Without it, every query starts with the LLM exploring your database blind.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Save your queries&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first time you ask a question, the LLM figures out the schema, writes the query, runs it, maybe adjusts it. That costs tokens and time. The second time, it will just run &lt;code&gt;activation-rate.sql&lt;/code&gt;. Saved queries are the data equivalent of wiki pages: compiled knowledge about how to get a specific answer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Write a skill file&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The schema says &lt;code&gt;status String&lt;/code&gt;. It doesn't say "a branch with status 'active' might still be hibernating." A skill file teaches the LLM what your columns actually mean, which metrics matter, and how to interpret what it finds. This is the highest-leverage thing you can do for output quality. You stop getting generic SQL output and start getting answers that reflect how &lt;em&gt;your&lt;/em&gt; &lt;em&gt;team&lt;/em&gt; thinks about &lt;em&gt;your&lt;/em&gt; &lt;em&gt;business&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Picking a database for this
&lt;/h3&gt;

&lt;p&gt;⚡ TLDR: we’re obviously going to suggest using a Xata Postgres database, but you do you.&lt;/p&gt;

&lt;p&gt;Signup at &lt;a href="http://console.xata.io/" rel="noopener noreferrer"&gt;console.xata.io&lt;/a&gt;, then:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -fsSL &amp;lt;https://xata.io/install.sh&amp;gt; | bash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;xata auth login&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;xata init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A knowledge base database has a specific usage pattern: you query it intensely for an hour while writing a weekly report, then don't touch it for days. Look for two things in your Postgres setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;scale-to-zero&lt;/strong&gt;: don’t pay for compute while the database sits idle between sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;branching&lt;/strong&gt;: so you can test hypotheses without risking your master knowledge base&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, your LLM is effectively &lt;a href="https://xata.io/blog/every-ai-agent-needs-a-database-it-can-break" rel="noopener noreferrer"&gt;an agent that needs a database it can break&lt;/a&gt;. Let it experiment freely on a copy.&lt;/p&gt;

&lt;p&gt;This pattern works at personal or small-team scale: a few data sources, a few hundred thousand rows, one or two people asking questions. &lt;a href="https://xata.io/blog/is-postgres-really-enough-in-2026" rel="noopener noreferrer"&gt;Postgres handles more than most people think&lt;/a&gt; before you need to reach for something else. If you need Spark here, I’d hate to see your Claude bill.&lt;/p&gt;

&lt;p&gt;If you want to add a data layer to your own knowledge base, start with one data source. Pick the data you're currently cramming into markdown (product events, billing exports, analytics CSVs), put it in Postgres, write a skill file that explains what the columns mean, and run a few cross-layer queries. Save the queries that work. The pattern compounds from there, just like Karpathy's wiki does.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
