# Решение: IoT Telemetry Storage Architecture # Дата: 2026-04-04 # Агент: GitHub Copilot (Claude Sonnet 4.6) # Статус: ПРИНЯТО --- ## Контекст IoT платформа принимает данные с датчиков через MQTT. Данные проходят: EMQX → iot-mqtt-bridge → RabbitMQ → event-dispatcher → function pod. Проблема: данные не сохраняются. Функция получает событие и забывает его. Для клиентов (мониторинг объектов, счётчики, производство) нужно: - Автоматическое хранение всей телеметрии - Доступ к историческим данным - Низкий порог входа — не требовать от клиента настройки БД --- ## Решения ### 1. Хранилище — Postgres, отдельная DATABASE per tenant **Выбрано**: один Postgres инстанс, отдельная DATABASE на каждого клиента. **Отклонено**: одна таблица с tenant_id колонкой. - Причина: изоляция только программная. Ошибка в WHERE → утечка чужих данных. **Структура**: ``` Postgres (StatefulSet в namespace iot) ├── sless_platform — системные данные платформы (tenants, etc) ├── tenant_{hash} — данные клиента A (полная изоляция) └── tenant_{hash} — данные клиента B (полная изоляция) ``` **Безопасность**: - Каждый tenant имеет свой Postgres USER с уникальным паролем (UUID) - Пароль генерируется при создании tenant, хранится в k8s Secret - Клиент B физически не может подключиться к DATABASE клиента A --- ### 2. Доступ клиента — только через REST API **Выбрано**: клиент читает телеметрию через REST API платформы. **Отклонено**: прямой доступ к Postgres через connection string. - Причина: Postgres внутри кластера, не должен торчать наружу. Security. **API**: ``` GET /v1/namespaces/{ns}/iot/telemetry ?device={device_id} &from={RFC3339} &to={RFC3339} &limit={int} GET /v1/namespaces/{ns}/iot/devices/{id}/last ``` Авторизация — Bearer токен (тот же механизм что и для functions). --- ### 3. Схема таблицы telemetry ```sql CREATE TABLE iot_telemetry ( id BIGSERIAL PRIMARY KEY, device_id TEXT NOT NULL, ts TIMESTAMPTZ NOT NULL DEFAULT now(), payload JSONB NOT NULL ); CREATE INDEX idx_iot_telemetry_device_ts ON iot_telemetry (device_id, ts DESC); ``` **Почему JSONB**: у каждого клиента разные наборы данных: - датчик температуры: `{"temp": 22.5, "humidity": 60}` - GPS трекер: `{"lat": 55.75, "lon": 37.61, "speed": 60}` - счётчик воды: `{"liters": 1234.5, "flow": 0.3}` Фиксированная схема невозможна. JSONB + индекс по (device_id, ts) даёт достаточную производительность для малого и среднего бизнеса. --- ### 4. schema.sql при деплое функции Клиент может положить `schema.sql` рядом с функцией: ``` my-function/ ├── handler.py ├── schema.sql ← CREATE TABLE IF NOT EXISTS my_alerts (...) └── requirements.txt ``` При деплое оператор выполняет `schema.sql` в БД tenant'а. Это позволяет клиентам без знания Python настраивать дополнительные таблицы. --- ### 5. DB_DSN в функцию При запуске function pod оператор прокидывает `DB_DSN` из Secret в env var: ``` DB_DSN=postgresql://tenant_abc:password@iot-postgres.iot.svc:5432/tenant_abc ``` Функция использует стандартный драйвер, не знает о деталях платформы. --- ### 6. Разделение sless и iot операторов **Решение**: sless-operator и iot-operator — ОТДЕЛЬНЫЕ компоненты с раздельными namespace. **Мотивация**: - В будущем могут быть в разных кластерах - Независимый деплой и масштабирование - Разные команды могут владеть компонентами - Нет cross-dependency в коде (loose coupling) **Namespace layout**: ``` namespace: sless — платформа serverless (sless-operator, event-dispatcher, RabbitMQ, Postgres invocations) namespace: sless-{hash} — tenant функции (function pods каждого клиента) namespace: iot — платформа IoT (iot-operator, EMQX, Postgres telemetry) namespace: iot-{hash} — tenant IoT ресурсы (IoTDevice CRDs) ``` **Связь между sless и iot**: - Общий идентификатор tenant: `{hash}` одинаковый в sless-{hash} и iot-{hash} - MQTT событие → RabbitMQ в namespace sless → function pod в sless-{hash} - iot-operator НЕ импортирует Go пакеты sless-operator - Общение только через k8s API и RabbitMQ endpoints **Postgres**: - sless: отдельный Postgres для invocations логов - iot: отдельный Postgres для telemetry per-tenant - Разные StatefulSet, разные PVC, разные credentials --- ### 7. Postgres инстанс для IoT **Выбрано**: `postgres:16-alpine` StatefulSet в namespace `iot`. **Причина**: простота для разработки. При передаче в production девопсы заменят на Managed Postgres от Nubes — connection string поменяется, код не меняется. **Ресурсы**: - PVC: 10Gi (начальный размер, увеличивается по мере роста) - Memory limit: 512Mi - CPU: 0.5 cores --- ## План реализации 1. StatefulSet Postgres в namespace `iot` 2. iot-operator: provisioning при создании IoTDevice namespace - CREATE USER tenant_{ns} PASSWORD '{uuid}' - CREATE DATABASE tenant_{ns} OWNER tenant_{ns} - CREATE TABLE iot_telemetry + индекс 3. iot-mqtt-bridge: INSERT telemetry при получении MQTT сообщения 4. iot-operator: REST API `/v1/namespaces/{ns}/iot/telemetry` 5. sless-operator: при деплое function → прокинуть DB_DSN + выполнить schema.sql