Skip to content

Raster-array timeline playback: visible-tile decoded band ArrayBuffers accumulate in JS heap; memory does not drop after pause; partial tile eviction ineffective #13685

Description

@shengzhou-git

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

  1. Load a map with a raster-array source and layers styled with raster-array-band.
  2. Ensure ~6–12 tiles are visible (e.g. zoom 4–6 over Japan).
  3. Start timeline playback, advancing bands programmatically every ~900 ms (do not pan/zoom during playback).
  4. Observe performance.memory.usedJSHeapSize (or Performance Monitor JS heap) over 2–5 minutes.
  5. Pause playback and wait several minutes (optionally trigger manual GC in DevTools).
  6. (Optional) Attempt partial eviction by removing off-screen tiles from internal source cache (equivalent to evicting non-visible tiles while keeping viewport tiles).
  7. (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

  1. 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?
  2. 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?
  3. 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.)
  4. Is there a recommended pattern for time-series raster-array animation that avoids retaining all previously visited bands in visible-tile MRT caches?
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions