I did not start with the intention of building a database.
I was simply looking for one that fit Kitwork.
The question that kept bothering me was simple:
Why should a small website need a large and complex database system? And how could I manage thousands of independent websites without constantly worrying about the infrastructure behind them?
That question is one of the reasons Kitwork exists.
Kitwork is a complete rewrite of Kitstack, a system I first began building about five years ago. Kitstack was an important part of my journey, but as it grew, I gradually realized that its original architecture no longer matched what I truly wanted to create.
I was not trying to build just another multi-tenant SaaS application.
I wanted Kitwork to run thousands of websites as thousands of small, independent systems. Each website should be able to have its own data structure, lifecycle, history, and direction while still running inside infrastructure simple enough for one person to manage.
That was where my search for the right database began.
Five Years Ago, I Used tenant_id
In the first version of Kitstack, I followed a common approach: all customer data lived in one database, and each record was separated by a tenant_id column.
Every website shared the same posts, pages, products, and other tables. Each row was simply associated with a particular tenant.
This was not necessarily a bad design.
At the beginning, it was simple, familiar, and productive. When the system contained only a limited number of websites with mostly similar structures, the model worked well.
But as the number and variety of websites increased, its limitations became more visible.
One website needed a new type of data. Another needed a different table structure. Some websites were simple landing pages, while others gradually added products, articles, orders, analytics, tracking, or entirely different workflows.
Yet all of them remained tied to the same shared schema.
A change made for one website was no longer isolated to that website. It could affect the entire system.
Backing up a single tenant became more complicated. Moving one website to another server was no longer as simple as moving its data file. Completely removing a customer’s data required deleting records across multiple tables, with the persistent risk of leaving something behind.
More importantly, I realized that the tenant_id model did not reflect how I thought about Kitwork.
To me, each website was not merely a collection of rows inside a large shared database.
Each website was its own system.
It should be able to have its own database, schema, history, backups, and recovery process. It should be possible to create, move, snapshot, restore, or remove a website without affecting any of the others.
When I decided to rewrite Kitstack as Kitwork, I knew I did not want to carry the old architecture forward.
PostgreSQL Was Still Excellent, but I Needed a Different Direction
My first option was still PostgreSQL.
I considered two approaches:
A separate schema for each tenant.
A separate PostgreSQL database for each tenant.
Both are valid architectures.
For a system with a relatively small number of customers—especially larger business customers with predictable workloads—PostgreSQL can handle these models very well.
But Kitwork was heading in a different direction.
I already had many websites running, and I wanted the architecture to eventually support thousands of smaller sites. Most of them did not need a large database server. Some contained only a few pages. Some received little traffic. Some existed temporarily and might later be archived, replaced, or deleted.
I did not want every small website to bring another layer of infrastructure complexity.
Even if all tenant databases lived inside the same PostgreSQL cluster, I would still need to think about connections, pools, migrations, permissions, backups, monitoring, and the lifecycle of thousands of databases or schemas.
I began asking a different question:
Could every website own a small database of its own while all of those databases were still managed efficiently inside one runtime?
Around that time, I started paying much more attention to SQLite.
The Return of SQLite
For a long time, SQLite was often treated as a database for mobile applications, desktop software, testing, or small side projects.
But over the past few years, I have seen more developers use SQLite in real production web applications.
That encouraged me to take it more seriously.
SQLite had one characteristic that matched my vision for Kitwork particularly well: a database could simply be a file.
If every tenant had its own database file, many operational problems could become much more natural.
Backing up a website could mean snapshotting or copying that website’s database. Moving a tenant could mean transferring its database and configuration to another node. Removing a tenant would no longer require a long series of DELETE ... WHERE tenant_id operations. It could instead mean closing and removing the database that belonged to that tenant.
The schema would no longer need to be identical across every website.
Each website could evolve according to its own needs without forcing the rest of the system to change with it.
That was also when I discovered Turso and the libSQL ecosystem.
Discovering Turso
For many people, Turso may be just another database option among many others.
For me, however, discovering Turso felt different. It seemed as though they were exploring several of the same principles I had been thinking about.
Small databases. Large numbers of tenants. Remote access. Lightweight deployment. A model in which databases could be created and managed independently.
I began experimenting with it.
I self-hosted the Turso/libSQL stack on my own cloud infrastructure, connected Kitwork to it through the Hrana protocol, and tested a database-per-tenant architecture.
The initial results were very good.
I could give each website its own database without running a heavy standalone database server for every tenant. The model was closer to what I had been searching for than anything I had previously tried.
Learning about Turso was fortunate for me.
It did not only give me another tool to use. More importantly, it showed me that the idea of one small database per tenant was not unreasonable.
But as I integrated it more deeply into Kitwork, I began to encounter another limitation.
The Problem Was Not Rust
Kitwork is written in Go.
The database and full-text search components I was interested in within the Turso ecosystem were moving in the direction of Rust. The search implementation relied on Tantivy, a powerful and highly optimized search engine.
I have nothing against Rust.
Rust is an excellent language, and Tantivy is clearly far more mature than a small search engine I could write myself.
The question was not whether Rust or Go was better.
The question was architectural.
Kitwork is being designed as a unified runtime. I want to be able to observe and control how its internal components use resources.
When a native library is introduced into a Go application through CGo or FFI, a new boundary appears.
On one side, there is the Go runtime, its garbage collector, goroutines, scheduler, and profiling tools. On the other side, there is a native library with its own memory model, execution behavior, and resource lifecycle.
I had to think about ownership across the boundary, allocations outside the Go heap, buffer copying, database handle lifetimes, file descriptors, native threads, and what would happen when hundreds or thousands of tenant databases were accessed within a short period of time.
These are not impossible problems.
Many successful systems combine Go, Rust, C, and other languages.
But for a project whose original goal was to simplify operations, I felt that I was introducing another layer that I could not observe as clearly as I wanted.
I also tried writing part of the full-text search system in pure Go.
It worked.
But I had to be honest with myself: my implementation could not yet compare with Tantivy in maturity, performance, or the years of optimization already invested in it.
That left me with two choices.
The first was to continue using mature external components, accept a multi-runtime architecture, and learn to operate it well.
The second was to build something smaller and more limited, but designed specifically around Kitwork’s workload and operational model.
I chose the second path.
I Decided to Build Only What Kitwork Actually Needed
The decision to build a database sounds enormous.
But I did not begin with the goal of creating a database that could replace PostgreSQL, SQLite, or Turso.
I did not believe that a new database written by one person could immediately outperform systems developed by hundreds of engineers over many years.
What I wanted to build was much smaller and more specific.
I needed a database for Kitwork.
A database that could exist naturally inside a Go runtime. A single process should be able to manage many small databases. Each tenant should have its own data and lifecycle. Opening, closing, caching, snapshotting, backing up, and monitoring those databases should all be controlled by the same system.
I did not need every possible feature on the first day.
I only needed to begin with what Kitwork truly used.
That was how KitDB started.
Not because I believed I could immediately build something better than every existing database.
But because, after years of working on Kitwork, I had finally begun to understand what its database needed to look like.
Learning While Building
I did not come from the world of database internals.
Before starting this project, I had not spent years researching B-trees, WALs, MVCC, query optimizers, or storage engines.
I started from the practical problems I had encountered while building a multi-tenant system.
I understood the experience I wanted.
I understood what made the previous architecture difficult to manage.
I understood the degree of tenant isolation Kitwork needed, how backups should work, and how resources should be controlled.
I researched the parts I did not understand while building them.
I studied how SQLite organized its database files and transactions. I explored how other systems handled write-ahead logs, pages, checksums, recovery, and concurrency. I experimented, benchmarked, discarded designs, and rebuilt parts when I discovered that my assumptions were wrong.
I did not begin with a perfect architecture.
KitDB emerged from a sequence of smaller questions.
How should a record be stored?
How could the engine detect an incomplete write?
What should happen if the process crashed in the middle of a commit?
How should the database recover after a power failure?
How could the runtime manage many databases without keeping too many file descriptors open?
How could one tenant run a heavy workload without significantly affecting the others?
Could the database’s change history also feed search indexes and analytics?
I moved forward one question at a time.
I learned as I built.
AI Helped Me Begin a Journey That Once Felt Much Harder to Start
Five years ago, I probably would not have seriously considered building a database.
Not because it was entirely impossible, but because the amount of knowledge required was overwhelming for one person who was also building the product, runtime, and infrastructure around it.
AI changed that.
I use AI as a research and programming partner.
I bring the real-world problems, the constraints of Kitwork, my previous experience, and the behavior I want from the system. AI helps me explore design alternatives, understand concepts I have not encountered before, compare how existing databases solve similar problems, identify edge cases, and build experiments more quickly.
But I do not believe AI makes database development easy.
AI can suggest a design that appears convincing while still failing in a specific crash scenario. It can produce code that works under normal conditions but violates an invariant under concurrency. It can generate impressive benchmark results that say very little about real durability.
AI does not remove the need to understand the system.
It helps me reach the difficult questions sooner.
The rest still depends on testing, benchmarking, fault injection, fuzzing, code review, and continuously questioning the design.
I am not handing KitDB to AI and waiting for it to build a database by itself.
I see AI as part of a development loop:
My experience identifies the problem. AI helps expand the set of possible solutions. Code, tests, and evidence determine which solution is actually correct.
That is how KitDB is being built.
KitDB Does Not Need to Be a Database for Everyone
One of the easiest mistakes when building a new tool is trying to make it solve every possible problem.
KitDB does not need to do that.
It does not need to replace PostgreSQL in systems that require a mature relational database with a vast ecosystem.
It does not need to replace SQLite in every embedded application.
It does not need to become Turso, FoundationDB, or CockroachDB.
KitDB only needs to solve Kitwork’s problem well:
How can a Go runtime operate a large number of independent websites, each with its own small database, while keeping the entire system simple enough for one person to understand, deploy, and manage?
If KitDB eventually becomes useful to other people facing the same problem, that would be meaningful to me.
But even if it only serves Kitwork, the journey is still worthwhile.
The database is no longer merely an external component selected separately and attached to the system.
It is becoming part of how Kitwork understands tenants, data, history, search, analytics, backups, and recovery.
I Am Still Searching for the Right Database for Kitwork
Saying that I am “building my own database” can make the decision sound impulsive.
In reality, I am still continuing the same search for a database that fits Kitwork.
The difference is that instead of waiting to find an existing database that matches every requirement, I have started building the missing pieces myself.
Turso showed me the value of a database-per-tenant model.
SQLite showed me the power of a small, embedded engine contained within a single file.
PostgreSQL showed me the value of a mature, disciplined, and reliable data system.
Tantivy showed me the distance between a search engine that merely works and one that has been seriously optimized.
Kitwork showed me what I actually needed.
KitDB was born at the intersection of those lessons.
I do not know how far it will go.
I also cannot say that every decision I am making today will prove to be correct.
I may need to change the file format, rebuild the storage layer, abandon ideas that initially seemed promising, or return to an external component when writing it myself no longer makes sense.
I accept that.
My goal is not to prove that I can rewrite everything.
My goal is to build a system I can understand, control, and continue developing for many years.
I never planned to build a database.
I only wanted Kitwork to manage thousands of websites more simply.
But perhaps new tools are sometimes born from needs that specific.
Not because the world lacks databases.
But because after living with a problem for long enough, you finally understand it well enough to begin building the exact tool you need.
