С чего всё началось

Как я уже писал, у нас уже работал промышленный 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 на слабом оборудовании, геморрой с каталогами, консистентность данных временного ряда и т.п.

Поэтому, как говорил дядюшка Владимир Ильич: "Учиться, учиться и еще раз учиться!", ведь ничего идеального не существует (это я уже от себя добавил).