Cloudflare D1 as a cache for globally distributed Django apps
In this article, I show that Cloudflare D1 can be used to create a globally distributed, eventually consistent data cache for Python applications.
To summarize, I was able to increase a Django server's request throughput by 554% and decrease it's latency by 87%:
Cloudflare D1 Django app latencyFull-size image ↗
Cloudflare D1 Django app throughputFull-size image ↗
Those tests were conducted in the following test situation:
- Django server and users are in Australia.
- Central authoritative database is in Amsterdam.
- Some queries need authoritative answers from the central DB, some queries are fine with data that is a few seconds old.
The code I used to conduct this benchmark can be seen on my django-edge repository.
D1 strengths and weaknesses
D1 has an interesting set of strengths and weaknesses - I don't know of any other data system with a similar set of tradeoffs. In my opinion, the most important ones are:
Strengths:
- Allows you to easily and cheaply create a global network of read replicas.
- Allows you to create many databases at very little cost.
Weaknesses:
- Each database can hold a maximum of 10 GB.
- You have to use a SQLite client (what were they thinking?!)
- Only 1 write at a time.
Selecting a data system is all about assessing whether its specific set of tradeoffs can help you accomplish a specific use case. D1 use is exploding - so what are people using it for?
Use cases
Two use cases immediately come to my mind when thinking about D1's unique properties. Of course, there are many more use cases that I've listed here, but they are the ones that I find most interesting.
Use case 1: Vibe coding platforms
One of the very first sentences in D1's docs, as of October 19th 2026, is:
D1 allows you to create thousands of databases at no extra cost for isolation, perfect for scaling out application vibe-coding platforms.
Cloudflare (and others) think that D1 is an ideal data system for creating vibe coded apps - I agree. It's ability to create many databases quickly and cheaply makes it an ideal data system to create small, throwaway apps. Perfect for a vibe coding platform.
In fact, ChatGPT Sites runs on D1. So, I don't think I need to convince anyone that it's useful for that purpose:
"ChatGPT Sites lets anyone turn ideas into shareable experiences. D1 gives our users a simple, scalable way to add features like custom metrics, leaderboards, and comments to their dashboards, games, and tools."
- Jon Abrams, ChatGPT Sites @ OpenAI
Alright, D1 is useful for creating small, throwaway apps. But I wonder, could it be used to accelerate large, legacy, globally distributed applications?
Use case 2: As a relational data cache for legacy apps
Most web applications people use on a daily basis have some central, monolithic web framework to handle authentication, authorization, routing, and middleware. Something like Wordpress, Rails, Django, Spring, Express, etc.
If you are blessed to have your web app become globally popular, you'll run into the challenge of scaling the traditional relational database that underpins most popular web frameworks. This is an age-old problem with many imperfect solutions.
For example, you could go for a fancy-pants NewSQL database like Google Cloud Spanner or CockroachDB. These databases give you advanced controls for sharding & replicating your data into multiple database instances in different geographic locations. They are amazing pieces of data system engineering, but are not easy to deploy, configure, or maintain.
You could also go for something like Aurora Global Database, which has one master centralized database for writes, and deploy read replicas to regions where you want your clients to have accelerated reads. However, Aurora is notorious for costing a pretty penny, especially as usage goes up.
I wanted to know if Cloudflare D1 can be used to implement the same pattern as Aurora Global Database, but for far less money.
The answer is yes.
The experiment
The technical architecture of the experiment can be summarized with the following diagram:
D1 relational cache architectureFull-size image ↗
- Core database is in Amsterdam
- D1 master database is in Amsterdam. A read replica is spun up in Australia.
- Django application is deployed on Cloudflare workers, using django-cf
- A Cloudflare durable object polls PostgreSQL once per second, syncing selected tables to D1.
Scenarios tested
- In scenario 1, I pretended like the D1 cache didn't exist. The Django server, running in Australia, had to query the PostgreSQL DB in Amsterdam. The median end-to-end latency for serving these requests was almost 4 seconds.
- In scenario 2, I used D1 as a cache for user, authentication, and session data. I did not cache any business data. This setup improved request latency by a little, but not much. This makes sense, as Django middleware is smart enough to lazy-load user and session data. In practice, this means that Django is only make 1 round-trip from Australia to Amsterdam per end-user request. If Django wasn't doing this optimization for us, requests would probably take 6 seconds or more.
- In scenario 3, D1 is used as a cache for that user data, as well as business data. This improved latency by a lot, as Django now only had to make a round trip to it's local Sydney D1 database.
CLoudflare D1 Django app latencyFull-size image ↗
Cloudflare D1 Django app throughputFull-size image ↗
How does it stay in sync?
Tables that you want to sync to D1 get three columns: needs_sync, sync_version, and is_deleted. These three columns allow us to avoid using CDC, which in my experience as a database engineer on a DevOps team, breaks frequently enough to avoid it when you can.
Roughly speaking, when a row is created, updated, or deleted, it gets needs_sync set to true, and sync_version is incremented by 1 (or set to 1 if it was just created).
This is preferable to using something like a last_modified cursor column, because timestamps don't necessarily tell you what order changes became visible. In other words, it's possible for a slow transaction to commit after a faster one, but have an earlier timestamp.
Here is a table showing the syncing algorithm at a high level - it's simple enough:
D1 relational cache sync algorithmFull-size image ↗
Comparison to Cloudflare Hyperdrive
This is not a zero-effort drop-in accelerator, like Cloudflare Hyperdrive. That product caches query results - we are caching the underlying data itself. The approach we're using here is more complex, but has advantages in certain situations.
For example, if your web app executes lots of unique queries with different parameters, or frequently queries data that changes faster than the cache can usefully retain it, Hyperdrive may offer little benefit.
With that being said, if Hyperdrive is good enough for your use case, it will definitely be much easier to implement.
Limitations and production readiness
This was an experiment and is not intended to be a production ready template. In particular, this architecture introduces serious complexity in the form of a bespoke data syncing process.
Even if the compute/cloud economics seriously favor using the architecture I've outlined here, you would have to weigh that against the engineering time needed to maintain such a system.
Conclusion
Cloudflare D1 is an interesting system. It's a relational database that isn't trying to compete with MySQL, PostgreSQL, or Spanner. That's probably a good thing, as those products are so good already that it's very difficult to differentiate yourself.
Instead, the D1 product team is leaning into small, inexpensive, and globally distributed databases. That is clearly working out great as a complement to the myriad vibe-coding platforms that are springing up right now. However, as I've shown, it can also be used as a tool to cache data for a traditional web application.
Hono
I think there is space for someone to take a Cloudflare-native web framework like Hono and build a "globally distributed by default" web framework. Maybe I'll do it!