Repository navigation
Conversation
Renderers fetch the tiles, glyphs and sprites of a style through MapLibre Native, which compresses and stores every response in an in-memory SQLite cache of its own (50 MiB) and looks each fetch up there first. When the style's sources are Martin's own, that work buys nothing: the tile is a local read away. `styles.rendering.ambient_cache_size_mb` sets its size; `0` disables it. Unset keeps MapLibre Native's default, so nothing changes unless configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o
… caches Every other `size_mb` in Martin is 1000 * 1000 bytes; this one was MiB. The conversion moves to `RendererConfig::ambient_cache_bytes`, tested unset, at 0 and at 128, and a negative size is rejected. The static worker gets a constructor, like the tile worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o
MapLibre Native asks the server again for any cached resource that is not fresh, and Martin sends no `Cache-Control` unless configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o
With one renderer per worker, cycling two styles reloads the first into a new renderer. With the ambient cache its tile reaches the upstream once; with `ambient_cache_size_mb: 0`, twice. The cassette can now mark its answers fresh (`Cassette::serving_fresh_for`): without `Cache-Control`, MapLibre Native requests a cached resource again anyway, so the cache makes no difference to the count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o
The example listed 50, which reads as the default but is 50 MB, not the 50 MiB MapLibre Native uses when the option is unset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new option,
styles.rendering.ambient_cache_size_mb, sets the size of MapLibre Native's ambient cache for the render pools, in MB (1000 × 1000 bytes, like Martin's othersize_mboptions).0disables it. Unset keeps MapLibre Native's default (50 MiB, in memory), so nothing changes unless configured.The value reaches every tile and static renderer as
ImageRendererBuilder::with_resource_options(ResourceOptions::default().with_maximum_cache_size(bytes)). Unset, the builder is left alone, which is the sameResourceOptions::default()(maplibre_native 0.10.0,renderer/builder.rs).Why
Renderers fetch a style's tiles, glyphs and sprites through MapLibre Native's file source. Every response is compressed and stored in an in-memory SQLite database (
OfflineDatabase::putTile), and every fetch looks it up there first (getTile, thendecompress). When the style's sources are Martin's own (http://127.0.0.1:3000/<source>), the tile is already a local read away, so that cache only costs CPU and memory.It also spares fewer requests than it seems:
MainResourceLoaderalways goes on to the network after a cache hit, andOnlineFileSourceonly holds that request back while the cached response'sExpires/Cache-Controlsays it is fresh. Martin sends noCache-Controlunlesscache_controlis configured, so with Martin's own sources every resource is requested again anyway.From MapLibre Native core
a33d3f00(the one maplibre_native 0.10.0 builds against):OfflineDatabase::disabled()(platform/default/src/mln/storage/offline_database.cpp:107) is true whenmaximumAmbientCacheSize == 0and no offline region exists (Martin creates none);get/putthen return before reading, compressing or writing anything (lines 259, 295). Only thelistRegions()query insidedisabled()is left.util::DEFAULT_MAX_CACHE_SIZE= 50 × 1024 × 1024 (src/mln/storage/resource_options.cpp:13).platform/default/src/mln/storage/main_resource_loader.cpp, lines ~85-110.It is unrelated to Martin's
cache.size_mb(Martin's own tile cache) and to MBTiles' SQLite. A CDN in front does not change the picture: it caches the rendered PNG, while this cache holds the vector tiles a renderer reads.Measured with this branch's binary
basemaps-server image with Martin built from this branch (release,
--features pmtiles,mbtiles,rendering), 24 PNG styles,tile_size: 256,max_pixel_ratio: 2,renderers_per_worker: 400, 4 render workers on 4 vCPU (--cpuset-cpus=0-3). Load: 12,000 raster URLs sampled from production's shield misses (2026-10-07 12:00-12:20 UTC), 8 concurrent requests. Figures are the second half of each run (6,000 URLs, warm page cache); runs interleaved base, 0, base, 0 on an otherwise idle VM (load average 1.4 and falling when the first run started, after a build).ambient_cache_size_mb: 0The same 13 URLs per half answer 404 in every run (tiles the styles do not serve); they are left out of the latency figures. An earlier measurement with an environment-gated build making the same call gave −12 % CPU and +13 % tiles/s.
Tests
martin/src/config/file/resources/styles.rs): unset givesNone,0gives 0 bytes,128gives 128,000,000 bytes;-1is rejected.e2e-tests/tests/rendering.rs,the_ambient_cache_answers_a_reloaded_renderer): one worker withrenderers_per_worker: 1rendersmaplibre_demo,maptiler_basic,maplibre_demo, so the third request loadsmaplibre_demointo a new renderer. With the cache, its tile reaches the test server once; withambient_cache_size_mb: 0, twice. Each tile also matches its reference image. Passed 5 runs in a row.Cassette::serving_fresh_for(hosts, max_age), which addsCache-Control: max-ageto its answers (with a unit test). Without it MapLibre Native requests the cached tile again (checked: a plain secondGET, no conditional headers), and the count is 2 either way. Two renderers of one style at@1xand@2xdo not work for this either: the cache keys tiles by pixel ratio.Checks run
In an Ubuntu 24.04 container with Rust 1.98.1, the prebuilt MapLibre Native core and lavapipe,
CARGO_BUILD_WARNINGS=deny,CI=1:cargo fmt --all -- --checkjust clippy(CI's lint job) andcargo clippy --workspace --all-targets --features martin/rendering: clean.cargo clippy -p martin -p martin-core --all-targets --no-default-features --features renderingandcargo clippy -p martin-e2e-tests --all-targets --features test-rendering: only findings already onmain(martin/build.rs,martin/src/config/file/process.rs,martin/src/config/file/main/lifecycle.rs,e2e-tests/tests/rendering.rs:762). CI runs neither.just test-rendering: all pass.cargo nextest run -p martin-e2e-tests --lib: all pass.cargo nextest run -p martin-core --features rendering --lib: all pass.cargo nextest run -p martin --features rendering --libandjust test-packages-ci: all pass exceptinit_warns_about_an_unreadable_directory_and_publishes_its_siblings, which needs a non-root user (the container runs as root, which can read achmod 000directory).just spellcheck,markdownlint-cli2with the repo config: clean.Not run: the PostgreSQL, S3, COG and DuckDB suites (untouched),
just check(cargo-hack over every feature),gen-schemas(the schema build has norendering).Before upstream
Upstream asks for an issue first and small PRs. Open questions for the maintainers:
*_cache_size_mbto a nested*.cache.size_mb/directory_cache.size_mb(migration guide).ambient_cache: { size_mb: 0 }would follow that.0when every source of a style is Martin's own, but that changes behavior, so it is left to them.martin-core:StyleSources::enable_renderingandRenderPools::newgain an argument, as they did forrenderers_per_workerandtile_size.🤖 Generated with Claude Code
https://claude.ai/code/session_01RWEkV2RXSxbJr1vx7W5n5o