mapbox-gl-js version
mapbox-gl-js ^3.20.0 (resolved 3.24.x in lockfile during investigation)
Browser and version
No response
Expected behavior
Mapbox Support Issue — Raster-array timeline playback retains visible-tile band ArrayBuffers (JS heap / process memory growth)
Purpose: Copy/paste sections below when opening a ticket with Mapbox Support or filing a GitHub issue on mapbox/mapbox-gl-js.
Product: FunWaves marine weather map (wind / wave height overlays on Mapbox GL JS inside mobile WebView)
Title
Raster-array timeline playback: visible-tile decoded band ArrayBuffers accumulate in JS heap; memory does not drop after pause; partial tile eviction ineffective
Summary
We use Mapbox GL JS with raster-array sources (MTS tilesets) to animate wind/wave fields over a timeline by updating raster-array-band on heatmap + raster-particle layers (~1 band every 900 ms).
During playback, main-thread JS heap (performance.memory.usedJSHeapSize) grows monotonically (e.g. ~150 MB → 500 MB → 1 GB+). Pausing playback does not cause memory to fall back. Chrome DevTools heap snapshots show ArrayBuffer as the largest retained type.
We attempted to release memory via partial source-cache eviction (removing off-screen / non-current-zoom tiles only). This does not reduce heap, which matches Mapbox Native SDK documentation that tileCacheBudget does not account for resources allocated by visible tiles (Android RasterArraySource.tileCacheBudget).
On GL JS, inspecting the bundle shows each raster-array tile keeps an MRT decoded-band LRU with MRT_DECODED_BAND_CACHE_SIZE = 30 when partial: true (default), plus possible entireBuffer on the main thread (raster_array_tile_worker_source.ts, raster_array_tile.ts).
With ~9 visible tiles at zoom 5, timeline playback appears to retain a large number of decoded band blocks across visible tiles, leading to GB-scale heap retention and mobile WebView OOM-kill risk.
Separately, Cache Storage (mapbox-tiles) accumulates many .mrt?range=bytes=... entries (expected for partial/range fetching). This appears to be disk cache (Mapbox enforces ~500 entries) and is not the primary driver of JS heap growth, but we mention it for completeness.
Environment
| Item |
Value |
| SDK |
mapbox-gl-js ^3.20.0 (resolved 3.24.x in lockfile during investigation) |
| Host |
Next.js 14 web app, also embedded in React Native WebView (iOS / Android) |
| Basemap |
mapbox://styles/mapbox/dark-v11 |
| Source type |
raster-array (MTS), tileSize: 512, minzoom: 3, maxzoom: 8 |
| Layers |
raster (heatmap) + raster-particle on same source |
| Band animation |
map.setPaintProperty(layer, 'raster-array-band', bandId) every ~900 ms |
| Map options |
maxTileCacheSize: 16 |
| Example tile URL pattern |
/rasterarrays/v1/funwaves.wind-openmeteo-dev/{z}/{x}/{y}.mrt?jobid=...&range=bytes=... |
| Browser (repro) |
Chrome desktop (DevTools Memory / Application panels) |
Steps to reproduce
- Load a map with a
raster-array source and layers styled with raster-array-band.
- Ensure ~6–12 tiles are visible (e.g. zoom 4–6 over Japan).
- Start timeline playback, advancing bands programmatically every ~900 ms (do not pan/zoom during playback).
- Observe
performance.memory.usedJSHeapSize (or Performance Monitor JS heap) over 2–5 minutes.
- Pause playback and wait several minutes (optionally trigger manual GC in DevTools).
- (Optional) Attempt partial eviction by removing off-screen tiles from internal source cache (equivalent to evicting non-visible tiles while keeping viewport tiles).
- (Optional) Take heap snapshots before playback, after playback, and after pause.
Expected behavior
- Decoded band data for previously displayed bands on visible tiles should be reclaimed after bands are no longer needed (LRU bounded memory per tile, or explicit release when band paint property changes).
- Pausing playback should allow memory to stabilize or decrease.
- Documented cache budget / eviction mechanisms should prevent unbounded growth during band animation.
Actual behavior
- JS heap monotonically increases during playback.
- Pause does not restore heap to baseline (not merely GC lag — retained size stays high).
- Heap snapshot:
ArrayBuffer dominates retained size.
- Partial eviction of off-screen tiles:
removedTiles: 0, heap unchanged (visible tile count unchanged, e.g. 9/9).
- Full
clearTiles() on the active source does force reload and is the only approach we found that can release visible-tile resources — but causes visible flicker, which we are trying to avoid during playback.
Measurements (representative)
| Metric |
Before playback |
During playback |
After pause |
JS heap (usedJSHeapSize) |
~150 MB |
~500 MB – 1069 MB |
No meaningful drop |
| Visible tiles |
~6–9 |
~6–9 |
~6–9 |
activeOffscreenTiles |
0 |
0 |
0 |
pressure (custom) |
none |
none |
none |
| Partial evict result |
— |
z5 -0t -0c |
same |
What we tried (workarounds)
| Action |
Effect on JS heap |
Partial eviction (touchActiveOffscreen: true, evict off-screen tiles) |
No decrease |
| Partial eviction (gentle, non-current zoom only) |
No decrease |
| Pause playback |
No decrease |
clearTiles() / full source cache clear |
Releases memory but full viewport reload / flicker |
Lower maxTileCacheSize (16) |
Tile count bounded, but band ArrayBuffer growth continues on visible tiles |
Questions for Mapbox
- Is monotonic growth of decoded band
ArrayBuffers on visible tiles during raster-array-band animation expected when partial: true and MRT cache size is 30?
- Is there a supported GL JS API (style property or runtime call) equivalent to Native
RasterArraySource.tileCacheBudget or MapboxMap.reduceMemoryUse() for raster-array band memory on visible tiles?
- Should we use
volatile: true (Native experimental) or another source flag to limit in-memory band retention? (Style spec notes GL JS does not yet support volatile.)
- Is there a recommended pattern for time-series raster-array animation that avoids retaining all previously visited bands in visible-tile MRT caches?
- Does Mapbox consider
clearTiles() during pause / memory pressure the intended recovery path for this scenario?
References
Contact / project context
- App:(marine weather map for surfers)
- Use case: Animated wind/wave heatmap + particles over ~24 hourly bands
- Priority: Mobile WebView stability (OOM kills on mid-range devices)
Actual behavior
No response
Link to the demonstration
No response
Steps to trigger the unexpected behavior
No response
Relevant log output
mapbox-gl-js version
mapbox-gl-js ^3.20.0 (resolved 3.24.x in lockfile during investigation)
Browser and version
No response
Expected behavior
Mapbox Support Issue — Raster-array timeline playback retains visible-tile band ArrayBuffers (JS heap / process memory growth)
Title
Raster-array timeline playback: visible-tile decoded band ArrayBuffers accumulate in JS heap; memory does not drop after pause; partial tile eviction ineffective
Summary
We use Mapbox GL JS with
raster-arraysources (MTS tilesets) to animate wind/wave fields over a timeline by updatingraster-array-bandon heatmap +raster-particlelayers (~1 band every 900 ms).During playback, main-thread JS heap (
performance.memory.usedJSHeapSize) grows monotonically (e.g. ~150 MB → 500 MB → 1 GB+). Pausing playback does not cause memory to fall back. Chrome DevTools heap snapshots showArrayBufferas the largest retained type.We attempted to release memory via partial source-cache eviction (removing off-screen / non-current-zoom tiles only). This does not reduce heap, which matches Mapbox Native SDK documentation that
tileCacheBudgetdoes not account for resources allocated by visible tiles (Android RasterArraySource.tileCacheBudget).On GL JS, inspecting the bundle shows each raster-array tile keeps an MRT decoded-band LRU with
MRT_DECODED_BAND_CACHE_SIZE = 30whenpartial: true(default), plus possibleentireBufferon the main thread (raster_array_tile_worker_source.ts,raster_array_tile.ts).With ~9 visible tiles at zoom 5, timeline playback appears to retain a large number of decoded band blocks across visible tiles, leading to GB-scale heap retention and mobile WebView OOM-kill risk.
Separately, Cache Storage (
mapbox-tiles) accumulates many.mrt?range=bytes=...entries (expected for partial/range fetching). This appears to be disk cache (Mapbox enforces ~500 entries) and is not the primary driver of JS heap growth, but we mention it for completeness.Environment
mapbox://styles/mapbox/dark-v11raster-array(MTS),tileSize: 512,minzoom: 3,maxzoom: 8raster(heatmap) +raster-particleon same sourcemap.setPaintProperty(layer, 'raster-array-band', bandId)every ~900 msmaxTileCacheSize: 16/rasterarrays/v1/funwaves.wind-openmeteo-dev/{z}/{x}/{y}.mrt?jobid=...&range=bytes=...Steps to reproduce
raster-arraysource and layers styled withraster-array-band.performance.memory.usedJSHeapSize(or Performance Monitor JS heap) over 2–5 minutes.Expected behavior
Actual behavior
ArrayBufferdominates retained size.removedTiles: 0, heap unchanged (visible tile count unchanged, e.g. 9/9).clearTiles()on the active source does force reload and is the only approach we found that can release visible-tile resources — but causes visible flicker, which we are trying to avoid during playback.Measurements (representative)
usedJSHeapSize)activeOffscreenTilespressure(custom)z5 -0t -0cWhat we tried (workarounds)
touchActiveOffscreen: true, evict off-screen tiles)clearTiles()/ full source cache clearmaxTileCacheSize(16)Questions for Mapbox
ArrayBuffers on visible tiles duringraster-array-bandanimation expected whenpartial: trueand MRT cache size is 30?RasterArraySource.tileCacheBudgetorMapboxMap.reduceMemoryUse()for raster-array band memory on visible tiles?volatile: true(Native experimental) or another source flag to limit in-memory band retention? (Style spec notes GL JS does not yet supportvolatile.)clearTiles()during pause / memory pressure the intended recovery path for this scenario?References
tileCacheBudgetcaveat (visible tiles excluded):https://docs.mapbox.com/android/maps/api/11.2.1/mapbox-maps-android/com.mapbox.maps.extension.style.sources.generated/-raster-array-source/tile-cache-budget.html
reduceMemoryUse()(clears in-memory tile/image/texture caches):https://github.com/mapbox/mapbox-maps-ios/blob/main/Sources/MapboxMaps/Foundation/MapboxMap.swift
partialdefault true):https://docs.mapbox.com/mapbox-gl-js/api/sources/#rasterarraytilesource
maxTileCacheSize:https://docs.mapbox.com/mapbox-gl-js/api/map/#map-parameters
raster-array:https://docs.mapbox.com/style-spec/reference/sources/#raster-array
Contact / project context
Actual behavior
No response
Link to the demonstration
No response
Steps to trigger the unexpected behavior
No response
Relevant log output