Add your description here
Project description
FDSNWS-Availability
A Flask implementation of the FDSN Availability web service 1.0. It reports the time spans for which time-series data exist, served from a WFCatalog MongoDB.
It runs as three Docker containers: the API (Flask + gunicorn, port 9001), a Redis cache, and a cacher that keeps the restriction inventory and the availability view up to date on a built-in daily schedule.
Upgrading from v1.0.x? See Upgrading from v1.0.x for the exact
config.pychanges — it's a quick, self-contained checklist.
Deployment
First, get and configure the repo (needed either way):
git clone https://github.com/EIDA/ws-availability.git
cd ws-availability
cp config.py.sample config.py # edit MongoDB creds, FDSNWS_STATION_URL, SENTRY_ENVIRONMENT
Then pick one of:
Option A — Build locally
Builds the images on your host. No registry access needed.
docker-compose up -d --build
Option B — Pull pre-built images
Each tagged release publishes images to GHCR, so you can skip the build. Replace <version> with a release tag (e.g. 1.1.0, or 1.1 for the latest 1.1.x):
# docker-compose.override.yml
services:
api:
image: ghcr.io/eida/ws-availability/api:<version>
cacher:
image: ghcr.io/eida/ws-availability/cacher:<version>
docker-compose pull
docker-compose up -d
Pre-built images exist only for tagged releases. To build from an untagged branch instead, use Option A (build locally).
Either way, three containers come up. Check it:
curl "127.0.0.1:9001/version" # -> 1.1.0
curl "127.0.0.1:9001/extent?net=NA&start=2023-02-01"
For a node that already has a populated WFCatalog, that's the whole install. A brand-new database also needs the one-time database setup. Requires MongoDB ≥ 4.2.
Upgrading from v1.0.x
Upgrading reuses the same containers and the same config.py. The only manual step is making sure config.py has the keys the new version expects, then rebuilding.
What changed for operators:
config.pyis now the only place to set MongoDB, FDSNWS-Station and Sentry settings.docker-compose.ymlno longer passes them to the container, so your edits inconfig.pyactually take effect.
-
Get the new code. Your
config.pyis gitignored, so this won't touch it:git fetch && git checkout v1.1.0
-
Add any missing
config.pykeys.MONGODB_*,CACHE_*andFDSNWS_STATION_URLare unchanged — keep your values. What to add depends on the version you're coming from (add the lines inside thetry:block, next to the otheros.environ.get(...)lines):-
From v1.0.5 or v1.0.4 — add one line:
SENTRY_ENVIRONMENT = "yournode_production"
-
From v1.0.3 or earlier (predates Sentry) — add all three:
SENTRY_DSN = os.environ.get("SENTRY_DSN", "") # your Sentry DSN, or "" to disable SENTRY_TRACES_SAMPLE_RATE = float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "1.0")) SENTRY_ENVIRONMENT = "yournode_production"
SENTRY_ENVIRONMENTis required and must be unique per node (e.g.noa_production,resif_production) so your events are distinguishable in Sentry. Not sure what's missing? Diff against the sample — see Troubleshooting. -
-
Rebuild and restart:
docker-compose up -d --build # or: docker-compose pull && docker-compose up -d
-
Remove the old host cron, if you had one triggering
views/main.js— it's now redundant, replaced by the built-in scheduler:crontab -l | grep -v "ws-availability.*views.*main.js" | crontab -
Then re-run the curl checks above; /version should report 1.1.0.
Endpoints
API on port 9001. /query (time spans per channel) and /extent (one row per channel) accept GET and POST. Also /version, /application.wadl, and / (landing page).
curl "127.0.0.1:9001/extent?net=NA&start=2023-02-01"
#Network Station Location Channel Quality SampleRate Earliest Latest Updated TimeSpans Restriction
NA SABA BHZ D 40.0 2023-02-01T00:00:00.000000Z 2023-02-14T00:00:00.000000Z 2023-02-14T07:41:41Z 1 OPEN
Configuration
Everything lives in config.py (copied from config.py.sample, gitignored so upgrades never touch it). Set these in the RUNMODE == "production" block:
| Key | Default | Description |
|---|---|---|
MONGODB_HOST |
host.docker.internal |
WFCatalog MongoDB host. |
MONGODB_PORT |
27017 |
MongoDB port. |
MONGODB_USR / MONGODB_PWD |
empty | MongoDB credentials (leave empty if no auth). |
MONGODB_NAME |
wfrepo |
Database name; also used as authSource unless MONGODB_AUTH_SOURCE is set. |
MONGODB_AUTH_SOURCE |
None |
Optional. Mongo auth database when it differs from MONGODB_NAME (e.g. admin). Falls back to MONGODB_NAME when unset. |
FDSNWS_STATION_URL |
https://orfeus-eu.org/fdsnws/station/1/query |
FDSNWS-Station endpoint to harvest restriction info from. |
CACHE_HOST |
cache |
Redis host. |
CACHE_PORT |
6379 |
Redis port. |
CACHE_INVENTORY_KEY |
inventory |
Redis key for the restriction inventory. |
CACHE_INVENTORY_PERIOD |
0 |
Inventory cache TTL in seconds; 0 = never expire. |
CACHE_RESP_PERIOD |
1200 |
Response cache TTL in seconds. |
SENTRY_DSN |
empty | Sentry DSN; empty disables Sentry. |
SENTRY_TRACES_SAMPLE_RATE |
1.0 |
Fraction of requests traced, 0.0–1.0. |
SENTRY_ENVIRONMENT |
{{node}}_production |
Unique per-node tag (e.g. noa_production) so Sentry can tell deployments apart. Must be changed from the placeholder. |
GUNICORN_WORKERS |
1 |
Number of gunicorn worker processes. Raise (2–3, or (2 × CPU cores) + 1) if you have CPU/RAM headroom. Read by gunicorn.conf.py at container start. |
What runs daily
The cacher runs a built-in scheduler — no host cron needed:
- 03:00 UTC — refresh the restriction inventory from FDSNWS-Station into Redis.
- 06:00 UTC — update the
availabilityview from the last 4 days of WFCatalog data. - On startup — both run once, so a restart leaves recent data fresh.
This covers only the recent window. To repair an older date (e.g. after a backfill), see Back-processing.
Tuning (optional)
- Workers — set
GUNICORN_WORKERSinconfig.py(default1).gunicorn.conf.pyreads it at startup. Raise if you have CPU/RAM headroom. - Row/stream caps —
MAX_DATA_ROWS(default 2.5M) andMAX_STREAMS(default 2000) env vars guard against oversized requests (HTTP 413).
Parallel fan-out
By default, each request is answered by a single MongoDB cursor. The availability collection holds one document per channel-per-day, so a long time range means many documents fetched in sequential round-trips — most of the time is spent waiting on the database, one batch after another.
Fan-out splits the request's time range into day-aligned windows and runs them as concurrent cursors, then merges the pieces back together. The waiting overlaps instead of stacking up, so a multi-month query finishes noticeably faster. Because each window is a separate day range, the slices never overlap and the merged result is byte-identical to the single-cursor answer — only the speed differs.
It is off by default, applies to both /query and /extent (they share the same fetch layer), and only engages when a request's time range is at least FANOUT_MIN_DAYS — shorter requests stay single-cursor because the thread overhead wouldn't pay off. Controlled by these environment variables:
| Variable | Default | Effect |
|---|---|---|
FANOUT_ENABLED |
false |
Master switch. When false, behaves exactly like the single-cursor path. |
FANOUT_MIN_DAYS |
7 |
Minimum request range, in days, before fan-out engages. |
FANOUT_WINDOW_DAYS |
30 |
Size of each window. A 90-day query becomes ~3 windows. |
FANOUT_MAX_WORKERS |
4 |
Max windows run at once — also the number of MongoDB connections a fan-out request uses. |
Best for long, narrow queries (months/years of a few channels). Before enabling on a busy node, check that workers × FANOUT_MAX_WORKERS stays within your MongoDB connection budget.
Development
Requires Python ≥ 3.13 and uv.
cp config.py.sample config.py # edit for RUNMODE=test
uv sync
docker run -p 6379:6379 -d redis:7.0-alpine # Redis is required
uv run python cache.py # build the restriction inventory
RUNMODE=test uv run gunicorn --bind 0.0.0.0:9001 start:app
Tests: uv run pytest tests/
First-time database setup
Skip this if you already run ws-availability — the view and index already exist.
For a brand-new WFCatalog database, build the materialized view once:
# Build the availability view (adjust daysBack to how far back you want)
mongosh -u USER -p PASSWORD --authenticationDatabase wfrepo --eval "daysBack=365" views/main.js
The compound index { net: 1, sta: 1, loc: 1, cha: 1, ts: 1, te: 1 } is created automatically by the API at startup (built in the background). If queries feel slow right after a brand-new install, give it a moment to finish.
After the initial build, the cacher keeps the view current automatically (see What runs daily) — no host cron is needed (earlier versions required one; it has been replaced by the built-in scheduler).
Back-processing (historical / scoped rebuild)
The daily scheduler only refreshes a rolling recent window (the last 4 days). It therefore cannot repair a historical gap: if WFCatalog is (re)populated for an old date — e.g. after a backfill, or a data correction surfaced by an EIDA consistency report — restarting the cacher does not rebuild that date, so /query and /extent keep reporting "no data" even though dataselect / the SDS archive serve it. You must reprocess that range explicitly.
Prerequisite: back-processing only re-derives the view from WFCatalog (
daily_streams/c_segments). It does not scan the SDS archive. If WFCatalog itself is missing the range, refresh WFCatalog for those dates first, otherwise the rebuild completes cleanly but writes nothing.
Preferred — the avail-rebuild CLI (runs inside the cacher, so it uses the container's own MongoDB credentials; supports full NSLC):
# A network/station over a range (all of 2008 -> end is the day boundary, 2009-01-01)
docker exec fdsnws-availability-cacher \
avail-rebuild --net IV --sta ABC --start 2008-01-01 --end 2009-01-01
# Channel-precise (e.g. just HHZ; --loc=-- means the empty location.
# Use the attached '=' form: a bare '--' is read by the shell/argparse as end-of-options.)
docker exec fdsnws-availability-cacher \
avail-rebuild --net IV --sta ABC --loc=-- --cha HHZ --start 2008-01-01 --end 2009-01-01
# Whole range, all streams
docker exec fdsnws-availability-cacher avail-rebuild --start 2023-01-01 --end 2023-02-01
--net/--sta/--loc/--cha are comma-separated exact-match lists; omit any to leave it unconstrained. The rebuild is idempotent ($merge whenMatched:"replace") — safe to re-run. After it finishes, flush Redis or wait out CACHE_RESP_PERIOD before re-checking, in case an empty response for that query is still cached. (If the console script isn't on PATH for some reason, docker exec fdsnws-availability-cacher python -m apps.cli … is equivalent.)
Fallback — views/main.js via mongosh (host-side; net+sta+date only, no loc/cha; needs a repo checkout and DB creds on the host):
mongosh -u USER -p PASSWORD --authenticationDatabase wfrepo \
--eval "networks='NL'; stations='HGN'; start='2022-12-01'; end='2023-01-31'" views/main.js
Troubleshooting
If the service isn't working — often right after an upgrade — it's usually a configuration problem:
-
Check the logs for runtime errors or connection failures:
docker logs fdsnws-availability-api docker logs fdsnws-availability-cacher
-
Verify
config.pyhas every field the current version expects. New versions add keys; list what yours is missing versus the sample:diff <(grep -oE '^[[:space:]]*[A-Z_]+ =' config.py | tr -d ' =' | sort -u) \ <(grep -oE '^[[:space:]]*[A-Z_]+ =' config.py.sample | tr -d ' =' | sort -u)
Lines prefixed
>are keys present in the sample but missing from yourconfig.py— add them. -
Check database access — confirm the MongoDB and Redis connection parameters in
config.pyare correct and that both services are reachable from the containers.
References
Forked from gitlab.com/resif/ws-availability — thanks to our colleagues at RESIF for sharing their FDSNWS-Availability implementation. 💐
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ws_availability-1.1.1.tar.gz.
File metadata
- Download URL: ws_availability-1.1.1.tar.gz
- Upload date:
- Size: 74.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
184e0c89c965f27888daaa66c08d1153d63249b228b1543d63d970136a967dfc
|
|
| MD5 |
cccddcef7682ccafc6cf47f9c90b5eb7
|
|
| BLAKE2b-256 |
f3f06a39b4e007c2405ad9c203b1f5d61b13908d8464d6e3a81f87c599f07bb5
|
Provenance
The following attestation bundles were made for ws_availability-1.1.1.tar.gz:
Publisher:
publish.yml on EIDA/ws-availability
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ws_availability-1.1.1.tar.gz -
Subject digest:
184e0c89c965f27888daaa66c08d1153d63249b228b1543d63d970136a967dfc - Sigstore transparency entry: 2025345736
- Sigstore integration time:
-
Permalink:
EIDA/ws-availability@b0a2cd1facc44cac8e9a10406a52af3d7723bc59 -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/EIDA
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b0a2cd1facc44cac8e9a10406a52af3d7723bc59 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ws_availability-1.1.1-py3-none-any.whl.
File metadata
- Download URL: ws_availability-1.1.1-py3-none-any.whl
- Upload date:
- Size: 51.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a05b2a17851599cdf3712f3faf491aba257afdb15d6a9ce9d110f598515f61b
|
|
| MD5 |
86ba73ee35b67e45357c9839428daa35
|
|
| BLAKE2b-256 |
788a4dad2364f9ec1b6bca633e2fdf917579a5a364bf403389a478893ec2d9e7
|
Provenance
The following attestation bundles were made for ws_availability-1.1.1-py3-none-any.whl:
Publisher:
publish.yml on EIDA/ws-availability
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ws_availability-1.1.1-py3-none-any.whl -
Subject digest:
7a05b2a17851599cdf3712f3faf491aba257afdb15d6a9ce9d110f598515f61b - Sigstore transparency entry: 2025345814
- Sigstore integration time:
-
Permalink:
EIDA/ws-availability@b0a2cd1facc44cac8e9a10406a52af3d7723bc59 -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/EIDA
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b0a2cd1facc44cac8e9a10406a52af3d7723bc59 -
Trigger Event:
push
-
Statement type: