Як я будував рекомендаційну систему для книжкового агрегатора: Data Engineering на практиці
Йоу, спільното DOU!
Мене звати Анатолій Шара. Частина з вас може пам’ятати мій попередній матеріал про MLOps, а для тих, з ким ми ще не знайомі: я — NLP Engineer й уже ось як п’ятий рік служу в лавах ЗСУ.
Крайній рік став особисто для мене непростим періодом. Я інтуїтивно відчував, що вдруге в житті на мене насувається ПТСР, тому, щоб пом’якшити його наслідки, я знову звернувся до перевіреного способу — інтенсивного навчання з посиленою практикою.
Але давайте повернемося до основної теми цього блогу. Чесно кажучи, я давно вмів будувати різні
Саме тому я вирішив поєднати приємне з корисним: зайняти голову на повну та системно добрати міцний фундамент у Data Engineering. Значну частину відповідної бази я отримав під час навчання на профільній програмі в УКУ. Проте головне для мене було інше: не просто пройти навчальні модулі й отримати сертифікат, а відразу «приземляти» знання та навички на живий технічний проєкт.
Так у мене під час навчання з’явився масштабний pet project — рекомендаційна система для онлайн-агрегатора книжок. Відверто кажучи, я не мав жодного бажання робити ще один Jupyter-ноутбук зі штучно ускладненим пайплайном і колаборативною фільтрацією. Мені хотілося побудувати систему, яка поводиться як реальний продукт: приймає події, накопичує історію взаємодій, зберігає профілі та метадані, агрегує сигнали, обчислює фічі, оновлює індекси, а потім віддає рекомендації через окремі сервіси.
І саме тут Data Engineering перестає бути абстрактною дисципліною й починає напряму визначати, чи взагалі система житиме.
З чого все почалося: не з моделі, а з архітектури
Перший висновок, до якого я прийшов досить швидко: якщо в системі є важкі NLP-компоненти, кілька джерел даних, різні типи сховищ і потреба в оновленні рекомендацій, монолітний Python-скрипт — це шлях у нікуди. На старті спокуса була очевидна: написати один великий пайплайн, який забирає дані, рахує фічі, генерує ембедінги, перебудовує індекси й десь у кінці повертає щось схоже на рекомендації. Але така схема ламається одразу, щойно в різних частин системи з’являються окремі життєві цикли. Клікстрім оновлюється постійно. Каталог книжок живе у своєму темпі. Ознаки користувача рахуються в іншому. Пошукові індекси та ембедінги — це вже окремий контур. А фінальне ранжування взагалі може жити в іншому шарі обчислень. Тому я почав мислити системою як набором сервісів і доменів, а не одним кодовим файлом. Окремо — події взаємодії користувача з каталогом. Окремо — обробка книжкових метаданих. Окремо — генерація ембедінгів і побудова індексів. Окремо — шар фіч і агрегатів. Окремо — сервіс, який віддає рекомендації. Саме тут підходи з модуля Architecture & Design, які я отримав в УКУ, виявилися для мене найбільш практичними: я перестав думати категорією «як написати пайплайн» і почав думати категорією «як система має поводитися, коли її частини змінюються незалежно одна від одної».
Чому для рекомендаційної системи мені виявилося замало батчів
Коли я тільки починав, моя ментальна модель була простою: є зібраний датасет, напишу якусь обробку, потім створю
Де і як я зберігав дані
Щойно події пішли потоком, постало друге велике питання: що і де зберігати. Швидко стало видно, що одна універсальна база тут не врятує. У системі було кілька різних типів даних: історія взаємодій, метадані книжок, агреговані фічі, проміжні представлення для рекомендацій, а також «гарячі» ключі для швидкого доступу.
І саме тут почали реально працювати мої знання про розподілені бази даних. Корисно було не просто знати назви NoSQL-систем, а розуміти, який тип навантаження вони люблять. Я дивився на системи через їхню поведінку: document-store — там, де потрібна гнучка модель метаданих; key-value/in-memory — там, де критичний швидкий доступ до гарячого стану; окремі write-heavy або горизонтально масштабовані сховища — там, де важливі обсяги подій і патерни запису. Саме такі речі ми багато розбирали в УКУ, але реальне розуміння прийшло вже тоді, коли ці рішення довелося приймати руками на своєму проєкті. Ще важливіше — я почав мислити не назвами технологій, а read/write-патернами, латентністю, реплікацією, партиціюванням і компромісами storage engine. Ти швидко перестаєш обирати базу «бо вона популярна», коли проходиш шлях від «усе ніби працює» до «чому ця схема почала душити систему під реальним профілем доступу».
Airflow, dbt і момент, коли пайплайн перестає бути просто скриптом за розкладом
Події та сховища — це лише половина системи. Рекомендації з’являються не з самого факту наявності сирих даних, а з правильно підготовлених і стабільно оновлюваних фіч. У моєму проєкті треба було регулярно агрегувати поведінкові сигнали, ознаки популярності, частину текстових ознак, а також оновлювати дані для пошукового та рекомендаційного контурів. Якщо зробити це по-простому, починаються класичні проблеми: повторний запуск ламає стан, backfill дублює дані, а будь-який фейл змушує вручну думати, де система опинилася між двома кроками. Тут для мене добре відкрився Apache Airflow. Не як «зручний інтерфейс, де красиво намалювати DAG», а як інструмент дисципліни. Я почав мислити задачами як ідемпотентними блоками, правильно розводити залежності, налаштовувати retries, продумувати backfill і сценарії відновлення після падіння. У парі з dbt це виявилось особливо корисним. Я будував не просто SQL-трансформації, а керований шар моделей: очищені події, агреговані сигнали, окремі представлення для фіч користувача й об’єкта, а також проміжні таблиці для кандидатної вибірки. Для рекомендаційної системи це критично, бо тут легко заплутатися між сирими подіями, очищеними сигналами, агрегаціями, фічами та готовими представленнями для сервісу видачі. Один із найбільш корисних практичних уроків був простий: оркестрація — це не «поставити джобу на розклад». Це продумати поведінку системи при фейлі так, щоб повторний запуск не ламав дані й не робив систему непередбачуваною.
Пошук, рекомендації та важкі NLP-компоненти
Оскільки це був не просто класичний recommender, а система для книжкового агрегатора, мені було важливо працювати не лише з поведінковими сигналами, а й зі змістом самих книжок. Саме тому в архітектурі з’явився гібридний контур: BM25, dense retrieval, reranking і рекомендаційна логіка. На практиці це означало кілька шарів: збір і нормалізація метаданих книжок, генерація ембедінгів для текстових полів, побудова індексів, а далі — комбінування сигналів через hybrid search, weighted RRF, CrossEncoder та інші NLP-компоненти для більш точного фінального ранжування. І саме тут стало очевидно, чому без хорошого Data Engineering усе це довго не живе. Бо проблема не в тому, щоб один раз порахувати красивий Jupyter-ноутбук. Проблема в тому, щоб оновлювати індекси без хаосу, перераховувати фічі без дублювання, синхронізувати нові дані з рекомендаційним шаром, контролювати залежності між компонентами й робити це так, щоб система відновлювалася після падіння.
Замість висновку: заземлення в реальність через Kubernetes
Але насправді вся ця архітектура лишалася б лише красивою схемою на дошці, якби не пройшла найважливіший тест — тест реальним середовищем. Останнім, але чи не найважливішим етапом для мене стало «заземлення» всієї цієї системи. Я перестав думати про генерацію ембедінгів, оновлення індексів і сервіси рекомендацій як про локальні скрипти, які «колись» переїдуть на сервер, і почав пакувати їх як повноцінні мікросервіси. Частину пайплайнів я загорнув у Docker, описав для них конфігурацію розгортання в Kubernetes і саме там по-справжньому зіткнувся з реальністю продакшну.
Швидко з’ясувалося, що в production код падає не так часто, як нам хочеться думати. Значно частіше закінчується пам’ять, сервіс упирається в обмеження CPU, починаються проблеми з мережею, IO або таймаутами. Саме тому налаштування requests/limits, liveness/readiness probes, health checks і базової спостережуваності стало для мене не «девопс-шоком», а частиною самої архітектури. Kubernetes змусив мене мислити системою, яка не просто працює, а працює передбачувано, деградує контрольовано і залишається придатною до дебагу в той момент, коли щось іде не так.
Паралельно стало очевидно, що без CI/CD і хмарної інфраструктури така система швидко перетворюється на набір ручних дій і крихких домовленостей. Тому наступним логічним кроком стало винесення збірки, перевірок і розгортання в більш відтворюваний процес, а самої інфраструктури — в декларативний опис. І саме тут для мене остаточно зійшлися докупи всі шматки пазла: дані, пайплайни, сервіси, інфраструктура,
Цей шлях — від сирого Parquet і перших експериментів до контейнеризованих сервісів, оркестрації та Kubernetes — став для мене найкращим підтвердженням того, що навчання працює лише тоді, коли проходить перевірку практикою. УКУ дало мені фундаментальний архітектурний каркас, а цей pet-проєкт дав найцінніше — реальні інженерні мозолі, без яких професійне зростання залишається теорією.
Мабуть, це і є головний висновок усього цього шляху: Data Engineering — це не просто робота з таблицями, пайплайнами чи черговим «модним стеком». Це спосіб мислити про систему як про єдине ціле, де дані, обчислення, сервіси й інфраструктура мають бути спроєктовані так, щоб усе це не розвалилося при першому ж серйозному навантаженні.
Якщо вам цікава тема архітектури рекомендаційних систем, Data Engineering-підходів у
Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.
6 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів