7.5 KiB
Журнал сессии — 2026-10-11 (IoT: внешнее код-ревью и фиксы)
Запись по требованию «документировать всё, включая собственные ошибки» (приказ 2026-09-24). Предыдущие:
HISTORY/2026-10-07-session-log.md,HISTORY/2026-10-10-session-log.md. Основание: внешнее ревью Claude Sonnet по промпту../CLAUDE-REVIEW-PROMPT.md(40 пунктов на два сервиса; SQS-часть — вSQS-service/HISTORY/2026-10-11-session-log.md).
1. Задача
Разобрать находки ревью по монолиту iot-service и исправить их.
2. Что исправлено
🔴 Критичные
- 🔴8/🔴9 — poison-строка в
flushAll(internal/storage/iotpg/iot_telemetry_store.go:166,177). При постоянной (не retryable) ошибкеflushAllвозвращал ошибку → consumer (consumer.go:159) не удалял батч из SQS → бесконечный redelivery всего батча. Теперь ошибку ставит только retryable-ветка; постоянная ошибка означает «валидные строки уже вставлены построчно, битые отброшены» →Flushвозвращает nil, батч подтверждается. Коммит1fe543f.
🟡 Важные
| Пункт | Файл | Суть | Коммит |
|---|---|---|---|
| 🟡13 | bridge/sender.go |
Ретраил бесконечно любые ошибки SQS; permanent (4xx/размер/нет очереди) занимали воркеры → классификация isPermanentSQSErr (smithy APIError) |
657850d |
| 🟡14 | bridge/handler.go |
MessageID = uuid на каждый колбэк → повторная QoS1-доставка не дедуплицировалась. Теперь SHA256(ns|device|topic|payload) |
657850d |
| 🟡15 | bridge/bridge.go |
После 3 неудач подписки ждали следующего реконнекта (которого не будет при живом соединении) → бесконечный ретрай подписки | 657850d |
| (new) | bridge/handler.go |
Defense-in-depth: валидация формата ns/deviceID из топика (барьер — EMQX authz) |
657850d |
| 🟡17 | consumer/consumer.go |
receiveCount проверялся только для битого envelope → применяем maxReceiveCount ко всем ошибкам |
5114320 |
| 🟡18 | cmd/iot-service/main.go |
run() выходил до завершения server.Shutdown → deferred Close срабатывали до дренажа in-flight. Добавлен shutdownDone |
5114320 |
| 🟡19 | api/handler/devices.go |
Ошибка GetByName после успешного INSERT давала 500 (пароль терялся, повтор → 409) → отдаём 201 с in-memory объектом |
5114320 |
| 🟡11 | iotpg/iot_telemetry_store.go |
getTenantDB/QueryTelemetry не проверяли safeNamespaceRe (a_b ↔ a-b → одна БД) → валидация на всех путях |
f41a9fe |
Осознанно НЕ менялось — РЕШЕНИЯ (2026-10-11)
- 🟡16 (QoS1 ack): решение — оставить async-ack + метрика дропов. Синхронный
ack (подтверждать только после отправки в SQS) блокировал бы MQTT-колбэк при
недоступности SQS — это уже чинили ранее (потеря ВСЕХ сообщений). Throughput
важнее; дроп при переполнении фиксируется счётчиком
dropped. - 🟡12 (
AUTH_TEST_MODE): решение — оставить как есть. Уже fail-closed: при отсутствииJWT_HMAC_SECRETиAUTH_TEST_MODE=falsemiddleware отказывает в обслуживании (500), а test-mode срабатывает только без секрета.
3. Прочее (перед ревью, в этой же сессии)
193509c— консоль: namespace =sub(убралиsless-{SHA256}, расходившийся с middleware) и удалили несуществующийPOST /v1/namespaces/{ns}/ensure.
4. Верификация
go build ./...,go vet ./...,go test -race ./...— зелёные.- Push:
54fe1f5..f41a9feвorigin/master.
5. Деплой на Nubes (Terraform) — образ EMQX, 2026-10-11
terraform apply упал на nubes_http.iot_emqx через ~70 с (стадия validation,
22.0 с): платформа требует РОВНО ОДИН EXPOSE в образе, иначе
«Не указан applicationUsePort…». У emqx/emqx:5.5.1 — 7 EXPOSE
(18083/1883/4370/5369/8083/8084/8883). Параметра applicationUsePort у
провайдера nubes 3.1.0 нет (проверено: generated/prod/resources_yaml/98_http.yaml
generated/prod/docs-rendered/http.md+ исходники провайдера — совпадений нет), поэтому лечится только пересборкой образа.
Что сделано (образ nail/iot-emqx:v0.2.5):
emqx/Dockerfile— multi-stage:FROM emqx/emqx:5.5.1 AS base→FROM scratch→COPY --from=base / /. EXPOSE/ENV/VOLUME наследуемого образа НЕ переносятся вscratch, поэтому остаётся единственныйEXPOSE 8083. ENVLANG/LC_ALL/PATHобъявлены заново.USER emqx+WORKDIR /opt/emqx- штатные
ENTRYPOINT/CMDбазы.
- ⛔ Попытка
FROM debian:bookworm-slim+COPY --from=base / /НЕ работает:cannot copy to non-directory: /bin— в bookworm/binсимлинк (usrmerge), в emqx-образе (Debian 11)/bin— реальный каталог.
- штатные
emqx/emqx.conf— удалёнsalt_rounds = 10изpassword_hash_algorithm: схема 5.5.1 его НЕ принимает (unknown_fields, unknown => "salt_rounds"→call_hocon_failed, контейнер не стартует). Роль поля нулевая: EMQX только сверяет bcrypt-хеш из БД, cost берётся из самого хеша.
Верификация образа (локально):
docker inspect … --format '{{json .Config.ExposedPorts}}'→{"8083/tcp":{}};- запуск контейнера →
Listener tcp:default on 0.0.0.0:1883 started,Listener ws:default on 0.0.0.0:8083 started,EMQX 5.5.1 is running now!, статусrunning. Ошибкиeconnrefusedк127.0.0.1:5432в тесте ожидаемы (PG рядом нет). - Реестр Gitea: теги
latest,v0.2.4,v0.2.5. terraform.tfvars/terraform.tfvars.example:iot_emqx_version = "v0.2.5".
⚠️ Блокер деплоя: инстанс iot-emqx (3bdaef8e-e880-4419-870a-bd0158826751)
остался в состоянии not created после неудачного apply и держит имя. По
документации платформы такие инстансы через API не удаляются — удалять вручную
в ЛК (deck). terraform state list пуст (в state ничего не записалось).