Files
IoT/HISTORY/2026-10-10-session-log.md

8.4 KiB
Raw Permalink Blame History

Журнал сессии — 2026-10-10 (IoT: чистка легаси → ветка master → ревью → фиксы)

Рабочая папка: /home/naeel/nubes/SQS_und_IoT/IoT Ветка: master (переименована из feature/replace-kafka-with-sqs) Состояние на конец сессии: 28 коммитов сверх origin/master, не запушено (пуш — только по команде).


1. Чистка легаси

Цель владельца: «старое ГАВНО не всплывало».

  • 🗑 Удалён код-кластер старого IoT (не импортировался монолитом, не собирался): cmd/iot-operator/, cmd/mqtt-bridge/, controllers/, api/v1alpha1/, internal/api/ (старый REST + UI-embeds admin/console). Коммит f152f54.
  • go mod tidy убрал k8s-зависимости (k8s.io/*, controller-runtime) — −57 строк go.mod, −614 go.sum.
  • 🗑 bin/ со старыми бинарниками (operator/kafka-consumer/mqtt-bridge).
  • iot-admin.html (живой /iot-admin): секции Kafka/K8s Pods → «Очередь shared-SQS» + «Рантайм»; удалён CSS .pod-*/.badge-*/.dot-*/.lag-bar*. Коммит 42f0d6a.
  • .gitignore: убраны сталые пути бинарников.
  • LEGACY.md обновлён (помечено УДАЛЁН 2026-10-10).

Осталось на диске вне git: iot-console.html со старыми доменами sless.kube5s.ru / iot.kube5s.ru (см. §6, требует решения владельца — какой домен).

2. Ветка → master, коммит/пуш

  • SQS-service: main → master; IoT: feature/replace-kafka-with-sqs → master (старая master перезаписана, её 3 коммита влиты merge'ом, конфликт только в .gitignore).
  • Пуш в Gitea (gitea.services.ngcloud.ru/Nail/{SQS-service,IoT}): origin/master создан/обновлён. Force не использовался.

3. Правки кода по ревью Opus

Блокеры Terraform (Этап 2)

  • 632e551 MQTT-пароль устройства отдаётся один раз в 201 + phase пишется в БД (Create).
  • a6ef9f9 created_at → RFC3339 + перечитывание после Create.
  • 7e3828f telemetry_topic + нормализация metadata (без drift {}/null).
  • 7ea4923 валидация namespace/name/device_id (^[a-zA-Z0-9][a-zA-Z0-9-]*$, запрет _ + # /).

Потери данных (Этап 1)

  • 046db68 🔴1 сериализация flush (flushMu) — Flush дожидается in-flight записи.
  • e0cd1ca 🔴2 построчный fallback (ядовитая строка не роняет весь батч).
  • 006dbfa 🔴2 семантическая валидация envelope (пустые поля → DLQ).
  • 01d21f6 🔴3 слив буфера bridge при shutdown + безопасное закрытие канала.
  • 241c98a 🔴4 ретраи SQS до shutdown + таймаут на попытку.
  • 69ae2ff 🔴6 не рвать автореконнект (ретрай подписки, без Disconnect).
  • 530ee7b 🔴7 ретрай резолва DLQ + не терять битые при недоступной DLQ.
  • 47d9a04 🔴5 ретрай GetQueueUrl на старте + выход ≠0 при сбое подсистемы.

4. PRO-ревью (моё) — находки и фиксы

Разобрал работу предыдущей модели, нашёл 3 🔴 + 🟡/🟢:

  • 98e6def #1 os.Exit обходил defer iotStore.Close() → финальный flush терялся. Вынес логику в run() int, main = os.Exit(run()).
  • 6e4a844 #2 «не подтверждать битое при недоступной DLQ» = ядовитое зацикливается. Добавлено чтение ApproximateReceiveCount (после maxReceiveCount=5 — дроп).
  • c1733c5 #3 /ready = «горутина запущена», а не «подписан». Bridge сам управляет флагом через onSubscribed.

Этап 4 (изоляция/JWT)

  • ea569da JWT: обязательный exp, 401 вместо 403, guard тест-режима, JSON-ошибки.
  • 2bea1de replaceDSNDatabase (устранено смешение тенантов: ошибка для key=value DSN, корректный разбор sslrootcert=/path) + guard namespace (safeNamespaceRe, ErrInvalidNamespace).

Этап 3 (жизненный цикл)

  • 75c30e5 readiness consumer по SQS (onReady), ограничение wg.Wait (waitWithTimeout, 15с), идемпотентный Close (sync.Once).

Этап 5 (мелочи)

  • f37f216 удалён мёртвый mqtt_auth.go + constant-time админ-токен.
  • fa98020 decodeJSON (MaxBytesReader 64КиБ + DisallowUnknownFields) + таймауты сервера.
  • d7cb850 DeleteMessageBatch (до 10 за вызов).
  • 1510efe пагинация: tiebreaker ts DESC, id DESC + потолок limit + точный лог.
  • 11bb38e MaxReconnectInterval 30с + Debug для health-проб/телеметрии.

Terraform / безопасность

  • eacc740 PATCH обновляет metadata.
  • 0a51d70 409 различает конфликт name vs device_id (ErrDeviceIDExists).
  • c1adb26 P0-3 bcrypt-хэш паролей устройств и бриджа + EMQX name=bcrypt, salt_rounds=10 (ключ salt_rounds подтверждён по исходникам emqx/emqx; Go DefaultCost=10).

Тесты / доки

  • 46744ac юнит-тесты: validateIdentifier (handler), replaceDSNDatabase + safeNamespaceRe (iotpg).
  • 1600339 деплой-дока: пароль отдаётся один раз, в БД bcrypt.

5. Повторное PRO-ревью — открытые находки

  • 🟠 ReceivedAt — точность до секунды. bridge/handler.go пишет time.RFC3339, consumer/processor.go парсит time.RFC3339; субсекунды теряются. Очередь standard (не FIFO) → два показания в одну секунду могут встать не в том порядке; tiebreaker id DESC не чинит (id = порядок SQS-доставки). Фикс: RFC3339Nano на обеих сторонах.
  • 🟡 Пустое тело (msg.Body == "") подтверждается без DLQ — для консистентности тоже в DLQ.
  • 🟡 processBackoff-sleep внутри цикла батча задерживает подтверждение соседей → redelivery (спасают дедуп + фоновый flusher). Пре-существующий дизайн.
  • ⚠️ Валидация namespace/exp зависят от формата sub/токена платформы — не проверено.
  • ⚠️ flushMu — глобальная сериализация (осознанный компромисс 🔴1 vs throughput).
  • ⚠️ Гонки не прогнаны под go test -race.

6. Осталось (не сделано)

Хвосты доделаны 2026-10-11: 18cdea7 RFC3339Nano; 250bbdf пустое тело → DLQ; 49422ef домены консоли (apiBase → origin, mqttBase → exqx…); d195629 race-тест dispatcher.

Осталось:

  • Проверить формат sub платформы (валидация namespace + JWT exp) — проверка, не код.
  • Тесты на flushAll/Close (требуют мока PG/SQS; go test -race прогнан, dispatcher покрыт).
  • Terraform-провайдер (следующая большая задача после IoT).
  • SQS-хвосты (CreateQueue→QueueNameExists, ListQueues, JWT по sub, лимит по байтам).

Правила (напомню себе)

  • Коммитить после каждой правки; пуш — только по команде.
  • Документировать всё, включая ошибки (HISTORY/ + doc/thinking/).
  • Не задавать вопросов, ответ на которые есть в коде/доках.
  • «Рекомендация без замера — не рекомендация».