I'll let you in on a sort of dirty secret:
It's almost always better to use Durable Objects storage, rather than D1. Even if you only want a single global database, it's better to implement that as a singleton Durable Object, than by using D1. Because that's all D1 itself actually is: a singleton Durable Object that exposes an API to its SQLite database. It's just a wrapper.
With raw Durable Objects, you get to bring your code to run on the same machine as the database itself. Your queries run on a local file, synchronously, rather than going over a network. There is essentially zero latency when using sqlite storage in a Durable Object.
If your app does no more than one DB query per request, then D1 is fine: the Worker runs near the end user, and talks over the long-haul network to D1 just once. Whereas with Durable Objects, your Worker would talk over the long-haul network to the Durable Object. No difference.
But if your app ever does two or more queries in series for a single request, then Durable Objects becomes vastly better, because you get to move that query-chaining code to happen directly where the database lives, rather than have multiple round trips.
Really, though, the only reason D1 exists is for comfort. Once you know how to use Durable Objects, there's no reason to use D1. We offer D1 because a lot of people don't want to learn a new model. (Which is fair. People are busy and may have better things to do.)
One caveat: D1's read replica support still isn't exposed in a way that you can use it in raw Durable Objects, so if you are using that, it's a legitimate advantage to D1. But we do plan to bring this functionality to raw Durable Objects at some point.
why not deploy the worker and d1 on the same machine, just like one would do with durable objects?
Part of the point of Workers is they run everywhere, not on a specific machines where they've been "deployed". We generally run a Worker in the colo where we received the HTTP request triggering it.
There is a feature called "smart placement" which, when enabled, tells us to detect if a Worker commonly makes multiple round trips to a particular backend and, if so, try to run the Worker close to that back-end, instead of close to the user. This helps with D1. But even if you have the Worker running in the same colo or even same machine as the D1 database, you're still speaking a network protocol to talk to it, serializing and deserializing data, switch contexts, etc. Directly invoking SQLite locally will still be orders of magnitude faster.
Also with Durable Objects you can have many objects, e.g. one object per user or one object per document, spread around the world. It's a distributed systems building block. Many of the things you can build on it can't really be "smart" auto-detected.
this is new, thanks for that. I just had a brief talk with Gemini about it and it sparked interest in this model (Durable Objects) which I'm going to dive into deeper.