18.12.2020, 10:40
Количество просмотров 2722

Как на Drupal-сайте для сравнения цен настроить многоуровневую систему бэкапа и ускорить обновление данных

Если вы будете разрабатывать Drupal-сайт для прайс-площадки с возможностью сравнения цен между товарами, то перед вами могут встать две проблемы: как постоянно поддерживать данные в актуальном виде и как ускорить их обновление. Кейсом делятся эксперты студии ADCI Solutions.
Как на Drupal-сайте для сравнения цен настроить многоуровневую систему бэкапа и ускорить обновление данных

 - рис.1

Если вы будете разрабатывать Drupal-сайт для прайс-площадки с возможностью сравнения цен между товарами, то перед вами могут встать две проблемы: как постоянно поддерживать данные в актуальном виде и как ускорить их обновление.

Площадка, с которой работала веб-студия ADCI Solutions, помогает искать товары для скалолазов. И когда-то озвученных проблем не было: площадка работала всего с двумя магазинами, которые продают такую продукцию. Позже её владельцы решили расширяться за счёт сотрудничества с ещё несколькими ритейлерами, и в итоге их стало 20. С прежней архитектурой нельзя было рассчитывать на быструю работу сайта и, соответственно, хороший клиентский сервис.

Чтобы масштабировать импорт данных и улучшить производительность сайта и интерфейс, мы использовали несколько популярных в веб-разработке приёмов.

Терминология

Начнём с маленького глоссария.

Ритейлер — сайт или магазин, который продает товары и предоставляет нам потоки данных, или фиды.

Фид — поток данных. В нашем случае это .csv или .txt-файл, содержащий всю необходимую информацию о товаре и обновляющийся раз в день.

Импорт — процесс обновления информации, полученной из фида.

Main-сервер — главный сервер для работы с данными, которые видит пользователь на сайте.

Sync-сервер — вспомогательный сервер для вычислений.

Как сохранить данные максимально свежими

Работу сайта с данными обеспечивали два сервера: main и sync. Первый занимался обработкой пользовательских запросов, второй — импортом данных. Они связаны между собой с помощью системы для автоматизации процессов разработки и деплоя Jenkins.

Почему двумя задачами не мог заниматься один сервер? Залог успеха любого сайта — быстрая загрузка. Это любят все. Если бы в нашем случае импорты происходили на main-сервере, пользователь во время загрузки страницы успевал бы выпить чашку кофе. Поэтому пока один сервер занят монотонной вычислительной работой, другой работает с пользователями.

Контент-менеджер прайс-площадки создаёт на main-сервере страницы с товарами, следит, чтобы информация была актуальной, и прописывает уникальные идентификаторы товара, благодаря которым можно соотнести строчки из фидов со страницами товаров на сайте.

А пока контент-менеджер что-то меняет на main-сервере, на sync-сервере может происходить импорт. И то, и другое меняет базу данных, и нам необходимо было сохранить все. А если какой-то из серверов упадёт, важно было иметь максимально свежие данные, чтобы откатить сервер до прежнего состояния.

Одной из основных специализаций студии ADCI Solutions является разработка на Drupal, поэтому мы по максимуму используем возможности этой CMS. У нас уже был настроен Drupal-модуль backup_migrate, делающий бэкапы на сервере раз в день по таймеру, а также были настроены еженедельные бэкапы на хостинге Digital Ocean. Но этого было мало, и, чтобы заставить два сервера работать в унисон с базой данных, мы написали скрипт, который делает ежедневные бэкапы данных перед началом очередного импорта и записывает их в бэкап-хранилище на удалённом сервере с отладочным сайтом.

 - рис.2

Это дало нам уверенность, что обновленная информация появится на рабочем сайте. И если основной сайт упадёт, у нас всё равно будет бэкап менее чем 12-часовой давности.

Это псевдокод скрипта, создающего бэкапы:

Переход в рабочую директорию

Объявление переменных: день (число), месяц, год, день недели

Если сегодня первый день месяца:

Имя бэкапа = день + dump_monthly

Удаляем все _weekly бэкапы старше месяца

Иначе:

Если сегодня пятница:

Имя бэкапа = день + dump_weekly

Удаляем все _daily бэкапы старше недели

Иначе:

Имя бэкапа = день

Удаляем все бэкапы старше 2 месяцев и ставшие пустыми после этого папки

Подключаемся к main-серверу, делаем бэкап и сохраняем локально в папку "год/месяц" под именем, опредёленным выше

Скрипт работает каждый день, поэтому каждый день мы можем быть уверены, что у нас в наличии в удаленном хранилище:

  1. ежедневные бэкапы за последнюю неделю;
  2. еженедельные бэкапы за последний месяц;
  3. ежемесячные бэкапы за последние 2 месяца.

Как ускорить обновление данных в два раза

Когда ритейлеров было два, система импорта работала примерно так. Скрипт для каждого фида вытаскивал из них информацию, на основе которой обновлялась страница товара на сайте. Скрипты запускались последовательно, и как только оба импорта заканчивались, мы объединяли обновлённую информацию о товарах на sync-сервере и возможные изменения на main-сервере.

В среднем эти два канала обновлялись 3-5 часов. Но когда мы увеличили число ритейлеров, закономерно увеличилось и время полного импортирования. В какой-то момент оно выросло до 24 часов, с чем нельзя было мириться.

Как снизить время? Обрабатывать фиды не последовательно, а параллельно. В этом случае мы имеем дело с многопоточностью, когда несколько запросов пытаются одновременно получить доступ к одной и той же информации.

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

Но поток не ждёт, когда вернут книгу, а идёт домой к человеку, который её читает, и садится читать рядом, но при этом дописывает в неё что-то своё. В этом и был конфликт: невозможно предугадать, чьи изменения сохранятся. А нам нужно было сохранить каждое.

Когда мы тестировали обновление для 1000 продуктов, обновлялись только 500-600. В поисках решения мы встроили lock mechanism — «замок», позволяющий контролировать данные и не дающий вмешаться другим процессам, пока данные зависят от текущего.

Псевдокод работы «замка»:

*секция с маппингом, где мы определяем, что продукт из фида соответствует ноде на сайте*

имя замка = lock + node_id #обеспечиваем уникальность имени замка

Захватываем замок методом acquire_lock(имя замка) #с этого момента мы можем быть уверены, что изменять эту ноду может только текущий поток

Сохраняем ссылку на продукт в поле ноды

Сохряняем ноду

Отпускаем замок методом release_lock(имя замка) #с этого момента любой ожидавший поток может пробовать захватить замок на ноду

Продолжаем делать дела, не требующие ограничения доступа

acquire_lock(имя замка):

Пока не захватили замок:

Проверяем, есть ли в таблице запись о замке с нужным нам именем

Если есть:

Уходим в ожидание на определенное время и после таймаута пробуем снова

Иначе:

Создаём в таблице запись о замке с нужным именем

Объявляем, что замок захвачен

release_lock(имя замка):

Удаляем из таблицы запись о замке с нужным именем

Рассмотрим абстрактный пример работы lock mechanism. P1 и P2 — это процессы, вносящие изменения в файл.

 - рис.3

Работа без lock mechanism. На этапе 3.2 P2 не знает об изменениях со стороны P1, поэтому во время сохранения переписывает изменения, внесённые P1. Проще говоря. без lock mechanism невозможно предугадать, чьи изменения сохранятся.

 - рис.4

Работа с lock mechanism. Пока один процесс пишет изменения, второй ждёт свой очереди.

Заключение

Клиент не раскрывает деталей, как отразилось масштабирование на бизнесе. Но о том, что динамика положительная, говорит общий отзыв: пользователи есть, они возвращаются, всё хорошо. Странно было бы ожидать обратного, когда вместо двух магазинов их становится в 10 раз больше.

Желание бизнеса расти и развиваться всегда идёт рука об руку с техническими проблемами. Желаем всегда помнить об этом, планировать бюджет и работать с опытными подрядчиками.

Рубрика:
{}E-Commerce

ТАКЖЕ ПО ТЕМЕ