С чего всё началось
Как я уже писал, у нас уже работал промышленный collector.
Он читал данные из PLC, складывал snapshots в RAM-очередь, а дальше отправлял их в ClickHouse.
Основная задача была такой:
PLC
↓
collector
↓
ClickHouse
↓
история
↓
ML
Сигналы собирались постоянно.
Накапливался временной ряд.
На нём уже можно было обучать модели, сравнивать периоды, искать предвестники событий и пробовать строить какие то модели.
Потом ребята из соседнего отдела начали запуск MES-системы управления производством:
часть каких-то данных нужно было отдавать в MES.
И первоначальное решение у меня было простым
Данные уже лежат в ClickHouse.
Сделать API и MES будет читать нужные показатели.
Для MES все равно не нужна частотат даже 1 сек, поэтому схема "выдернуть данные из clickhouse" была логичной.
Примерно месяц так всё и выглядело.
А потом началась неразбериха:
Добавьте ещё вот этот регистр.
Потом:
И вот эти датчики тоже нужны.
Потом:
А тут нам ещё один показатель понадобился.
И каждый такой «маленький» запрос заставлял перестраивать всю базу временного ряда для ML.
И стало понятно, что это архитектурная мина для потомков.
Почему MES и ML вообще нельзя было держать в одном контракте
Для MES изменение набора сигналов — нормальная ситуация.
MES нужна текущая картина производства.
Сегодня бизнес хочет видеть температуру.
Завтра счётчик.
Послезавтра ещё какой-нибудь технологический параметр.
Набор данных естественно развивается.
Для ML ситуация почти противоположная.
Мне нужен стабильный временной ряд.
Если условно вчера snapshot выглядел так:
[A, B, C, D]
сегодня:
[A, B, X, C, D]
а через неделю:
[A, B, X, C, Y, D]
то технически данные у меня всё ещё есть.
Но теперь я должен постоянно хранить какие-то инструкции, типо:
Так, до этой даты индекс 3 означал одно, после этой даты другое, а ещё тут поменялось декодирование...
И вконце концов вместо анализа данных начинаются танцы с бубном, чтобы понять кто что и когда менял.
Для ML — изменение исторического контракта данных КРИТИЧНО. Особенно если мы имеем дело с временными рядами
Либо мы при изменении каталога каждый раз выкидываем "прошлое", либо мы больше не "насилуем" наши временные ряды и разделяем хранение данных.
Главное правило: никто не должен иметь возможность менять ML-датасет
В моем случае MES не должен иметь возможность менять ML-датасет. Вообще.
ML-каталог должен жить своей жизнью.
Если я решил изменить его — это отдельная осознанная НОВАЯ версия датасета.
MES-каталог, наоборот, должен спокойно расширяться хоть каждую неделю потому что у заказчика MES (руоводство) каждый раз новое видение системы.
То есть теперь надо было коллектор переделать на два потока.
┌→ ML
PLC → collector ┤
└→ MES
Вот тут и возникла основаня сложность:
Сделаем два коллектора и баста

Очевидное решение выглядит так:
PLC ← collector ML
PLC ← collector MES
И архитектурно оно как будто бы тоже норм: - Два независимых приложения. - Два независимых расписания. - Два каталога.
Никто никому не мешает.
Только источник данных у нас не Kafka или облачная база.
Источник — настоящий промышленный PLC.
И идея посадить на него два независимых процесса, которые каждый по своему расписанию начинают дёргать контроллер, да еще и на одном порту TCP - не могу себе такое представить.
Не потому что это невозможность, а потому что такие опыты могут иметь очень дорогие последствия ввиде нарушения работы PLC или останову/поломке станка.
Особенно учитывая, что ML-сбор уже идёт довольно близко к пределу секундного цикла.
Поэтому: один хозяин PLC, два разных потребителя
Итоговая схема теперь такая:
┌→ ML catalog
│ ↓
PLC → one reader ───┤ ML queue
│ ↓
│ ML ClickHouse
│
└→ MES catalog
↓
MES queue
↓
MES ClickHouse
↓
MES API
Главная идея:
TCP-соединением с PLC владеет один reader.
Он один решает:
- когда читать;
- что читать;
- в каком порядке читать;
- сколько времени на это можно потратить.
А уже после чтения данные расходятся в два независимых логических контура.
У ML свой YAML-каталог.
У MES свой (теперь его можно менять как вздумается и безопасно).
Своя очередь.
Своя база.
Своя схема хранения.
Грубо говоря: если MES завтра попросит ещё столько-то сигналов, ML-каталог от этого вообще пострадает.
Еще одно ограничение - слабый мини ПК, который подключен к PLC.
Да-да, edge-компьютер который подключен к PLC, мягко говоря, слабенький.
Но теперь через него должнго проходить сразу два каталога MES/ML.
Ну точнее я сначала вообще так хотел сделать. Чтобы reader собирал сразу два готовых snapshot:
ML snapshot
MES snapshot
Но это означало дополнительные структуры, копирование массивов и лишнюю работу именно внутри самого чувствительного процесса — reader, который должен успеть закончить PLC-опрос вовремя. А он у нас и не такой быстрый для промышленного оборудования - секундный
После пары попыток запустить изменный пайплайн, Front Compact (тот самый dge-компьютер) стабильно уходил в OOM (out of memory).
Поэтому пришлось менять структуру ридера, думать над оптимизацией кэша и упрощать сами каталоги чуть ли не до сырых значений: A, B, C, D, E и т.п.
Почему на edge-ПК не разместить локальную бд?
Ну казалось бы, поставь локальную БД прямо на Front Compact и забудь.
Да, это очень удобно:
PLC → local DB → дальше всё остальное
Но на Front Compact стоит обычный SSD и рано или подзно он превратится в расходник постоянными записями.
Поэтому промежуточные данные надо было хранить в RAM-очередях.
А постоянное хранение — уже в ClickHouse на другом узле.
То есть edge должен остатстя максимально, простите, тупым:
прочитал
↓
положил в RAM
↓
отправил
↓
забыл
При такой схеме, нам действительно ненадо закупать дорогостоящее оборудование для обслуживания каждого станка.
Да и ценность такого оборудования теперь минимальная. Коллектор простой и может стабильно работать на маломощных ПК.
Что получилось в итоге:
После разделения ML-контракт сохранился.
В нём осталось 7 271 логическое значение, и главное — структура ML-каталога не изменилась из-за появления MES.
Историческая база тоже осталась как есть.
Ничего не пришлось мигрировать и переписывать задним числом.
MES при этом получил отдельный каталог на 641 сигнал и собственную базу в ClickHouse.
То есть теперь можно спокойно менять MES каталог изолрованно от ML и наоборот.
Для чего все это? Главный инсайт.
Самый главный вывод не про PLC или ML/MES.
Он про data engineering на собственных ошибках. И чем дольше я работаю, тем больше понимаю насколько я ничего не понимаю :)
Очереди, кэш, ram-queue, постоянный OOM на слабом оборудовании, геморрой с каталогами, консистентность данных временного ряда и т.п.
Поэтому, как говорил дядюшка Владимир Ильич: "Учиться, учиться и еще раз учиться!", ведь ничего идеального не существует (это я уже от себя добавил).
