|
2 | 2 |
|
3 | 3 | Runbook for operating the service in production. Covers how to tell when something is wrong, what the service does automatically, and when a human needs to step in. |
4 | 4 |
|
5 | | -## Health signal |
| 5 | +## Health and metrics |
6 | 6 |
|
7 | | -The service has no HTTP health endpoint. What you can observe externally: |
| 7 | +The service exposes a small HTTP server on `HTTP_PORT` (default `8080`) with three endpoints: |
| 8 | + |
| 9 | +| Path | Purpose | |
| 10 | +|---|---| |
| 11 | +| `/healthz` | Liveness probe. Always returns 200 while the process is alive. Deliberately cheap — a stuck event loop can still return 200, which is the point of separating it from readiness. | |
| 12 | +| `/readyz` | Readiness probe. Returns 200 only when RabbitMQ is connected and the process is not shutting down. Flips to 503 as soon as SIGTERM is received so load balancers and `PodDisruptionBudget` logic stop routing to the pod before the drain begins. | |
| 13 | +| `/metrics` | Prometheus text exposition. Scrape interval of 15–30 s is plenty; the metrics below are counters and gauges, not high-cardinality. | |
| 14 | + |
| 15 | +In Kubernetes, point `livenessProbe` at `/healthz` and `readinessProbe` at `/readyz`. The default Helm chart values do this for you. |
| 16 | + |
| 17 | +### Metrics worth alerting on |
| 18 | + |
| 19 | +| Metric | Type | What it tells you | |
| 20 | +|---|---|---| |
| 21 | +| `nextcloud_migration_active` | gauge | How many migrations are in-flight right now. Sustained at the concurrency cap means you're queue-bound. | |
| 22 | +| `nextcloud_migration_started_total` | counter | Migration arrival rate. Alert on a drop to zero while the RabbitMQ queue has items — indicates the consumer has stopped picking work up. | |
| 23 | +| `nextcloud_migration_finished_total{outcome}` | counter | `outcome="completed"` vs `outcome="failed"`. Alert on a sustained failure ratio above your tolerance (e.g. > 10%). | |
| 24 | +| `nextcloud_migration_files_total{outcome}` | counter | Per-file outcomes (`transferred`, `skipped`, `failed`). `skipped` is normal during resumes; `failed` is not. | |
| 25 | +| `nextcloud_migration_file_transfer_duration_seconds` | histogram | Per-file transfer latency. A shift in the upper buckets is an early signal of Stack or Nextcloud slowdown. | |
| 26 | +| `nextcloud_migration_cloudery_token_total{outcome}` | counter | `outcome="success"` vs `"failed"`. A spike in failed tokens points at Cloudery, not at this service. | |
| 27 | +| `nextcloud_migration_rabbitmq_connected` | gauge | `1` when connected, `0` otherwise. Complements the `/readyz` probe for dashboards. | |
| 28 | + |
| 29 | +Default Node.js runtime metrics (event loop lag, heap, GC) are included automatically by `prom-client`. |
| 30 | + |
| 31 | +## Log-based signals |
| 32 | + |
| 33 | +Even with metrics, the event log remains the source of truth for per-migration diagnosis: |
8 | 34 |
|
9 | 35 | - **Process uptime.** Log lines with `event: service.starting`, `service.shutting_down`, `service.stopped` delimit lifetimes. |
10 | | -- **RabbitMQ connection.** An `event: rabbitmq.connected` on startup means the broker is reachable. Silent thereafter; connection drops surface as errors from the underlying library. |
11 | | -- **Consumption rate.** Count `event: consumer.message_received` over time. A healthy instance emits one per incoming migration request. |
| 36 | +- **RabbitMQ connection.** An `event: rabbitmq.connected` on startup means the broker is reachable. |
| 37 | +- **Consumption rate.** Count `event: consumer.message_received` over time. |
12 | 38 |
|
13 | | -If the process is alive and RabbitMQ is drained but the queue keeps growing, look at the concurrency cap next. |
| 39 | +If the process is alive, `/readyz` is green, but the queue keeps growing, look at the concurrency cap next. |
14 | 40 |
|
15 | 41 | ## Events worth alerting on |
16 | 42 |
|
@@ -74,8 +100,10 @@ See [Configuration](configuration.md) for the full list. |
74 | 100 |
|
75 | 101 | On `SIGTERM` or `SIGINT`: |
76 | 102 |
|
77 | | -1. RabbitMQ subscription closes immediately — no new messages are accepted. |
78 | | -2. The process waits up to 60 seconds for in-flight migrations to finish naturally. |
79 | | -3. If the deadline passes, the process exits anyway. Anything still running becomes a stale-running zombie and is reclaimed by the next consumer. |
| 103 | +1. `/readyz` flips to 503 immediately so load balancers stop routing to the pod. |
| 104 | +2. RabbitMQ subscription closes — no new messages are accepted. |
| 105 | +3. The process waits up to 60 seconds for in-flight migrations to finish naturally. |
| 106 | +4. The ops HTTP server closes. |
| 107 | +5. If the drain deadline passed, the process exits anyway. Anything still running becomes a stale-running zombie and is reclaimed by the next consumer. |
80 | 108 |
|
81 | 109 | This matters for rolling deployments: if you expect migrations to routinely run longer than 60 seconds (most do), you'll see `service.stopped` with `drained: false` on every rollout. That's not a regression — it's the heartbeat recovery doing its job. |
0 commit comments