Skip to content

Conversation

@polastre
Copy link
Collaborator

This change introduces a number of performance optimizations to reduce the time to generate tiles.

  • Creates a reusable buffer pool for gzip compression so that each tile generated doesn't allocate a new buffer. This significantly reduces garbage collector pressure
  • Reuse the gzip writer instance, since each allocation of the gzip writer incurs a 1.4MB allocation
  • Use the underlying pgtype.DriverBytes from pgx for postgres queries. Instead of allocating a buffer to write the tile data into from postgres, reuse the underlying pgx buffer which results in no allocations.
  • Pre-generate the sql queries for each zoom and store them in a shared map. Since the sql query at each zoom is the same, there's no need to generate the query string on each and every tile.
  • Refactor the query strings to use coordinate placeholders ($1, $2, $3) so that a single statement per zoom can be prepared and reused by pgx. Before, every tile query had the coordinate embedded in the query, so each query was a new prepared statement and didn't benefit from the reuse of previous queries.
  • Turn JIT off in each connection session. When running many repeated queries, JIT adds 40ms per query that is completely unnecessary.
  • Add a custom --init command that accepts a sql statement to run on connection session start. This can be used to further optimize the performance of postgres queries based on the behavior of your tileset.json.
  • Refactor the exporter and worker into its own package to make it easier to decompose and improve, rather than in main.go
  • Add memory and gc stats to progress reporter to verify memory allocations
  • Upgrade to go 1.25, and upgrade dependencies

polastre and others added 21 commits December 19, 2025 18:30
this reduces memory by not re-creating a buffer on every single
tile compression. Instead, it reuses the buffer and reuses the writer.
Previously we were scanning into a new []byte slice which gets
created for every tile. Since the pgx.Conn is open for the
entirety of the export process (per worker), we can use its
underlying buffer since no other queries will be made during
that time.

If a BulkWriter is used (the only one is mbtiles), then compression
is also used, which means the resulting byte buffer that is
ultimately written is the gzip compression output (and not the
raw result from postgres). If mbtiles format is not selected,
the tile is immediately written to disk.
Since tiles are written in batches, we need to hold on to the buffer
until the tile is written. This creates an issue since the gzip
implementation wants to release the buffer immediately and copy
the results to yet another byte slice. Instead, keep track of the
buffers and their corresponding byte slices and don't release them
until the batch is written. Then release all the buffers and clear
the references, and get ready for the next batch.
This is a merge of branch 'include-sqlite' into memory-optimization
The SQL query strings are the same for each zoom level,
however the coordiantes (z, x, y) change. Before, a new
query string was generated for every tile. This had two
bad side effects:
1. A new string was allocated (and had to be garbage collected) for each tile's query
2. Postgres couldn't re-use the query plan it prepared because the z/x/y coordinates were included in the query string itself

The fix here is to:
1. Generate query strings prior to kicking off the export, store them in a map, then retrieve them from the map for each tile. This is O(1) time and zero space allocated per tile.
2. Use query strings with argument placeholders (like `$1`), then pass z/x/y coordinates in as query parameters. This also has zero allocation and allows postgres/pgx to prepare the statement. pgx works by caching the statements it has previously prepared and then using those prepared statements. If every tile has a different query, then the cache grows like crazy. If each zoom has its own query, then the cache size is only related to the number of zooms being processed.
This allows for sending custom session configuration and tuning commands
to improve export speed for your specific tileset
@polastre polastre requested a review from maryryang2 January 12, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants