Blog

Dynamic vector tiles straight from PostGIS

Why we built MapSocket, and how a row change in your database becomes a fresh tile on every open map within seconds.

2 min read The MapSocket team

Most map stacks treat tiles as a build artifact. You export your data, run it through a tiler, upload the result and wait for caches to expire. That’s fine for a basemap that changes once a quarter. It’s painful for data that changes all day: parcels being edited, a fleet’s service areas, sensor footprints, work orders.

MapSocket takes the other route: dynamic vector tiles. You give it a read-only connection to the PostGIS database you already have, pick the tables to publish, and get a tile URL. When a row changes, the tiles that row touches are rebuilt and every map that’s open gets told about it.

The moving parts

Under the hood every source runs tilefeed, our open-source tile server written in Rust. One worker serves one source. The pipeline from database to browser looks like this:

  1. A trigger on each published table sends a NOTIFY on the tile_update channel with the layer name, the row id, the operation and the row’s old bounds.
  2. tilefeed listens on that channel, works out which tiles the old and new geometry cover at each zoom, and regenerates only those.
  3. An update_complete event goes out over Server-Sent Events with the zooms and the number of tiles touched.
  4. The map refetches those tiles. In MapLibre that’s a one-line setTiles call with a new cache-busting query.
CREATE TRIGGER mapsocket_parcels
  AFTER INSERT OR UPDATE OR DELETE ON public.parcels
  FOR EACH ROW EXECUTE FUNCTION
    mapsocket_notify_tile_update('parcels', 'id', 'geom');

The dashboard generates the trigger function for you and checks whether it’s installed, so you can see at a glance which layers will update live and which will only refresh on a full rebuild.

Why not query PostGIS on every tile request?

ST_AsMVT makes it tempting to render each tile on demand. It works well until a busy map pans across a dense area and fires a hundred tile requests at your production database at once. Your database is sized for your application, not for a map viewer’s request pattern.

tilefeed keeps a pre-built tile store and only goes back to the database for the rows that changed. Read load on your database is proportional to how much your data changes, not to how many people are looking at the map.

What’s next

The core tile path is what we spend most of our time on. Next up:

  • Versioned tile URLs, so browsers and CDNs can cache tiles for a long time and still pick up an update the moment it’s built.
  • Text and UUID primary keys. Today the id column has to be an integer.
  • PMTiles export for sources you want to archive or serve from object storage.

If you have a PostGIS table and a map that needs to keep up with it, the docs take you from connection string to live map.