# Ошибки и rate limits ## HTTP status coды Ordinis следует стандартам REST с расширениями: | Status | Используется для | Можно retry? | |---|---|---| | `200 OK` | GET / PUT успешен | — | | `201 Created` | POST создан новый ресурс | — | | `202 Accepted` | Async операция принята | — | | `204 No Content` | DELETE / close успешен | — | | `400 Bad Request` | Malformed JSON / missing required fields | Нет — fix payload | | `401 Unauthorized` | Token expired / invalid / missing | Refresh token и retry | | `403 Forbidden` | Token валиден, но scope не позволяет | Нет — request higher role | | `404 Not Found` | Resource не существует / закрыт / out of scope | Зависит от context | | `409 Conflict` | Optimistic lock fail / cascade block | Refetch + retry | | `422 Unprocessable Entity` | Validation errors (schema / x-references) | Нет — fix data | | `429 Too Many Requests` | Rate limit hit | Wait + retry с backoff | | `500 Internal Server Error` | Server bug | Retry с exponential backoff | | `502 Bad Gateway` | Upstream (config-server, Vault) недоступен | Retry | | `503 Service Unavailable` | Postgres недоступен / restart | Retry с long backoff | | `504 Gateway Timeout` | Запрос > timeout (typically 30s) | Retry | ## Error response format Все 4xx/5xx с body имеют единый shape: ```json { "timestamp": "2026-05-07T15:23:45.123+03:00", "status": 422, "error": "Validation Error", "errors": [ { "path": "/data/satelliteTypeCode", "code": "x_references_target_not_found", "message": "Referenced record not found in 'satellite_type' for code='UNKNOWN'" }, { "path": "/data/launchDate", "code": "schema_format_invalid", "message": "Expected ISO 8601 date, got 'tomorrow'" } ], "traceId": "abc123-def456-789" } ``` | Поле | Описание | |---|---| | `timestamp` | Server time когда ошибка возникла. | | `status` | HTTP status code. | | `error` | Категория (`Validation Error`, `Authorization Error` и т.д.). | | `errors` | Array of detail errors. Может быть один или несколько. | | `errors[].path` | JSON Pointer к полю с ошибкой (например `/data/launchDate`). | | `errors[].code` | Machine-readable код для programmatic handling. | | `errors[].message` | Human-readable описание (русский для admin UI, английский для consumers). | | `traceId` | Tempo trace ID — приложи при эскалации в Ordinis team. | ## Известные error codes ### Validation (422) | Code | Описание | Fix | |---|---|---| | `schema_required_missing` | Required field пропущен | Добавить поле | | `schema_format_invalid` | Wrong format (date / email / etc) | Fix format | | `schema_type_mismatch` | Wrong JSON type (string vs number) | Fix type | | `x_references_dict_not_found` | Referenced dictionary не существует | Schema bug — escalate | | `x_references_target_not_found` | Referenced record не активен / out of scope | Use existing key либо upgrade scope | | `x_references_non_string` | v1 поддерживает только string refs | Fix schema | | `x_references_malformed` | Bad x-references syntax | Fix schema | | `x_localized_default_missing` | i18n field без `default_locale` | Add default locale | | `geometry_invalid_wkt` | Невалидный WKT для geometry | Fix WKT | | `geometry_invalid_lat_lon` | lat/lon вне диапазона | Use valid coords | ### Authorization (401/403) | Code | Описание | |---|---| | `auth_token_missing` | No `Authorization` header | | `auth_token_invalid` | Token не verified (signature, issuer) | | `auth_token_expired` | Token expired | | `auth_scope_insufficient` | Scope не покрывает запрашиваемый словарь | ### Conflict (409) | Code | Описание | Fix | |---|---|---| | `optimistic_lock_failed` | Запись изменилась между read и write | Refetch + retry | | `record_already_closed` | close() на уже закрытой записи | No-op либо ignore | | `bulk_partial_failure` | Bulk операция: часть успешна, часть нет | Check `errors` array в response | ### Server (5xx) | Code | Описание | |---|---| | `internal_unexpected` | Server bug — escalate с traceId | | `db_unavailable` | Postgres недоступен | | `vault_unavailable` | Vault sealed либо unreachable | | `kafka_publish_failed` | Outbox не смог publish (но запись в БД OK — eventually consistent) | ## Rate limits Сегодня (v1.2.1) Ordinis **не имеет explicit rate limits**. Но есть soft limits через resource constraints: | Resource | Limit | |---|---| | Postgres connections | 200 (shared между read-api + writer + projection-writer) | | Read-api app pods | 2× в проде, 1× CPU + 512MB memory | | Writer pods | 2× в проде | | Concurrent JWT verifications | bounded heap (~5k/sec практический максимум) | При превышении (типично DDoS-like patterns): - HTTP `429 Too Many Requests` после ingress rate limit (ingress nginx default). - HTTP `503 Service Unavailable` при exhausting connection pool. - `5xx error rate spike` → alert `OrdinisReadApiErrorBudgetBurnFast`. ### Recommended client-side limits Чтобы не triggers backend limits: | Сценарий | Recommended rate | |---|---| | Cold load на startup | `≤ 100 req/sec` parallel, then cache | | Steady state read | `≤ 50 req/sec` per consumer | | Bulk import / migration | Talk to Ordinis team первым | Внутренний `RateLimiter` (Caffeine `Bucket4j` — TBD) — в roadmap для v2 если наблюдаются abuse patterns. ## Retry strategy ```python import time, random from typing import Callable def retry_with_backoff( fn: Callable, max_attempts: int = 5, base: float = 0.5 ) -> any: for attempt in range(max_attempts): try: return fn() except (HTTPError, TimeoutError) as e: if attempt == max_attempts - 1: raise if e.status_code in (400, 401, 403, 404, 422): raise # don't retry permanent errors sleep = base * (2 ** attempt) + random.uniform(0, 0.5) time.sleep(sleep) ``` ## Tracing Каждый response имеет `X-Trace-Id` header и `traceId` field в error body. Ordinis эмитит OpenTelemetry traces в Tempo (`tempo.monitoring:4317`). При эскалации Ordinis team: 1. Скопируй `traceId` из response либо логов. 2. Создай issue с воспроизведением. 3. Ordinis team query'ит Tempo: `traceId="abc123-..."`. 4. Trace покажет full path: ingress → read-api → postgres → response. ## Дальше - [Best practices](best-practices.md) — как писать robust integration code - [Авторизация](auth.md) — как избежать 401/403