MAATRIX / Блог / Книжный: свои рекомендации без выгрузки базы покупателей в чужой сервис

Книжный: свои рекомендации без выгрузки базы покупателей в чужой сервис

MAATRIX

Блок «с этой книгой также покупают» поднимает средний чек заметнее почти любой другой доработки витрины — это знает любой, кто хоть раз включал и выключал такие рекомендации и смотрел на разницу в отчёте. Проблема в том, как это обычно делают: подключают внешний сервис персонализации, который просит выгружать историю покупок и просмотров каждого клиента к себе на сервера, чтобы «обучить модель». Для магазина, где заказы связаны с именами, адресами доставки и часто с номерами карт лояльности, это означает, что коммерчески чувствительная база покупателей уезжает третьей стороне — не разово, а на регулярной основе, потоком. Дальше разберём, почему так устроено большинство внешних сервисов рекомендаций, что конкретно вы отдаёте, когда подключаете такой сервис, и как собрать работающие рекомендации на собственном сервере — без выгрузки базы покупателей наружу.

Почему рекомендации обычно строятся через чужое облако

Логика поставщиков SaaS-рекомендаций понятна: чтобы предсказать, что купят рядом с конкретной книгой, нужна статистика по большому числу заказов и по поведению пользователей на сайте — клики, добавления в корзину, время на странице. Строить и обучать такую систему у себя выглядит сложнее, чем подключить готовый виджет по API-ключу. Поэтому типовая интеграция выглядит так: на сайт магазина ставится скрипт, который в реальном времени отправляет во внешний сервис события — какую книгу посмотрел пользователь, что положил в корзину, что купил, иногда вместе с идентификатором клиента или email. Внешний сервис у себя строит модель и в ответ отдаёт список «похожих» товаров, который магазин показывает на странице.

Формально это работает и часто работает неплохо. Но для книжного магазина в этой схеме есть слепое пятно, которое обычно замечают уже после подключения. История покупок в книжном — это не просто список SKU. Она прямо говорит о вкусах, взглядах, состоянии здоровья (медицинская и психологическая литература), религии, политических предпочтениях, семейном положении (книги о беременности, разводе, воспитании) конкретного человека. Это данные, по которым можно составить довольно точный профиль личности — и вы отправляете этот профиль вместе с базой покупателей на серверы компании, чьи условия обработки данных вы, скорее всего, читали по диагонали при подключении виджета.

Отдельная проблема — коммерческая. База покупок с привязкой к товарам и датам — это, по сути, слепок ассортиментной стратегии и оборота магазина. Отдав её в третьи руки на постоянной основе, вы отдаёте не только персональные данные клиентов, но и данные, по которым конкурент или сам поставщик сервиса (если он работает и с другими книжными) может делать выводы о вашей марже, ценовой политике и о том, какие категории у вас продаются лучше.

Что именно вы отдаёте, когда подключаете внешний сервис рекомендаций

Стоит разложить по пунктам, что реально утекает наружу при типичном подключении, потому что на уровне «мы просто поставили виджет» это не всегда очевидно.

  • Идентификатор клиента — email, номер телефона или ID из вашей CRM, по которому события связываются между визитами.
  • Полная история просмотров и покупок — какие книги, в каком порядке, с какой частотой, часто с ценами и скидками.
  • Поведенческие данные — то, что человек искал, но не купил, сколько времени провёл на карточке, отказался ли от корзины.
  • Метаданные заказа — способ доставки, регион, иногда часть данных доставки, если скрипт стоит и на странице оформления заказа.

Всё это уходит непрерывным потоком, а не разово. И даже если договор с поставщиком сервиса формально запрещает передачу данных третьим лицам, вы физически не можете это проконтролировать — данные уже не на вашем сервере, вы полагаетесь на чужую политику безопасности, чужой персонал и чужую инфраструктуру. Если у поставщика случится утечка (а такие истории с сервисами аналитики и персонализации случаются регулярно у самых разных компаний), пострадавшей стороной перед вашими клиентами будете вы — это ваш магазин собрал их данные и передал третьей стороне без явного информирования о том, куда именно уходит история покупок.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Как устроена альтернатива: рекомендации на собственном сервере

Принцип на самом деле простой и не требует научной степени в data science, чтобы его реализовать в базовом виде: рекомендации строятся на основе совместной встречаемости товаров в одних и тех же заказах — если книгу А часто покупают вместе с книгой Б, значит на странице А стоит показать Б. Это классический подход «люди, купившие это, также купили» — вычисляется он статистически, прямо по таблице заказов, без передачи данных куда-либо.

Технически это выглядит так:

  1. У вас уже есть таблица заказов (order_items) с товарами и заказами, к которым они привязаны — она и так лежит в базе вашего магазина.
  2. Раз в сутки (или чаще, если поток заказов большой) на сервере запускается расчёт: для каждой пары книг считается, сколько раз они встретились в одном заказе.
  3. Результат — таблица «книга → список похожих книг с весом связи» — сохраняется отдельно и отдаётся на витрину при отрисовке страницы товара.

Ключевая разница с внешним сервисом не в качестве рекомендаций (на старте они будут сопоставимы или чуть проще), а в том, что вся история покупок клиентов не покидает ваш сервер ни на одном шаге. Расчёт идёт локально, по вашей же базе, результат — тоже локальная таблица.

Пример расчёта на PostgreSQL, если у вас там уже лежат заказы:

CREATE MATERIALIZED VIEW book_recommendations AS
SELECT
    a.book_id AS source_book_id,
    b.book_id AS related_book_id,
    COUNT(*) AS co_purchase_count
FROM order_items a
JOIN order_items b
    ON a.order_id = b.order_id
    AND a.book_id != b.book_id
GROUP BY a.book_id, b.book_id
HAVING COUNT(*) >= 2
ORDER BY a.book_id, co_purchase_count DESC;

CREATE INDEX idx_book_reco_source ON book_recommendations (source_book_id);

Обновление раз в сутки по крону:

0 4 * * * psql -U bookstore -d bookstore_db -c "REFRESH MATERIALIZED VIEW book_recommendations;"

На витрине запрос к этой таблице по book_id страницы товара занимает миллисекунды даже на каталоге в десятки тысяч позиций — это обычный индексированный SELECT, а не вызов внешнего API с задержкой в сотни миллисекунд на сетевой round-trip до чужого облака.

Что можно добавить сверх базовой схемы

Совместная покупка в заказах — это основа, но у книжного магазина есть и другие сигналы, которые стоит подмешать, оставаясь при этом на своём сервере.

  • Просмотры без покупки. Если вы логируете переходы по карточкам товара (даже просто в отдельную таблицу events на своей базе), можно учитывать и связи «часто смотрят вместе», не только «часто покупают вместе» — это расширяет рекомендации для новых книг, по которым ещё мало продаж.
  • Категория и автор. Простое правило-фолбэк на случай, если у книги ещё нет статистики совместных покупок: показывать другие книги того же автора или ближайшей подкатегории. Это не «умная» персонализация, но закрывает холодный старт для новинок.
  • Вес по свежести. Совместные покупки полугодовой давности значат меньше, чем недавние — можно добавить убывающий вес по дате заказа, чтобы рекомендации следовали за текущим спросом, а не застревали в прошлом сезоне.
  • Векторные эмбеддинги описаний. Если хочется рекомендаций «по смыслу» (похожая тематика, а не только совместные покупки), в PostgreSQL для этого есть расширение pgvector — оно позволяет хранить и быстро искать по векторным представлениям текста описаний книг прямо в той же базе, без отдельного сервиса. Мы разбирали его отдельно в статье про векторный поиск в PostgreSQL — для книжного каталога это довольно естественное применение: похожесть по аннотации и жанру дополняет похожесть по факту совместных покупок.

Если поток заказов и просмотров у вас большой (несколько тысяч событий в день и выше), пересчёт материализованного представления по сырым данным на каждый REFRESH может начать заметно нагружать базу. В этом случае имеет смысл держать промежуточный агрегат в Redis — считать топ-N связанных книг для «горячих» товаров и отдавать их из памяти, обновляя раз в час, а не гонять полный JOIN по всей таблице заказов при каждом обращении к витрине. Сравнение подходов к кэшированию есть в статье Redis или Memcached — что выбрать для сервера.

Каталог и рекомендации — общая инфраструктура

Рекомендации редко существуют отдельно от остального магазина — это часть той же базы данных, того же сервера, что отдаёт каталог, корзину и оформление заказа. Смысл держать их на своём сервере теряется, если сам сервер магазина стоит на общем хостинге, где вы не контролируете ни ресурсы, ни то, кто ещё «арендует» соседний контейнер на той же машине.

Практический ориентир по ресурсам для книжного магазина среднего размера (каталог от нескольких тысяч до пары сотен тысяч позиций, посещаемость от сотен до нескольких тысяч визитов в день):

КомпонентРольОриентир по ресурсам
PostgreSQLКаталог, заказы, расчёт рекомендаций2-4 vCPU, 4-8 ГБ RAM
Redis (опционально)Кэш горячих рекомендаций и сессий1 vCPU, 1-2 ГБ RAM
Веб-сервер (nginx + приложение)Отдача витрины2 vCPU, 2-4 ГБ RAM

Это ориентир, а не точный расчёт — реальные цифры зависят от движка магазина, объёма каталога и того, сколько логики вы держите на сервере приложения против базы. Для старта конфигурации на 4-6 vCPU и 8-16 ГБ RAM обычно хватает с запасом, а дальше можно масштабировать по факту нагрузки. Мы подробно разбирали подбор ресурсов под интернет-магазин в статье сколько ресурсов нужно VPS для интернет-магазина, а базовую настройку сервера с нуля — в пошаговой настройке VPS под интернет-магазин.

Держать всё на одном сервере (или на паре серверов — база отдельно от веба, если нагрузка растёт) даёт ещё один практический плюс: расчёт рекомендаций и сама витрина работают в одной сети без выхода в интернет для передачи данных туда и обратно. Это не только вопрос приватности — это ещё и задержка. Запрос к своей базе по локальной сети — это доли миллисекунды. Запрос к внешнему API рекомендаций — это минимум один сетевой переход туда и обратно, часто через границу страны, если сервис размещён не рядом с вашим хостингом.

Честно о ценности утечки истории покупок

Стоит проговорить прямо: не каждая продажа истории покупок стороннему сервису — это злой умысел поставщика. В большинстве случаев это просто архитектура их продукта — им действительно нужны ваши данные, чтобы модель работала, и они честно об этом пишут в документации API. Проблема не в злонамеренности, а в том, что решение о ценности этих данных принимаете не вы, а внешняя компания, у которой другие приоритеты — вырастить свою модель на данных всех клиентов сразу, а не защитить конкретно вашу базу покупателей.

Что реально стоит взвесить перед подключением любого внешнего сервиса персонализации:

  • Что написано в разделе договора про использование данных клиентов для «улучшения сервиса» — это часто означает обучение общей модели на ваших данных вместе с данными других магазинов.
  • Где физически расположены серверы поставщика и под какую юрисдикцию попадает хранение данных ваших клиентов.
  • Что будет с уже переданными данными, если вы решите отключить сервис — гарантированно ли они удаляются, или просто перестают использоваться для показа рекомендаций вам, но остаются в общей обучающей выборке.

Если ответы на эти вопросы вас не устраивают (а чаще всего они просто отсутствуют в публичной документации, и их приходится выяснять через поддержку), собственная реализация на своём сервере снимает вопрос целиком — данные физически никуда не уходят, потому что расчёт происходит там же, где данные лежат.

Отдельно к этому же классу рисков относится и то, что происходит при утечке данных не по вашей вине, а из-за уязвимости в стороннем сервисе, которому вы доверили базу. Мы разбирали, что магазин обязан делать в такой ситуации по закону, в статье утечка данных — что обязаны сделать — стоит прочитать её до подключения любого стороннего сервиса, а не после инцидента.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Рекомендации на своей базе будут хуже, чем у специализированного SaaS-сервиса?

На старте — сопоставимо, а для каталога среднего книжного магазина часто даже точнее, потому что модель считается по вашим реальным заказам, а не по усреднённой статистике по многим магазинам сразу. Основная разница не в качестве алгоритма, а в том, что у крупных SaaS-сервисов обычно больше готовых фич из коробки — A/B-тестирование выдачи, готовые виджеты для разных типов страниц. Это можно доращивать постепенно, начиная с базовой схемы совместных покупок.

Нужно ли специальное железо для машинного обучения, чтобы считать рекомендации?

Нет. Описанная схема — это статистика по SQL-таблице заказов, для неё достаточно обычного VPS с PostgreSQL. GPU и специализированные ML-фреймворки нужны только для сложных нейросетевых моделей рекомендаций, которые для магазина на несколько тысяч SKU обычно избыточны — прирост качества не окупает сложность поддержки.

Что делать с холодным стартом — у новой книги ещё нет истории покупок?

Использовать фолбэк по метаданным: показывать книги того же автора, той же подкатегории или (если подключён pgvector) ближайшие по векторной похожести аннотации. Как только у новинки накопится хотя бы несколько совместных покупок, материализованное представление подхватит её в основную схему рекомендаций при следующем REFRESH.

Можно ли совмещать своё решение с внешней аналитикой, если она уже подключена?

Можно, но стоит разделить задачи: обезличенную веб-аналитику (посещаемость, конверсия по разделам без привязки к личности клиента) — во внешний сервис, если это удобно, а расчёт персональных рекомендаций, завязанных на историю покупок конкретных людей, — оставить на своём сервере. Граница проходит там, где данные перестают быть анонимными.

Сколько времени занимает переход с внешнего сервиса рекомендаций на свой?

Базовая схема на материализованном представлении PostgreSQL разворачивается за один день, включая интеграцию с версткой карточки товара. Более тонкая настройка — веса по свежести, векторный поиск по описаниям, кэш в Redis для крупного каталога — это уже отдельные итерации, которые можно добавлять постепенно, не откладывая первый рабочий вариант.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →