Never Sync the Whole Planet into Your Serving Database
81,455,423 places ship inside Overture Maps release 2026-09-23.1, and Postgres can hold every one of them. The inserts finish. The spatial index builds. The seduction is that this works. The trap is what the full copy costs afterward: a database chosen before anyone knows the query pattern answers the questions it was built for and quietly refuses the rest.
Our claim is narrower than a survey. The destination of an Overture extract has one real input: the shape of the query that will hit it. Team taste has none. Never point a serving database at the full release. Cut a regional extract with DuckDB first, and land it in the engine the workload implies. What follows is one decision path with three destinations, and each destination wins a different workload outright.
The extract comes first, wherever the extract ends up
One rule holds under every roof in this post. Between the release and any database you serve from sits an extract, scoped to the region you actually operate in, cut with DuckDB against the GeoParquet in place. We walked through those mechanics in the previous post on querying Overture with DuckDB. The part that matters for storage: a predicate on the bbox struct column prunes Parquet files and rows without touching a single geometry. That is how a bounding box over Lake Tahoe returns 28,166 places in 2.77 seconds and 106,242 transportation segments in 18.0 seconds, measured with DuckDB 1.5.6 on 2026-10-11 against release 2026-09-23.1.
That extract is also the only sane unit of transfer in this stack, and GeoParquet is the reason. Overture publishes GeoParquet, the extract keeps GeoParquet, and the engine that loads it next consumes the same file shape, whatever that engine turns out to be. The release ID rides along in the job that produced the file, so every store downstream knows which version of the world it is describing.
The skeptic's version of this section reads "let the index handle it," and against a full theme the index helps a query, not the monthly refresh cycle. Places alone is 81,455,423 rows to re-import every release, and the refresh is the part you repeat. Scope the region first and the refresh becomes a two-second region query followed by a bounded load, which is a Tuesday afternoon instead of a migration project.
DuckDB wins the questions that arrive in batches
Analytical workloads barely leave DuckDB at all. Any question posed against a pinned release, whether a quality audit or an enrichment join of your own records to the places theme, runs where the data already sits. The engine is columnar and reads GeoParquet without translation. The spatial extension carries the geometry functions, and because there is no server to run, the extract and the analysis can be the same process on the same day.
Ground the claim. A question like "which furniture stores in this delivery zone carry no website" is batch-shaped. Nobody is standing in front of the answer, so it runs in DuckDB against release 2026-09-23.1, and the result goes out as a file.
The limit is concurrency. DuckDB is a single analytical engine, not a transactional server, and it will not take an application's concurrent writes. That is fine, because the batch engine's job ends when the answer lands in a file, most usefully a GeoParquet file, ready for whichever store does the serving.
PostGIS wins the questions that interrupt an application
A user drops a pin and the app owes them a delivery zone before the spinner gets annoying. That is a random point-in-polygon question, asked once per interaction, under concurrent load, with the answer joining on to the rest of your business tables. Postgres with PostGIS wins this workload because a GiST index answers random spatial lookups, transactions keep concurrent writers honest, and the join from geography to your schema is plain SQL:
SELECT zone.name
FROM delivery_zones zone
WHERE ST_Contains(zone.geometry, ST_SetSRID(ST_MakePoint(-120.4, 39.1), 4326));
With a GiST index on delivery_zones.geometry, that query is the shape of almost every serving-layer question an Overture extract will face on the relational side. Random access is the whole story here. The extract loaded into PostGIS exists so that the map answers per user instead of per batch run.
The cost is the load, and it is real. The places theme alone is 81,455,423 rows in release 2026-09-23.1, and the same Tahoe bounding box counted 228,935 buildings in 37.1 seconds of DuckDB query time. The addresses theme is heavier still: 474 million features that the Overture docs mark alpha. Importing a full theme makes every monthly release a maintenance event with your pager attached. The workaround is already in place if you followed the first section: load the extract rather than the theme, and pin the release the extract was cut from.
MongoDB wins when documents, not relations, are the currency
Some pipelines already speak documents, and for them Overture's official Python CLI, maintained in the OvertureMaps GitHub organization, writes a download straight into a running MongoDB:
overturemaps download --mongodb
A places record survives that trip as a document, which is what it always was. Arrays of websites and addresses stay arrays, and brand and taxonomy keep their nesting. The point geometry arrives as GeoJSON, indexed with a 2dsphere index, so a proximity question becomes the store's native work instead of a foreign join.
This path wins when the application consuming the extract is already a document store. A catalog service that stores its records as documents gets the extract in the same collection style as its own data, and answers its proximity questions from the same engine. Introducing a relational system for the privilege of storing points in rows is a second system to back up, and the impedance layer between documents and tables is real work that buys nothing here.
The honest limit is depth. MongoDB's geospatial vocabulary, 2dsphere, covers the GeoJSON geometry types up to polygons, and it stops short of geoprocessing. There is no st_buffer, and nothing inside the database builds new geometry from existing geometry. The workaround keeps the pipeline honest: compute the buffer in DuckDB during the extract, and land the finished polygon for MongoDB to index and serve.
Each engine has a hard limit, and each limit has a workaround
PostgreSQL's limit is the import itself. What a full-theme load costs is mostly the rhythm, more than the bytes: index builds and vacuum cycles, one round per monthly release, on dates Overture picks. The escape is scope: the theme stays in Parquet, and the serving database never sees a whole release.
MongoDB's limit is vocabulary, and the consequence follows directly. What 2dsphere gives the document path it also bounds: the store answers with the geometry you loaded, never with geometry it computes, so every derived polygon must already sit in the extract. The escape is a wider DuckDB step, which does not touch the store at all.
DuckDB's limit is the serving role it cannot hold. Readers can share a file without trouble, but an application writes all day, and writers get one process at a time. The escape is that it never has to hold the role: its answers leave as files, and files are the input every other engine here understands.
The decision path, in one table and three questions
Ask one question first: is anyone standing in front of the answer? If no, the question is batch-shaped and DuckDB keeps it, against the pinned release, with GeoParquet as the output. If yes, ask a second: does the application around the data already speak documents or relations? Documents route the extract to MongoDB, relations route it to PostgreSQL, and in both cases the engine you already run is the cheaper destination because the extract is the part that moves.
| The query that arrives | Where the extract lands | The reason that settles it |
|---|---|---|
| Release-level aggregates and record enrichment | DuckDB over GeoParquet | Columnar and in place, with no server to keep alive |
| A user's point-in-polygon question, under load | PostgreSQL with PostGIS | Indexed random access with relational guarantees |
| A document service consuming places records | MongoDB | The CLI writes there directly, and the rows are already documents |
The extract is the architecture. The database underneath stays negotiable, and the monthly release is the reminder.
AngoraTek builds release-pinned Overture pipelines, from the DuckDB extract to whichever engine serves your workload. Show us the query that has to be fast and we will walk the decision path with you.Thanks for reading this far. If this sounds like the problem you're wrestling with, tell us about it. A principal reads every message.
Start a conversation