I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS
kentcdodds.com
kentcdodds.com
Now, I don’t know if every server-side app NEEDS to run in 10 regions, auto-refresh session cookies, notify when someone starts typing a comment, report metrics every time the user moves the mouse pointer (or whatever else the kids do for fun these days). But let’s assume for a moment that's the common case.
My understanding is that these relational dbs require a lot of config, awareness of the difficult distributed issues, and other layers such as caching, batching and so on, all of which is incredibly error-prone.
In fact, the most important parts of the ground laws of data manipulation isn’t even part of a schema! I could worry less about enums and nulls, in a world with orders of magnitude more dangerous creatures: losing writes because I wrote to a replica, reading or writing inconsistent state, duplicate writes etc etc. It feels like we have a lot of structure around the easy stuff, but no structure around the hard parts.
As a mere a dabbler with databases, can someone help calm my nerves?
> I could worry less about enums and nulls, in a world with orders of magnitude more dangerous creatures: losing writes because I wrote to a replica, reading or writing inconsistent state, duplicate writes etc etc
LiteFS uses a primary/replica setup (as do many distributed databases) where the primary can perform writes and replicas are read-only. You won't be able to lose writes written to a replica because LiteFS doesn't allow that. As for inconsistent state, there is a transaction ID that lets you track your replication position[1]. You can implement strict serializability across your cluster using that. Then for duplicate writes, all writes go to the primary so it has the same serializable guarantees as regular SQLite.
I hope that explanation helps. Let me know if you have any other questions.
While I have you, do you have any high level thoughts on reactivity within a db like sqlite? (My app needed some real-time features so I have mostly been looking at NATS.io plus websockets). Are there any real-time like features (reactivity, subscriptions, CDC, materialized views) that work well and ergonomically within that context, either today or in the future? If so it could be really, really useful for my app.
Thanks again!
It definitely gets more complicated when you have a distributed application. Using something like NATS works but you'd need to synchronize between the NATS message and your database state on your replicas (e.g. NATS could arrive before data is propagated to replicas).
We do have plans to add event data to the LiteFS transaction files[1]. That would let you write out a message like "user_updated id=123" during your transaction and it would get bundled with the transaction payload on commit. Then your application on your replica can listen for events and they are already synced up with your database state.
I also recommend checking out Replicache[1] and alternatives, which may be a better way to handle the networking and database replication so that it doesn't rely on the underlying DB.
[0] https://github.com/WiseLibs/better-sqlite3/blob/HEAD/docs/ap... [1] https://replicache.dev/
But you could use triggers to write to a table (as any process that writes will run that trigger), and then poll that table from another process.
Webassembly is a compile target for SQLite, AFAIR, so you might use that in the client to get the fastest possible response without using too much other libraries/services.
But without more information, it is hard to help you.
Yeah I know. Sorry. Think chat application latency. I think I should have just used event-driven or reactive instead.
Enforcing a single writable nodes / primary can go a long way. Of course you've got a lot more options if your data naturally partitions, with few or no relationships among them.
Just use one of the cloud or SaaS database options which are battle tested, have well-defined guarantees and 24/7 support. Running your own database seems to be a fool's errand if data loss or corruption worries you at all.
- So LiteFS turns sqlite into a client server database. But SQLite being embedded is what gives it its low latency in the first place. So, I kind of have to ask - what's point? Just prefering the way sqlite does SQL over postgres?
- What language is that database model under "Evaluating the existing data"?
SQLite can do things PostgreSQL can't. Like the "insert or replace" is superior to PostgreSQL upsert for maintaining seed data. And the flexible typing is great for a settings table where you can just have key/value columns and store numbers/text/bool/dates/etc... in the value column. Also, if you want to do a graph style query then SQLite is better then PostgreSQL.
The way I see it, both PostgreSQL and SQLite have their strengths and LiteFS expands the areas where SQLite can be used.
How is SQLite's replace and typing is superior to these?
If you want to store lot's of json data then yes, JSONB is the better option.
As for the MERGE and INSERT ON CONFLICT vs INSERT OR REPLACE, they all work differently, they are not the same. The SQLITE option is simpler and better if you just want to ensure a full row is there in a specific state. If you want to preserve certain columns then the PostgreSQL options are better.
Edit: I do wish that PostgreSQL had the SQLite options and that SQLite had the PostgreSQL options, because they are good for different things and it would be nice to have access to all of it regardless of which DB I am working with (I use both in different apps).
I find duck typing in a DB more of a problem than a solution, unless the dataset is so small and variable I don't even need a RDBMS at all.
And I guess the second one is covered by the "generator" entry, but:
https://www.prisma.io/docs/concepts/components/prisma-client
I don't feel it did.
I think it's cool but it sucks that their prisma schema doesn't support constraints and more complex things. It is very basic and for any real world use migrations need to be used.
And I've had issues using it with SQLite. It doesn't support case insensitive filtering when using SQLite (I know, neither does SQLite). But as a result, i have to build a custom query which is pretty much what i wanted to avoid by using Prisma.
One I discovered just the other day [0] was that if you pass an undefined value into a field in a where clause of findFirst, it's treated the same as if you didn't pass anything at all.
If you do this with an id, for example, this can result in just the first record in the table being returned, because it's as though you have no where clause at all. Naturally this can lead to some surprising bugs if undefined values crop up at runtime, even potentially some really bad security bugs (imagine if this happens on the account table for instance).
I don't mean this as a criticism, I think it is just a normal part of being new and exciting software, maturing over time. But it's always a tradeoff to be aware of.
[0] https://github.com/prisma/prisma/issues/5149#issuecomment-13...
They all deploy web apps at the edge, so are distributed by default. A lot of the web apps deployed on these services have small volumes of data, read heavy and low writes. They are also low system resources so an embedded db makes sense?
Running SQLLite locally to the application will give extremely low read latency with most of the apps being read heavy/biased. The sales pitch for deploying at the edge is you can have hundreds of apps running across the globe near your users transparently.
I’m fairly interested in LiteFS as a distributed cache. I’ve seen systems use a message queue listening for updates then updating local caches which is a bunch of custom code/plumbing that can be replaced with LiteFS. Using LiteFS the read replicas are essentially materialised views.
We've been finding a lot of use cases for this at Fly.io. We'll use the static leasing in LiteFS for this as it's simpler for that use case.
LiteFS author here. Some other folks responded but I'll try to clarify here. LiteFS keeps SQLite as an embedded database so it maintains low latency. It injects itself as a passthrough file system so it can track changes with each transaction. On the replica side, it mostly acts like a separate SQLite process would in that it obtains the same locks as SQLite and applies page change sets to the database.
tl;dr from the application perspective, it just looks like a SQLite database. I mean, it is. We just capture change sets and propagate them behind the scenes.
With LiteFS, what happens if master goes down? Can a new one be elected?
LiteFS author here. You can run LiteFS anywhere -- even on a laptop with a few instances. There's no special Fly.io lock-in. The "fly-replay" is some syntactic sugar for our proxy that makes it easy to redirect requests but you could do the same with regular HTTP proxying.
> With LiteFS, what happens if master goes down? Can a new one be elected?
Yes, if you use Consul-based leasing then a new primary will come up automatically. Consul sessions have a minimum TTL of 10 seconds so you could lose write availability for up to that amount of time in the event of unexpectedly losing the primary. If you cleanly shutdown the primary then it will release its session and another node will pick up the lease right away.
The other option is static leasing where a single node will always be primary. This is a simpler option but you do lose write availability if your primary node goes down.
[0] https://github.com/splitgraph/seafowl/blob/main/examples/lit...
One thing that's not clear to me is whether Litestream+SQLite works in-memory. If Litestream wrote to disk on the primary node, but kept the full DB in-memory for facilitating reads, I can definitely see how it would be a lot faster than Postgres (as the author claims).
Though I'd also like to know if any RDBMSs have been designed for that use case, as I'd imagine traditional DB optimizations have been made for high incidence of disk access that would no longer hold true if the entire DB was in-memory
The underlying OS page cache is still available, as long as the fuse file system doesn't mess with it [1] so I think this is regular sqlite performance with a fuse overhead to reads/writes.
- [0] https://github.com/superfly/litefs/blob/main/docs/ARCHITECTU...
The way most web apps use DBs, sqlite is incredibly fast. Because most web apps make multiple queries, often sequentially, and they're very sensitive to DB latency. The network overhead adds up: https://twitter.com/benbjohnson/status/1514969870529560580
Loading the site was noticeably slow in MEL despite a SYD deployment. Took ~8s to load the page on a fast home fibre connection, abnormally slow for local sites, typical for US-only deployments.
I wonder if anonymous users are unnecessarily being given a session for tracking purposes, and that is causing the slowness. Cycling session IDs for authenticated users makes sense for the UX benefit of not having to log in again, but that speed issue is terrible for low intent anonymous users landing on the site for the first time.
So you implement some thing to do rollbacks and retries. Now you’ve created a complicated monstrosity because you will have users dealing with potentially different databases with different schemas.
You were better off with a single database with a sharding key.
I may try this when I get some free time, thanks for the feedback!
ALTER TABLE your_table ADD updated_at TEXT;
This is also relatively harmless, but now you have to figure out how to run this migration across your 100 databases, and deal with the case where maybe some fail, what do you do then? Rollback everywhere? But what if some users have already have rows that use the new column? Do you delete their data? Etc. Now consider a more complicated migration, maybe it renames a column, drops a column, adds a default value, drops an index, etc. You can see where problems might come from.That said I do really like your idea, I've gotten into sqlite a lot lately (I work on the database team at Fly.io), I'm just responding to you questions specifically about migrations.
For context, this would really be aimed at developers and admins to manage data for their clients or co-workers. Stuff that currently is an excel sheet, but needs to leave excel world to do some mundane task, such as filling a pdf and uplading it somewhere. You could do this is word mailmerge + excel, but now you (the admin) are in charge of performing this mundane task every week. And belive me this kind of thing is very common. In fact, the one special thing I may do is somehow write the data into an excel file and somehow allow them to sync to each other (not the logic part from either side though).
Not even sure if I would try to sell or just make it for free. It wouldn't be something streamlined like airtable, although if it got interest maybe I would take your approach.
If you like my idea btw, feel free to use it! I have nothing but respect for fly.io, I used it breifly for a side project and it was realy simple to set up. You saying you like my idea actually gives me confidence in trying it out, thank you!
Edit: Maybe the author needed to scale up the site infrastructure for Hacker News?