A sleeping Render web service cannot wake itself from code running inside that same service. If the process is asleep, timers and in-process jobs are also asleep.
Working automatic options:
- Keep the service always-on by using a paid Render instance.
- Ping it from an external scheduler (GitHub Actions, Render Cron Job, UptimeRobot, etc.).
- Run a separate worker/cron process that pings your web service.
This API exposes a lightweight health endpoint for keepalive probes:
GET /healthzGET /health(redirects to/healthz)
Use this endpoint for all keepalive traffic.
Workflow file: .github/workflows/render-keepalive.yml
Setup:
- Open
Settings -> Secrets and variables -> Actionsin GitHub. - Add secret
RENDER_BACKEND_URL. - Set it to either:
https://your-service-name.onrender.com- or
https://your-service-name.onrender.com/healthz
The workflow runs every 5 minutes and pings /healthz.
A keepalive worker script is available at scripts/keepalive-worker.js.
Run locally:
KEEPALIVE_URL=https://your-service-name.onrender.com npm run keepalive:workerEnvironment variables:
KEEPALIVE_URL(required)KEEPALIVE_INTERVAL_MS(optional, default240000)KEEPALIVE_TIMEOUT_MS(optional, default10000)
Recommended on Render:
- Create a separate Background Worker or Cron Job service.
- Point it to this same repo.
- Start command:
npm run keepalive:worker - Set env
KEEPALIVE_URL=https://your-service-name.onrender.com
This keeps the web service warm more reliably than in-process timers.
The trained ranking model runs in a separate Python service
(music-app-backend/ml-service), deployed as its own Render web service. See
render.yaml for the blueprint that wires the two together, and
music-app-backend/README.md for training and endpoint details.
| Variable | Default | Purpose |
|---|---|---|
ML_SERVICE_URL |
(empty) | Base URL of the ML service. Empty disables the integration — the gateway then ranks with local heuristics only. A bare host:port is accepted: render.yaml wires the two services over Render's private network, and blueprint fromService properties never include a scheme. Public DNS names get https://, private names and IPs get http://. |
ML_SERVICE_API_KEY |
(empty) | Shared secret, sent as X-API-Key. Must match the ML service. |
ML_ENABLED |
true |
Kill switch. Set false to bypass the ML service without clearing the URL. |
ML_TIMEOUT_MS |
800 |
Per-request budget for search reranking. |
ML_RECOMMENDATION_TIMEOUT_MS |
2000 |
Per-request budget for recommendations, radio and next-song. |
The ML service is treated as strictly optional. If it is disabled, asleep, unreachable, or slower than its budget, the request is not retried and the user is not made to wait — the gateway ranks with its own heuristics, which is exactly what it did before the integration existed.
A circuit breaker opens after 3 consecutive failures and stays open for 30s, so a spun-down ML instance costs one timeout rather than one per request. Scores are cached for 60s per user/result-set.
Because Render free instances spin down, expect the first request after an idle
period to fall back while the ML service wakes. Keep it warm the same way the
gateway is kept warm (see the keepalive section above), pointed at the ML
service's /health.
curl https://your-service.onrender.com/healthz/mlReports whether the integration is enabled, whether the service is reachable,
circuit state, call/hit/failure counters, and the loaded model's training
metrics. It is deliberately not part of /healthz, which must stay a local
zero-IO check for keepalive probes.