Files
Zimin A.N. bcfb07f547 docs: split integrator guide (Diplodoc) vs internal docs + add build-docs CI
Чёткое разделение audiences:
- docs/ — consumer-facing only, Diplodoc-built. Public contract для
  интеграторов (Альтум, BI, third-party).
- docs-internal/ — operator runbooks, status snapshots, legacy tech pages.
  НЕ в Diplodoc build, НЕ публикуются.

Изменения:
- Move docs/{ops,status,tech} → docs-internal/{ops,status,tech}
- docs/toc.yaml: убраны секции Operations, Архитектура (legacy), Status
- docs/index.md: убран tab "оператор / on-call", focus на integrators
- docs/README.md: rewritten — scope только integrator guide, editorial
  guidelines (no leak internal architecture, stable API contract)
- .yfm: убран явный ignore status/* (теперь не нужно — папка переехала)
- docs-internal/README.md: index для внутренней документации с rationale
  разделения

Scrubbing implementation details из Diplodoc страниц:
- events.md: убраны Strimzi/cluster.142 references, OutboxPoller детали,
  internal alert names (OrdinisOutboxLagHigh, OrdinisOutboxDLQNonEmpty),
  metric names (nsi_outbox_*). Заменены на consumer-observable contract.
- webhooks.md: убраны metric names (nsi_webhook_ssrf_rejected_total),
  alert table (OrdinisWebhookHighFailureRate и т.д.). Retry policy
  переписана на user-side perspective.
- errors.md: убран alert name OrdinisReadApiErrorBudgetBurnFast, Tempo
  endpoint URL, internal connection details.
- x-references.md: OrphanReferenceScanner → general "Ordinis периодически
  детектирует". Убраны metric/alert names. Cascade roadmap без link на
  internal design doc.
- auth.md: убран file path ScopeContext.java.
- bundle-cuod.md: убран Maven module path.
- caching.md: cascade design link заменён на text reference.

CI:
- .gitlab-ci.yml: новый job build-docs (stage: build) — Node 20 alpine,
  pnpm install --frozen-lockfile + pnpm build, artifact docs/_dist на
  неделю. Triggers: auto на docs/**, либо manual web. Готов к follow-up
  pages-deploy job когда host решён.
- Новый pattern .docs-changes для rules.

Build verified clean: 13 HTML pages, 6.9M, no broken links/refs.
2026-05-07 23:27:03 +03:00

170 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ошибки и 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` | БД недоступна — retry с long backoff |
| `dependent_unavailable` | Зависимый сервис (config, secrets) недоступен — retry |
| `event_publish_failed` | Запись в БД ОК, но события не отправлены — eventually consistent (events доедут позже) |
## 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.
- HTTP `503 Service Unavailable` при exhausting connection pool.
### 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 team:
1. Скопируй `traceId` из response либо логов.
2. Создай issue с воспроизведением.
3. Ordinis team поднимет trace по этому ID — full path запроса виден.
Без `traceId` investigation занимает существенно больше времени —
обязательно сохраняй его при ошибках.
## Дальше
- [Best practices](best-practices.md) — как писать robust integration code
- [Авторизация](auth.md) — как избежать 401/403