From 90e62cc0ecd31c993a9c5283bb6099ca8fb2edbb Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:02:58 +0300 Subject: [PATCH 01/12] docs(tracing): document trace exemplars on the duration histograms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page said `[tracing]` and `[observer]` "have nothing to say to each other", which exemplars falsify: with both enabled the duration histograms carry the active trace, so a latency spike in Grafana is one click from the trace behind it. That claim is now scoped to what is still true — neither section implies the other, and tracing publishes no metric family of its own. The new section carries what an operator has to get right, because all three requirements fail quietly: the scrape format, exemplar storage on the server, and the datasource link. And the sampling asymmetry with log records, which looks like an inconsistency until the reason is stated — an exemplar is only a link, and prometheus keeps one per bucket. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- docs/tracing.md | 93 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/docs/tracing.md b/docs/tracing.md index 1b4eaa4..2e114d3 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -12,7 +12,8 @@ it. It is **off by default** and configured in its own `[tracing]` section. Tracing runs *alongside* the [Prometheus observer](#relationship-to-metrics) rather than replacing it, and puts its trace ids on -[log records](#relationship-to-logs). +[log records](#relationship-to-logs) and on the +[duration histograms](#trace-exemplars). ## What tracing answers that metrics cannot @@ -175,15 +176,95 @@ datasource wiring for both directions, and the sampling caveat are on the ## Relationship to metrics -Tracing and metrics are configured and switched on independently: `[tracing]` -and `[observer]` have nothing to say to each other. Metrics stay the Prometheus -observer's job. Nothing in the tracing path registers a Prometheus collector or +Tracing and metrics are switched on independently — `[tracing]` and `[observer]` +are separate sections and neither implies the other. Metrics stay the Prometheus +observer's job: nothing in the tracing path registers a Prometheus collector or installs an OpenTelemetry meter provider, so a build with tracing enabled -publishes exactly the metric families it published before — which Shigola's own +publishes exactly the metric families it published before, which Shigola's own test suite asserts rather than assuming. +With both enabled, though, the two signals are joined in one direction: the +duration histograms carry the active trace as a **Prometheus exemplar**. + Use both. Metrics tell you *that* something is slow across the whole fleet; -traces tell you *where*, for one request. +traces tell you *where*, for one request. Exemplars are what get you from the +first to the second without a search. + +### Trace exemplars + +Each duration observation made inside a **sampled** trace carries that trace and +span, so a bucket in a Grafana histogram panel shows a dot you can click: + +| Family | The exemplar names | +|:---|:---| +| `shigola_cache_duration_seconds` | the cache operation as a whole | +| `shigola_cache_tier_duration_seconds` | that tier's own read, write or purge | +| `shigola_api_duration_seconds` | the request | + +The labels are `trace_id` and `span_id` — the same names the +[log records](./logging.md#trace-correlation) carry. + +`span_id` names the operation measured rather than the request, which is the +point on the per-tier family: a slow bucket there lands on the tier read that +was slow, on the one histogram whose +[whole purpose](./layered-cache.md#tier-latency-and-why-it-used-to-look-identical-everywhere) +is telling tiers apart. + +Nothing is attached to an observation made outside a trace, or inside an +unsampled one. The observation is recorded exactly as it would have been, with +no empty label — so a panel looks the same as before, minus the dots. + +### Wiring it up in Grafana + +Three things have to be true, and each fails quietly on its own. + +**The scraper must ask for OpenMetrics.** It is the only exposition format that +encodes exemplars; the classic Prometheus text format has no syntax for them and +drops them without a word. Prometheus asks for it by default, and Shigola's +`/metrics` answers in it — so this is normally already true, and worth checking +first if the dots never appear. + +**The server must store them.** Prometheus needs +`--enable-feature=exemplar-storage`; Mimir has its own equivalent. Without it +the exemplars are scraped and discarded. + +**The Prometheus datasource needs the link.** In its *Exemplars* section, add +one with the label name `trace_id` and the Tempo datasource as its target: + +```yaml +exemplarTraceIdDestinations: + - name: trace_id + datasourceUid: +``` + +Without it the exemplars still render as dots on the panel, with nothing behind +them. + +:::warning +**An exemplar is only ever a link, so unsampled traces are deliberately left +out.** Prometheus keeps one exemplar per bucket and overwrites it with the next +observation to land there, so the stored one is almost always the most recent — +and at the default `sample_ratio = 0.01` the most recent observation is almost +never sampled. Attaching them regardless would make clicking a bucket open +nothing roughly 99 times out of 100. This is the opposite of what +[log records](./logging.md#trace-correlation) do with the same trace, and for +the opposite reason: a log line's trace id still groups that request's lines +whether or not Tempo kept the trace. +::: + +### Two limits + +**`le` label values changed.** Under OpenMetrics a bucket boundary that would +otherwise look like an integer is written with a trailing `.0`, and a label value +is part of a series' identity — so `le="1"` is now `le="1.0"`. That affects the +1, 2.5 and 5 boundaries of the cache families and the 1, 5 and 10 of the HTTP +one. Anything matching an exact `le` — a recording rule, a panel pinned to one +bucket — needs checking against the new spelling. It was the price of exemplars +being scrapeable at all. + +**Pushed metrics carry no exemplars.** A deployment using the observer's +`push_url` pushes through the classic text format to a Pushgateway, which has no +notion of them. Everything above applies to scraped deployments only. ## Spans From 9124dd37e266f8c406ae2e15e5fc7298ea315d5a Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:02:58 +0300 Subject: [PATCH 02/12] docs: cross-reference exemplars from the pages they change Four pages describe something exemplars touch. The logging page names the same two ids and now says where they differ; the layered cache page's tier-latency section is the one exemplars are most useful on, and its bucket-identity warning has a second instalment now that the le values changed again; the /metrics endpoint answers in a different format; and the tracing config section claimed independence from the observer. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- docs/configuration.md | 3 ++- docs/http-endpoints.md | 2 +- docs/layered-cache.md | 9 +++++++++ docs/logging.md | 5 +++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 118e558..57b7a49 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -527,7 +527,8 @@ Cache tiles in a GCS bucket. `[tracing]` configures OpenTelemetry trace export over OTLP. It is off unless the section says otherwise, and it is independent of `[observer]`: metrics and -traces are switched on separately. +traces are switched on separately. With both on, the duration histograms carry +[trace exemplars](./tracing.md#trace-exemplars). ```toml [tracing] diff --git a/docs/http-endpoints.md b/docs/http-endpoints.md index 4f7a7c6..9c6b792 100644 --- a/docs/http-endpoints.md +++ b/docs/http-endpoints.md @@ -25,7 +25,7 @@ it, apart from `/metrics`. | `/collections/{collectionId}/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}` | A vector tile | | `/tileMatrixSets` | The [tiling schemes](./tile-matrix-sets.md) served | | `/tileMatrixSets/{tileMatrixSetId}` | One scheme's definition | -| `/metrics` | Prometheus metrics, when a Prometheus observer is configured. Cache metrics are listed under [Layered cache](./layered-cache.md#metrics). | +| `/metrics` | Prometheus metrics, when a Prometheus observer is configured. Answers in OpenMetrics when the scraper asks for it, which is what carries [trace exemplars](./tracing.md#trace-exemplars). Cache metrics are listed under [Layered cache](./layered-cache.md#metrics). | Full documentation on [OGC API - Tiles](./ogc-api-tiles.md), including content negotiation, caching and the conformance classes declared. diff --git a/docs/layered-cache.md b/docs/layered-cache.md index edada81..26b9640 100644 --- a/docs/layered-cache.md +++ b/docs/layered-cache.md @@ -175,6 +175,11 @@ The pool and the chain publish their own counters: > `shigola_cache_errors_total` (and `shigola_cache_tier_errors_total` per tier). Dashboards and alerts > referring to `errors` need updating. +With [tracing](./tracing.md) enabled, `shigola_cache_duration_seconds` and +`shigola_cache_tier_duration_seconds` also carry a +[trace exemplar](./tracing.md#trace-exemplars) — so a slow bucket on the per-tier +histogram is one click from the trace of the tier read that was slow. + ### Tier latency, and why it used to look identical everywhere `shigola_cache_tier_duration_seconds` buckets at **1-2-5 per decade from 100µs to 5 seconds**, and @@ -196,6 +201,10 @@ round number, that was this. identity, so `le` series from before the change do not line up with the ones after. A panel spanning the upgrade shows a discontinuity, and any alert threshold tuned against the old artifact values needs re-deriving against real ones. + +The `le` *values* changed once more, separately, when the metrics route began negotiating +OpenMetrics for [exemplars](./tracing.md#trace-exemplars): an integer-looking boundary is now +written `le="1.0"` rather than `le="1"`. ::: ## Operating a layered cache diff --git a/docs/logging.md b/docs/logging.md index 4da288f..8637a8c 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -74,6 +74,11 @@ Loki with the trace id off the span: Both keys are flat, so `| json` yields the labels `trace_id` and `span_id` without a prefix. +The [duration histograms](./tracing.md#trace-exemplars) carry the same two names +as Prometheus exemplars, so a trace reached from a log line and one reached from +a latency spike are the same trace. Exemplars differ in one respect: they are +attached only for sampled traces, for the reason given there. + ### What is correlated, and what is not **Correlated:** cache tier read and promotion failures, PostGIS statement From 729912c000b1084a08841642f0510d98e59ee010 Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:19:05 +0300 Subject: [PATCH 03/12] docs: correct which le boundaries the OpenMetrics switch respells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The claim was wrong in the direction that understates an upgrade break. The client appends ".0" only when the shortest 'g' rendering contains neither "." nor "e", so 2.5 was never affected — and the response-size families are, even though they carry no exemplars, because the format is negotiated once per scrape rather than per family. Both pages now carry the derived table, with the two counterintuitive parts named rather than left to be noticed. The code repo derives the same list in a test, so these pages cannot drift from the bucket sets again. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- docs/layered-cache.md | 6 ++++-- docs/tracing.md | 24 +++++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/layered-cache.md b/docs/layered-cache.md index 26b9640..a0a1b9a 100644 --- a/docs/layered-cache.md +++ b/docs/layered-cache.md @@ -203,8 +203,10 @@ the upgrade shows a discontinuity, and any alert threshold tuned against the old needs re-deriving against real ones. The `le` *values* changed once more, separately, when the metrics route began negotiating -OpenMetrics for [exemplars](./tracing.md#trace-exemplars): an integer-looking boundary is now -written `le="1.0"` rather than `le="1"`. +OpenMetrics for [exemplars](./tracing.md#trace-exemplars): a boundary rendering as a whole number is +now written `le="1.0"` rather than `le="1"`. For these two families that is `1` and `5` on the +duration histogram and every boundary from `1024` to `512000` on the size one — +[the full table](./tracing.md#two-limits) covers the HTTP families too. ::: ## Operating a layered cache diff --git a/docs/tracing.md b/docs/tracing.md index 2e114d3..abbd020 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -254,11 +254,25 @@ whether or not Tempo kept the trace. ### Two limits -**`le` label values changed.** Under OpenMetrics a bucket boundary that would -otherwise look like an integer is written with a trailing `.0`, and a label value -is part of a series' identity — so `le="1"` is now `le="1.0"`. That affects the -1, 2.5 and 5 boundaries of the cache families and the 1, 5 and 10 of the HTTP -one. Anything matching an exact `le` — a recording rule, a panel pinned to one +**`le` label values changed.** Under OpenMetrics a boundary that renders as a +whole number is written with a trailing `.0`, and a label value is part of a +series' identity — so `le="1"` is now `le="1.0"`, which Prometheus sees as a +different series. + +| Family | Respelled boundaries | +|:---|:---| +| `shigola_cache_duration_seconds`, `shigola_cache_tier_duration_seconds` | `1`, `5` | +| `shigola_api_duration_seconds` | `1`, `5`, `10` | +| `shigola_cache_response_size_bytes`, `shigola_cache_tier_response_size_bytes` | `1024`, `5120`, `25600`, `102400`, `256000`, `512000` | +| `shigola_api_response_size_bytes` | `512000` | + +Two things about that table are worth reading twice. `2.5` is **not** in it — it +already contains a `.` — and nor are the megabyte boundaries, which render as +`1.048576e+06` and `5.24288e+06`. And the **response-size** families are in it +even though they carry no exemplars: the format is negotiated once per scrape, +not per family. + +Anything matching an exact `le` — a recording rule, a panel pinned to one bucket — needs checking against the new spelling. It was the price of exemplars being scrapeable at all. From ea989427d842e0a5793b5ed246c772c970b2e986 Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:01:42 +0300 Subject: [PATCH 04/12] docs(tracing): correct what happens to pushed metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page said a push_url deployment "pushes through the classic text format to a Pushgateway, which has no notion of them". The push client actually sends protobuf, which does carry exemplars — so the page was wrong about the mechanism and asserted a conclusion this repo cannot verify. It now says what is checkable: the exemplars go out on the wire, whether they are stored and re-exposed is the Pushgateway's business, Shigola does not test it, and push_url is meant for ephemeral jobs rather than the serving path the section describes. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/gh-pages.yml | 51 +++--- .github/workflows/preview.yaml | 67 ++++++++ docs/tracing.md | 11 +- docusaurus.config.js | 273 +++++++++++++++++---------------- 4 files changed, 241 insertions(+), 161 deletions(-) create mode 100644 .github/workflows/preview.yaml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index d528a17..9ace21c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -2,29 +2,31 @@ name: Deploy docs to GitHub Pages on: push: - branches: [master] + branches: + - master + # Allows a manual run from the Actions tab. workflow_dispatch: -# GITHUB_TOKEN permissions needed to deploy to Pages. +# Required because the deployment action writes to the gh-pages branch. permissions: - contents: read - pages: write - id-token: write + contents: write -# One deployment at a time. In-progress runs are allowed to finish rather than -# being cancelled, so a half-uploaded artifact is never what gets published. +# Prevent multiple production deployments from running at the same time. concurrency: - group: pages + group: gh-pages-production cancel-in-progress: false jobs: - build: - name: Build + deploy: + name: Build and Deploy runs-on: ubuntu-latest + steps: - name: Check out uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 @@ -33,28 +35,21 @@ jobs: cache: npm - name: Install - # ci, not install: build from the committed lockfile so a deploy cannot - # pick up a dependency the build was never verified against. run: npm ci - name: Build - # url and baseUrl are in docusaurus.config.js rather than passed here, - # so a local `npm run build` produces exactly what gets deployed. run: npm run build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + - name: Deploy to GitHub Pages + uses: JamesIves/github-pages-deploy-action@v4 with: - path: ./build + branch: gh-pages + folder: build - deploy: - name: Deploy - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + # PR previews are stored here by rossjrw/pr-preview-action. + # Production deployments must not delete them. + clean-exclude: pr-preview + + # Do not force-push gh-pages because that would destroy + # preview deployments created by PR workflows. + force: false diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml new file mode 100644 index 0000000..2fc82de --- /dev/null +++ b/.github/workflows/preview.yaml @@ -0,0 +1,67 @@ +name: Deploy PR previews + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed + paths: + - 'docs/**' + - 'tutorials/**' + - 'src/**' + - 'static/**' + - 'docusaurus.config.js' + - 'sidebars.js' + - 'sidebarsTutorials.js' + - 'package.json' + - 'package-lock.json' + +permissions: + contents: write + pull-requests: write + +concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + BASE_URL: '/shigola-docs/pr-preview/pr-${{ github.event.number }}/' + +jobs: + deploy-preview: + name: Deploy PR preview + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node + if: github.event.action != 'closed' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install + if: github.event.action != 'closed' + run: npm ci + + - name: Build + if: github.event.action != 'closed' + run: npm run build + + - name: Deploy preview + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: ./build/ + preview-branch: gh-pages + umbrella-dir: pr-preview + action: auto + wait-for-pages-deployment: true + comment: true + qr-code: false diff --git a/docs/tracing.md b/docs/tracing.md index abbd020..7edc054 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -276,9 +276,14 @@ Anything matching an exact `le` — a recording rule, a panel pinned to one bucket — needs checking against the new spelling. It was the price of exemplars being scrapeable at all. -**Pushed metrics carry no exemplars.** A deployment using the observer's -`push_url` pushes through the classic text format to a Pushgateway, which has no -notion of them. Everything above applies to scraped deployments only. +**Pushed metrics take a different path, and an unverified one.** A deployment +using the observer's `push_url` never reaches the exposition format above — the +push client sends protobuf, which *does* carry exemplars. Whether they are then +stored and re-exposed is the Pushgateway's own business, and Shigola does not +test it either way. `push_url` is in any case meant for +[ephemeral jobs](./cache-seeding-and-purging.md) rather than for the serving +path this section is about, so treat everything above as describing a scraped +deployment. ## Spans diff --git a/docusaurus.config.js b/docusaurus.config.js index 2ca94ae..18d7a3e 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -2,150 +2,163 @@ // Documentation for Shigola, a vector tile server. // Source: https://github.com/MapColonies/shigola -import {themes as prismThemes} from 'prism-react-renderer'; +import { themes as prismThemes } from 'prism-react-renderer'; const FORK_REPO = 'https://github.com/MapColonies/shigola'; const DOCS_REPO = 'https://github.com/MapColonies/shigola-docs'; /** @type {import('@docusaurus/types').Config} */ const config = { - title: 'Shigola', - tagline: 'Vector tiles with OGC API - Tiles, tile matrix sets and a layered cache', - favicon: 'images/logo.png', + title: 'Shigola', + tagline: + 'Vector tiles with OGC API - Tiles, tile matrix sets and a layered cache', + favicon: 'images/logo.png', - // GitHub Pages project site. The workflow does not override these, so a local - // `npm run build` produces exactly what is deployed. - url: 'https://mapcolonies.github.io', - baseUrl: '/shigola-docs/', + // GitHub Pages project site. The workflow does not override these, so a local + // `npm run build` produces exactly what is deployed. + url: 'https://mapcolonies.github.io', + baseUrl: process.env.BASE_URL ?? '/shigola-docs/', - organizationName: 'MapColonies', - projectName: 'shigola-docs', + organizationName: 'MapColonies', + projectName: 'shigola-docs', - // A broken internal link should fail the build, not ship. This is what - // replaces Hugo's ref shortcode, which errored on an unresolvable target. - onBrokenLinks: 'throw', - onBrokenAnchors: 'throw', + // A broken internal link should fail the build, not ship. This is what + // replaces Hugo's ref shortcode, which errored on an unresolvable target. + onBrokenLinks: 'throw', + onBrokenAnchors: 'throw', - markdown: { - // `.md` is parsed as CommonMark, `.mdx` as MDX. Without this every `.md` - // file is MDX, and MDX reads `{z}/{x}/{y}` — which these docs are full of — - // as a JSX expression and fails on the undefined identifier. - format: 'detect', - hooks: { - onBrokenMarkdownLinks: 'throw', - }, - }, + markdown: { + // `.md` is parsed as CommonMark, `.mdx` as MDX. Without this every `.md` + // file is MDX, and MDX reads `{z}/{x}/{y}` — which these docs are full of — + // as a JSX expression and fails on the undefined identifier. + format: 'detect', + hooks: { + onBrokenMarkdownLinks: 'throw', + }, + }, - i18n: { - defaultLocale: 'en', - locales: ['en'], - }, + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, - presets: [ - [ - 'classic', - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - path: 'docs', - // Keeps the URLs the Hugo site published: /documentation/. - routeBasePath: 'documentation', - sidebarPath: './sidebars.js', - editUrl: `${DOCS_REPO}/edit/master/`, - }, - blog: false, - theme: { - customCss: './src/css/custom.css', - }, - }), - ], - ], + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + path: 'docs', + // Keeps the URLs the Hugo site published: /documentation/. + routeBasePath: 'documentation', + sidebarPath: './sidebars.js', + editUrl: `${DOCS_REPO}/edit/master/`, + }, + blog: false, + theme: { + customCss: './src/css/custom.css', + }, + }), + ], + ], - plugins: [ - [ - '@docusaurus/plugin-content-docs', - { - id: 'tutorials', - path: 'tutorials', - routeBasePath: 'tutorials', - sidebarPath: './sidebarsTutorials.js', - editUrl: `${DOCS_REPO}/edit/master/`, - }, - ], - ], + plugins: [ + [ + '@docusaurus/plugin-content-docs', + { + id: 'tutorials', + path: 'tutorials', + routeBasePath: 'tutorials', + sidebarPath: './sidebarsTutorials.js', + editUrl: `${DOCS_REPO}/edit/master/`, + }, + ], + ], - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - image: 'images/logo.png', - colorMode: { - respectPrefersColorScheme: true, - }, - navbar: { - title: 'Shigola', - logo: { - alt: 'Shigola', - src: 'images/logo.png', - }, - items: [ - { - type: 'docSidebar', - sidebarId: 'documentation', - position: 'left', - label: 'Documentation', - }, - { - type: 'docSidebar', - docsPluginId: 'tutorials', - sidebarId: 'tutorials', - position: 'left', - label: 'Tutorials', - }, - {to: '/support', label: 'Support', position: 'left'}, - { - to: '/documentation/about', - label: 'About Shigola', - position: 'right', - }, - {to: '/download', label: 'Download', position: 'right'}, - { - href: FORK_REPO, - label: 'GitHub', - position: 'right', - }, - ], - }, - footer: { - style: 'dark', - links: [ - { - title: 'Docs', - items: [ - {label: 'About Shigola', to: '/documentation/about'}, - {label: 'Getting Started', to: '/documentation/getting-started'}, - {label: 'Configuration', to: '/documentation/configuration'}, - {label: 'OGC API - Tiles', to: '/documentation/ogc-api-tiles'}, - ], - }, - { - title: 'Shigola', - items: [ - {label: 'Source', href: FORK_REPO}, - {label: 'Download', to: '/download'}, - {label: 'Support', to: '/support'}, - {label: 'These docs', href: DOCS_REPO}, - ], - }, - ], - copyright: - 'Shigola is maintained by MapColonies and is MIT licensed.', - }, - prism: { - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - additionalLanguages: ['toml', 'bash', 'json', 'sql'], - }, - }), + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + image: 'images/logo.png', + colorMode: { + respectPrefersColorScheme: true, + }, + navbar: { + title: 'Shigola', + logo: { + alt: 'Shigola', + src: 'images/logo.png', + }, + items: [ + { + type: 'docSidebar', + sidebarId: 'documentation', + position: 'left', + label: 'Documentation', + }, + { + type: 'docSidebar', + docsPluginId: 'tutorials', + sidebarId: 'tutorials', + position: 'left', + label: 'Tutorials', + }, + { to: '/support', label: 'Support', position: 'left' }, + { + to: '/documentation/about', + label: 'About Shigola', + position: 'right', + }, + { to: '/download', label: 'Download', position: 'right' }, + { + href: FORK_REPO, + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Docs', + items: [ + { + label: 'About Shigola', + to: '/documentation/about', + }, + { + label: 'Getting Started', + to: '/documentation/getting-started', + }, + { + label: 'Configuration', + to: '/documentation/configuration', + }, + { + label: 'OGC API - Tiles', + to: '/documentation/ogc-api-tiles', + }, + ], + }, + { + title: 'Shigola', + items: [ + { label: 'Source', href: FORK_REPO }, + { label: 'Download', to: '/download' }, + { label: 'Support', to: '/support' }, + { label: 'These docs', href: DOCS_REPO }, + ], + }, + ], + copyright: + 'Shigola is maintained by MapColonies and is MIT licensed.', + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + additionalLanguages: ['toml', 'bash', 'json', 'sql'], + }, + }), }; export default config; From d8a8d5c163a718a7e699762a2118171eff3aabed Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Thu, 10 Sep 2026 11:04:36 +0300 Subject: [PATCH 05/12] revert: take unrelated CI and config changes off this branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ea98942 was committed with `git add -A` and swept up three files that were sitting uncommitted in the working tree and have nothing to do with MAPCO-11496: a new .github/workflows/preview.yaml, a rewrite of .github/workflows/gh-pages.yml, and a wholesale reformat of docusaurus.config.js carrying a baseUrl change. None of it was mine to commit, the commit message described none of it, and it changed the Pages deploy topology inside a docs-wording PR. This restores all three to origin/master. The content is not lost — it is in ea98942, and has been put back in the working tree as the uncommitted work it was. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/gh-pages.yml | 51 +++--- .github/workflows/preview.yaml | 67 -------- docusaurus.config.js | 273 ++++++++++++++++----------------- 3 files changed, 158 insertions(+), 233 deletions(-) delete mode 100644 .github/workflows/preview.yaml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 9ace21c..d528a17 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -2,31 +2,29 @@ name: Deploy docs to GitHub Pages on: push: - branches: - - master - + branches: [master] # Allows a manual run from the Actions tab. workflow_dispatch: -# Required because the deployment action writes to the gh-pages branch. +# GITHUB_TOKEN permissions needed to deploy to Pages. permissions: - contents: write + contents: read + pages: write + id-token: write -# Prevent multiple production deployments from running at the same time. +# One deployment at a time. In-progress runs are allowed to finish rather than +# being cancelled, so a half-uploaded artifact is never what gets published. concurrency: - group: gh-pages-production + group: pages cancel-in-progress: false jobs: - deploy: - name: Build and Deploy + build: + name: Build runs-on: ubuntu-latest - steps: - name: Check out uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 @@ -35,21 +33,28 @@ jobs: cache: npm - name: Install + # ci, not install: build from the committed lockfile so a deploy cannot + # pick up a dependency the build was never verified against. run: npm ci - name: Build + # url and baseUrl are in docusaurus.config.js rather than passed here, + # so a local `npm run build` produces exactly what gets deployed. run: npm run build - - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 with: - branch: gh-pages - folder: build + path: ./build - # PR previews are stored here by rossjrw/pr-preview-action. - # Production deployments must not delete them. - clean-exclude: pr-preview - - # Do not force-push gh-pages because that would destroy - # preview deployments created by PR workflows. - force: false + deploy: + name: Deploy + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml deleted file mode 100644 index 2fc82de..0000000 --- a/.github/workflows/preview.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: Deploy PR previews - -on: - pull_request: - types: - - opened - - reopened - - synchronize - - closed - paths: - - 'docs/**' - - 'tutorials/**' - - 'src/**' - - 'static/**' - - 'docusaurus.config.js' - - 'sidebars.js' - - 'sidebarsTutorials.js' - - 'package.json' - - 'package-lock.json' - -permissions: - contents: write - pull-requests: write - -concurrency: - group: preview-${{ github.event.pull_request.number }} - cancel-in-progress: true - -env: - BASE_URL: '/shigola-docs/pr-preview/pr-${{ github.event.number }}/' - -jobs: - deploy-preview: - name: Deploy PR preview - runs-on: ubuntu-latest - - steps: - - name: Check out - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node - if: github.event.action != 'closed' - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - - name: Install - if: github.event.action != 'closed' - run: npm ci - - - name: Build - if: github.event.action != 'closed' - run: npm run build - - - name: Deploy preview - uses: rossjrw/pr-preview-action@v1 - with: - source-dir: ./build/ - preview-branch: gh-pages - umbrella-dir: pr-preview - action: auto - wait-for-pages-deployment: true - comment: true - qr-code: false diff --git a/docusaurus.config.js b/docusaurus.config.js index 18d7a3e..2ca94ae 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -2,163 +2,150 @@ // Documentation for Shigola, a vector tile server. // Source: https://github.com/MapColonies/shigola -import { themes as prismThemes } from 'prism-react-renderer'; +import {themes as prismThemes} from 'prism-react-renderer'; const FORK_REPO = 'https://github.com/MapColonies/shigola'; const DOCS_REPO = 'https://github.com/MapColonies/shigola-docs'; /** @type {import('@docusaurus/types').Config} */ const config = { - title: 'Shigola', - tagline: - 'Vector tiles with OGC API - Tiles, tile matrix sets and a layered cache', - favicon: 'images/logo.png', + title: 'Shigola', + tagline: 'Vector tiles with OGC API - Tiles, tile matrix sets and a layered cache', + favicon: 'images/logo.png', - // GitHub Pages project site. The workflow does not override these, so a local - // `npm run build` produces exactly what is deployed. - url: 'https://mapcolonies.github.io', - baseUrl: process.env.BASE_URL ?? '/shigola-docs/', + // GitHub Pages project site. The workflow does not override these, so a local + // `npm run build` produces exactly what is deployed. + url: 'https://mapcolonies.github.io', + baseUrl: '/shigola-docs/', - organizationName: 'MapColonies', - projectName: 'shigola-docs', + organizationName: 'MapColonies', + projectName: 'shigola-docs', - // A broken internal link should fail the build, not ship. This is what - // replaces Hugo's ref shortcode, which errored on an unresolvable target. - onBrokenLinks: 'throw', - onBrokenAnchors: 'throw', + // A broken internal link should fail the build, not ship. This is what + // replaces Hugo's ref shortcode, which errored on an unresolvable target. + onBrokenLinks: 'throw', + onBrokenAnchors: 'throw', - markdown: { - // `.md` is parsed as CommonMark, `.mdx` as MDX. Without this every `.md` - // file is MDX, and MDX reads `{z}/{x}/{y}` — which these docs are full of — - // as a JSX expression and fails on the undefined identifier. - format: 'detect', - hooks: { - onBrokenMarkdownLinks: 'throw', - }, - }, + markdown: { + // `.md` is parsed as CommonMark, `.mdx` as MDX. Without this every `.md` + // file is MDX, and MDX reads `{z}/{x}/{y}` — which these docs are full of — + // as a JSX expression and fails on the undefined identifier. + format: 'detect', + hooks: { + onBrokenMarkdownLinks: 'throw', + }, + }, - i18n: { - defaultLocale: 'en', - locales: ['en'], - }, + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, - presets: [ - [ - 'classic', - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - path: 'docs', - // Keeps the URLs the Hugo site published: /documentation/. - routeBasePath: 'documentation', - sidebarPath: './sidebars.js', - editUrl: `${DOCS_REPO}/edit/master/`, - }, - blog: false, - theme: { - customCss: './src/css/custom.css', - }, - }), - ], - ], + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + path: 'docs', + // Keeps the URLs the Hugo site published: /documentation/. + routeBasePath: 'documentation', + sidebarPath: './sidebars.js', + editUrl: `${DOCS_REPO}/edit/master/`, + }, + blog: false, + theme: { + customCss: './src/css/custom.css', + }, + }), + ], + ], - plugins: [ - [ - '@docusaurus/plugin-content-docs', - { - id: 'tutorials', - path: 'tutorials', - routeBasePath: 'tutorials', - sidebarPath: './sidebarsTutorials.js', - editUrl: `${DOCS_REPO}/edit/master/`, - }, - ], - ], + plugins: [ + [ + '@docusaurus/plugin-content-docs', + { + id: 'tutorials', + path: 'tutorials', + routeBasePath: 'tutorials', + sidebarPath: './sidebarsTutorials.js', + editUrl: `${DOCS_REPO}/edit/master/`, + }, + ], + ], - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - image: 'images/logo.png', - colorMode: { - respectPrefersColorScheme: true, - }, - navbar: { - title: 'Shigola', - logo: { - alt: 'Shigola', - src: 'images/logo.png', - }, - items: [ - { - type: 'docSidebar', - sidebarId: 'documentation', - position: 'left', - label: 'Documentation', - }, - { - type: 'docSidebar', - docsPluginId: 'tutorials', - sidebarId: 'tutorials', - position: 'left', - label: 'Tutorials', - }, - { to: '/support', label: 'Support', position: 'left' }, - { - to: '/documentation/about', - label: 'About Shigola', - position: 'right', - }, - { to: '/download', label: 'Download', position: 'right' }, - { - href: FORK_REPO, - label: 'GitHub', - position: 'right', - }, - ], - }, - footer: { - style: 'dark', - links: [ - { - title: 'Docs', - items: [ - { - label: 'About Shigola', - to: '/documentation/about', - }, - { - label: 'Getting Started', - to: '/documentation/getting-started', - }, - { - label: 'Configuration', - to: '/documentation/configuration', - }, - { - label: 'OGC API - Tiles', - to: '/documentation/ogc-api-tiles', - }, - ], - }, - { - title: 'Shigola', - items: [ - { label: 'Source', href: FORK_REPO }, - { label: 'Download', to: '/download' }, - { label: 'Support', to: '/support' }, - { label: 'These docs', href: DOCS_REPO }, - ], - }, - ], - copyright: - 'Shigola is maintained by MapColonies and is MIT licensed.', - }, - prism: { - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - additionalLanguages: ['toml', 'bash', 'json', 'sql'], - }, - }), + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + image: 'images/logo.png', + colorMode: { + respectPrefersColorScheme: true, + }, + navbar: { + title: 'Shigola', + logo: { + alt: 'Shigola', + src: 'images/logo.png', + }, + items: [ + { + type: 'docSidebar', + sidebarId: 'documentation', + position: 'left', + label: 'Documentation', + }, + { + type: 'docSidebar', + docsPluginId: 'tutorials', + sidebarId: 'tutorials', + position: 'left', + label: 'Tutorials', + }, + {to: '/support', label: 'Support', position: 'left'}, + { + to: '/documentation/about', + label: 'About Shigola', + position: 'right', + }, + {to: '/download', label: 'Download', position: 'right'}, + { + href: FORK_REPO, + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Docs', + items: [ + {label: 'About Shigola', to: '/documentation/about'}, + {label: 'Getting Started', to: '/documentation/getting-started'}, + {label: 'Configuration', to: '/documentation/configuration'}, + {label: 'OGC API - Tiles', to: '/documentation/ogc-api-tiles'}, + ], + }, + { + title: 'Shigola', + items: [ + {label: 'Source', href: FORK_REPO}, + {label: 'Download', to: '/download'}, + {label: 'Support', to: '/support'}, + {label: 'These docs', href: DOCS_REPO}, + ], + }, + ], + copyright: + 'Shigola is maintained by MapColonies and is MIT licensed.', + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + additionalLanguages: ['toml', 'bash', 'json', 'sql'], + }, + }), }; export default config; From 1d14eafb0f0e9fa2d6ab02aaf895ad6f1d744efb Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:43:05 +0300 Subject: [PATCH 06/12] docs(layered-cache): point at the le table rather than restating it The section named the respelled boundaries in prose, which made a fifth hand-maintained copy of a list only one place derives. It now says both families are affected and links to the table. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- docs/layered-cache.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/layered-cache.md b/docs/layered-cache.md index a0a1b9a..cdf9957 100644 --- a/docs/layered-cache.md +++ b/docs/layered-cache.md @@ -202,11 +202,10 @@ identity, so `le` series from before the change do not line up with the ones aft the upgrade shows a discontinuity, and any alert threshold tuned against the old artifact values needs re-deriving against real ones. -The `le` *values* changed once more, separately, when the metrics route began negotiating +The `le` *values* then changed once more, separately, when the metrics route began negotiating OpenMetrics for [exemplars](./tracing.md#trace-exemplars): a boundary rendering as a whole number is -now written `le="1.0"` rather than `le="1"`. For these two families that is `1` and `5` on the -duration histogram and every boundary from `1024` to `512000` on the size one — -[the full table](./tracing.md#two-limits) covers the HTTP families too. +now written `le="1.0"` rather than `le="1"`. Both of these families are affected — +[which boundaries exactly](./tracing.md#two-limits). ::: ## Operating a layered cache From 324a4b3d1c890980bd6d7d4ea2bf3393da5779ea Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:55:47 +0300 Subject: [PATCH 07/12] docs(tracing): name what the limits section is about "Two limits" said neither, while layered-cache.md linked to it as "which boundaries exactly". The heading now names the changed le labels and the pushed metrics, and the inbound link follows it. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- docs/layered-cache.md | 2 +- docs/tracing.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/layered-cache.md b/docs/layered-cache.md index cdf9957..d25cc66 100644 --- a/docs/layered-cache.md +++ b/docs/layered-cache.md @@ -205,7 +205,7 @@ needs re-deriving against real ones. The `le` *values* then changed once more, separately, when the metrics route began negotiating OpenMetrics for [exemplars](./tracing.md#trace-exemplars): a boundary rendering as a whole number is now written `le="1.0"` rather than `le="1"`. Both of these families are affected — -[which boundaries exactly](./tracing.md#two-limits). +[which boundaries exactly](./tracing.md#changed-le-labels-and-pushed-metrics). ::: ## Operating a layered cache diff --git a/docs/tracing.md b/docs/tracing.md index 7edc054..4b51fe0 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -252,7 +252,7 @@ the opposite reason: a log line's trace id still groups that request's lines whether or not Tempo kept the trace. ::: -### Two limits +### Changed `le` labels, and pushed metrics **`le` label values changed.** Under OpenMetrics a boundary that renders as a whole number is written with a trailing `.0`, and a label value is part of a From 9fd54dfe94bd796aa79e549c35bb11592aff1735 Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Mon, 14 Sep 2026 10:16:19 +0300 Subject: [PATCH 08/12] docs(tracing): the le respelling reaches quantile labels too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page framed the OpenMetrics break as le-only over Shigola's own families. It belongs to the encoder rather than to histograms, which writes summary quantile labels through the same formatter — so go_gc_duration_seconds, a Go runtime metric Shigola never touches and every Go service publishes, has quantile="0" respelled to "0.0". A dashboard pinned to either is affected and the page did not say so. MAPCO-11496 Co-Authored-By: Claude Opus 5 (1M context) --- docs/tracing.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/tracing.md b/docs/tracing.md index 4b51fe0..81415b3 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -272,9 +272,18 @@ already contains a `.` — and nor are the megabyte boundaries, which render as even though they carry no exemplars: the format is negotiated once per scrape, not per family. -Anything matching an exact `le` — a recording rule, a panel pinned to one -bucket — needs checking against the new spelling. It was the price of exemplars -being scrapeable at all. +:::warning +**It reaches past `le`, and past Shigola's own metrics.** The respelling belongs +to the encoder, not to histograms — it writes summary `quantile` labels through +the same formatter. The Go runtime's `go_gc_duration_seconds` is a summary, so +`quantile="0"` becomes `quantile="0.0"` and `quantile="1"` becomes +`quantile="1.0"`, on a metric Shigola never touches and every Go service +publishes. A dashboard pinned to either is affected. +::: + +Anything matching an exact `le` or `quantile` — a recording rule, a panel pinned +to one bucket — needs checking against the new spelling. It was the price of +exemplars being scrapeable at all. **Pushed metrics take a different path, and an unverified one.** A deployment using the observer's `push_url` never reaches the exposition format above — the From 7877042dc91abe4ca48825d1c7fd9007dd21b62a Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:52:23 +0300 Subject: [PATCH 09/12] docs(tracing): name the two duration histograms that carry no exemplar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page said every duration observation inside a sampled trace carries its trace and span. Two do not: shigola_mvt_provider_sql_query_seconds and shigola_provider_sql_query_seconds are measured inside their own query span and attach nothing, because a provider cannot reach exemplarFrom without importing the Prometheus observer, which the noPrometheusObserver build tag exists to compile out. Their le boundaries move all the same — 1, 5 and 20 — and the respelled table did not list them either, which understated the upgrade break for anyone running the PostGIS provider. Both corrected. Pairs with shigola's tracing/README.md and observability/prometheus/README.md (MAPCO-11496). Co-Authored-By: Claude Opus 5 (1M context) --- docs/tracing.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/tracing.md b/docs/tracing.md index 81415b3..f533cd6 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -192,8 +192,8 @@ first to the second without a search. ### Trace exemplars -Each duration observation made inside a **sampled** trace carries that trace and -span, so a bucket in a Grafana histogram panel shows a dot you can click: +Three families carry an exemplar on every observation made inside a **sampled** +trace, so a bucket in a Grafana histogram panel shows a dot you can click: | Family | The exemplar names | |:---|:---| @@ -201,6 +201,14 @@ span, so a bucket in a Grafana histogram panel shows a dot you can click: | `shigola_cache_tier_duration_seconds` | that tier's own read, write or purge | | `shigola_api_duration_seconds` | the request | +Not every duration histogram is in that table. +`shigola_mvt_provider_sql_query_seconds` and `shigola_provider_sql_query_seconds` +are measured inside their own query span and still carry nothing, because the +provider would have to import the Prometheus observer to attach one — and that +observer is compiled out entirely under the `noPrometheusObserver` build tag. +Their latency is still readable as the duration of the query span itself, in the +trace. + The labels are `trace_id` and `span_id` — the same names the [log records](./logging.md#trace-correlation) carry. @@ -265,12 +273,15 @@ different series. | `shigola_api_duration_seconds` | `1`, `5`, `10` | | `shigola_cache_response_size_bytes`, `shigola_cache_tier_response_size_bytes` | `1024`, `5120`, `25600`, `102400`, `256000`, `512000` | | `shigola_api_response_size_bytes` | `512000` | - -Two things about that table are worth reading twice. `2.5` is **not** in it — it -already contains a `.` — and nor are the megabyte boundaries, which render as -`1.048576e+06` and `5.24288e+06`. And the **response-size** families are in it -even though they carry no exemplars: the format is negotiated once per scrape, -not per family. +| `shigola_mvt_provider_sql_query_seconds`, `shigola_provider_sql_query_seconds` | `1`, `5`, `20` | + +Three things about that table are worth reading twice. `2.5` is **not** in it — +it already contains a `.` — and nor are the megabyte boundaries, which render as +`1.048576e+06` and `5.24288e+06`, nor the provider families' `.1`, which renders +as `0.1`. The **response-size** families are in it even though they carry no +exemplars: the format is negotiated once per scrape, not per family. And so are +the **provider query** families, for the same reason — they carry no exemplars +either, and their `le` labels move regardless. :::warning **It reaches past `le`, and past Shigola's own metrics.** The respelling belongs From 81c224732beb320d2c533ff0f694f4ddea8a4a98 Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:02:32 +0300 Subject: [PATCH 10/12] docs: say the le break applies with tracing off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three write-ups framed the respelling as a consequence of exemplars — one of them literally "when the metrics route began negotiating OpenMetrics for exemplars" — and exemplars are an off-by-default feature. The switch is not conditional on them: the metrics route negotiates OpenMetrics whenever the observer is enabled. So the operator most likely to be caught out is the one running metrics with tracing off, who reads the tracing page's warning and concludes it is not about them. Said plainly on all three pages, and the /metrics row now names the break rather than only the exemplars. Pairs with shigola's observability/prometheus/README.md (MAPCO-11496). Co-Authored-By: Claude Opus 5 (1M context) --- docs/http-endpoints.md | 2 +- docs/layered-cache.md | 8 +++++--- docs/tracing.md | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/http-endpoints.md b/docs/http-endpoints.md index 9c6b792..4ac3565 100644 --- a/docs/http-endpoints.md +++ b/docs/http-endpoints.md @@ -25,7 +25,7 @@ it, apart from `/metrics`. | `/collections/{collectionId}/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}` | A vector tile | | `/tileMatrixSets` | The [tiling schemes](./tile-matrix-sets.md) served | | `/tileMatrixSets/{tileMatrixSetId}` | One scheme's definition | -| `/metrics` | Prometheus metrics, when a Prometheus observer is configured. Answers in OpenMetrics when the scraper asks for it, which is what carries [trace exemplars](./tracing.md#trace-exemplars). Cache metrics are listed under [Layered cache](./layered-cache.md#metrics). | +| `/metrics` | Prometheus metrics, when a Prometheus observer is configured. Answers in OpenMetrics when the scraper asks for it, which is what carries [trace exemplars](./tracing.md#trace-exemplars) — and which [respells some `le` labels](./tracing.md#changed-le-labels-and-pushed-metrics) whether or not tracing is on. Cache metrics are listed under [Layered cache](./layered-cache.md#metrics). | Full documentation on [OGC API - Tiles](./ogc-api-tiles.md), including content negotiation, caching and the conformance classes declared. diff --git a/docs/layered-cache.md b/docs/layered-cache.md index d25cc66..9fffcf2 100644 --- a/docs/layered-cache.md +++ b/docs/layered-cache.md @@ -203,9 +203,11 @@ the upgrade shows a discontinuity, and any alert threshold tuned against the old needs re-deriving against real ones. The `le` *values* then changed once more, separately, when the metrics route began negotiating -OpenMetrics for [exemplars](./tracing.md#trace-exemplars): a boundary rendering as a whole number is -now written `le="1.0"` rather than `le="1"`. Both of these families are affected — -[which boundaries exactly](./tracing.md#changed-le-labels-and-pushed-metrics). +OpenMetrics: a boundary rendering as a whole number is now written `le="1.0"` rather than `le="1"`. +Both of these families are affected — +[which boundaries exactly](./tracing.md#changed-le-labels-and-pushed-metrics). That switch was made +for [trace exemplars](./tracing.md#trace-exemplars), but it is not conditional on them: the metrics +route negotiates OpenMetrics whenever the observer is enabled, tracing on or off. ::: ## Operating a layered cache diff --git a/docs/tracing.md b/docs/tracing.md index f533cd6..d152696 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -267,6 +267,12 @@ whole number is written with a trailing `.0`, and a label value is part of a series' identity — so `le="1"` is now `le="1.0"`, which Prometheus sees as a different series. +This section is on the tracing page because exemplars are why the format was +switched, but **the break is not conditional on tracing**. The metrics route +negotiates OpenMetrics whenever the observer is enabled; running with +`[tracing]` disabled — the default — gets you these renamed series and no +exemplars. + | Family | Respelled boundaries | |:---|:---| | `shigola_cache_duration_seconds`, `shigola_cache_tier_duration_seconds` | `1`, `5` | From 5c3a0c6b7bdc340d9783a9fbc9f1884700b80d77 Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:14:18 +0300 Subject: [PATCH 11/12] docs(tracing): drop the provider family that publishes no series shigola_provider_sql_query_seconds is registered but never observed, so it emits no family at all. This page listed it among the respelled le series and said its latency was readable as its query span's duration; neither is true, and both arrived on this branch two commits ago. Its sibling shigola_mvt_provider_sql_query_seconds is the one that is really observed, and its row is unchanged. Pairs with shigola's observability/prometheus/README.md (MAPCO-11496). Co-Authored-By: Claude Opus 5 (1M context) --- docs/tracing.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/tracing.md b/docs/tracing.md index d152696..4ee40d9 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -202,12 +202,11 @@ trace, so a bucket in a Grafana histogram panel shows a dot you can click: | `shigola_api_duration_seconds` | the request | Not every duration histogram is in that table. -`shigola_mvt_provider_sql_query_seconds` and `shigola_provider_sql_query_seconds` -are measured inside their own query span and still carry nothing, because the -provider would have to import the Prometheus observer to attach one — and that -observer is compiled out entirely under the `noPrometheusObserver` build tag. -Their latency is still readable as the duration of the query span itself, in the -trace. +`shigola_mvt_provider_sql_query_seconds` is measured inside its own query span +and still carries nothing, because the provider would have to import the +Prometheus observer to attach one — and that observer is compiled out entirely +under the `noPrometheusObserver` build tag. Its latency is still readable as the +duration of the query span itself, in the trace. The labels are `trace_id` and `span_id` — the same names the [log records](./logging.md#trace-correlation) carry. @@ -279,7 +278,7 @@ exemplars. | `shigola_api_duration_seconds` | `1`, `5`, `10` | | `shigola_cache_response_size_bytes`, `shigola_cache_tier_response_size_bytes` | `1024`, `5120`, `25600`, `102400`, `256000`, `512000` | | `shigola_api_response_size_bytes` | `512000` | -| `shigola_mvt_provider_sql_query_seconds`, `shigola_provider_sql_query_seconds` | `1`, `5`, `20` | +| `shigola_mvt_provider_sql_query_seconds` | `1`, `5`, `20` | Three things about that table are worth reading twice. `2.5` is **not** in it — it already contains a `.` — and nor are the megabyte boundaries, which render as From 893d728a02a19bc722d6cb107ec6835c1f54a534 Mon Sep 17 00:00:00 2001 From: Niv Greenstein <88280771+NivGreenstein@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:21:01 +0300 Subject: [PATCH 12/12] docs(tracing): make the provider-family sentence singular The previous commit cut shigola_provider_sql_query_seconds from the respelled table but left the paragraph beneath it plural, still claiming the provider query families' le labels move. Only one of them has le labels at all. Pairs with shigola's observability/prometheus/README.md (MAPCO-11496). Co-Authored-By: Claude Opus 5 (1M context) --- docs/tracing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tracing.md b/docs/tracing.md index 4ee40d9..b06e23d 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -282,11 +282,11 @@ exemplars. Three things about that table are worth reading twice. `2.5` is **not** in it — it already contains a `.` — and nor are the megabyte boundaries, which render as -`1.048576e+06` and `5.24288e+06`, nor the provider families' `.1`, which renders +`1.048576e+06` and `5.24288e+06`, nor the provider family's `.1`, which renders as `0.1`. The **response-size** families are in it even though they carry no -exemplars: the format is negotiated once per scrape, not per family. And so are -the **provider query** families, for the same reason — they carry no exemplars -either, and their `le` labels move regardless. +exemplars: the format is negotiated once per scrape, not per family. And so is +`shigola_mvt_provider_sql_query_seconds`, for the same reason — it carries no +exemplar either, and its `le` labels move regardless. :::warning **It reaches past `le`, and past Shigola's own metrics.** The respelling belongs