blog-post

Manticore Buddy: вызовы и решения

Привет всем! 🤗 Мы надеемся, что вы уже ознакомились с нашим Введением в Buddy и хорошо понимаете, как он работает. Мы хотим поделиться нашим путем и опытом разработки и теми вызовами, с которыми мы столкнулись.

В Manticore Software мы столкнулись с двумя основными проблемами:

  • Расширение Manticore Search некритичными для производительности функциями без изменения C++.
  • Упрощение процесса внесения вкладов в улучшения и реализации новых функций.

Мы были полны решимости найти решение. Так что давайте погрузимся в наш путь разработки Buddy и проблемы, с которыми мы столкнулись. Готовы? Поехали!

Начало путешествия

Наше путешествие началось с тщательного изучения проблемы. Хотя Manticore Search является исключительным продуктом поисковой базы данных, мы столкнулись с трудностями в выпуске новых функций с желаемой скоростью, потому что кодовая база написана на C++. Написание кода на C++ требует низкоуровневого взаимодействия с структурами данных, байтами, глубоких знаний о том, как работает машина, компиляции, отладки и нахождения наилучшего подхода к написанию кода, что занимает много времени, но приводит к более быстрому выполнению программы. Это значительно задерживало процесс разработки. Хотя C++ является отличным вариантом для производительности, на его разработку уходит время. Мы хотели двигаться быстро, поставлять больше функций и делать это последовательно.

Мы пришли к идее создания компаньона для нашего основного процесса searchd, который мог бы обрабатывать неудачные запросы от Manticore Search и возвращать результаты оригинальному клиенту. Мы не раздумывали долго, решая, какой язык использовать, и остановились на PHP по нескольким причинам:

  • Большинство членов основной команды были с ним знакомы, поэтому на его настройку уйдет меньше времени.
  • PHP быстрый, особенно с самой новой версией (8+), даже когда мы не требовали производительной работы от Buddy. Он быстрее, чем Python или JavaScript, поэтому это подходило под наши требования.
  • PHP не только быстрый, но и простой, что снижает уровень экспертизы, необходимый для вклада в будущую экосистему.

Вот почему мы выбрали PHP и начали реализовывать базовый код, чтобы понять, что нам потребуется позже.

Мы по-прежнему используем C++ для разработки функций, критичных к скорости. C++ идеально подходит для задач, которые требуют скорости. Для задач, которые не требуют высокой скорости, Buddy является оптимальным выбором.

Так был создан Buddy. Чтобы сделать это возможным на стороне C++ Manticore Search, мы реализовали отдельный цикл и связь между демоном searchd и процессом Buddy на PHP с использованием расширения CURL. Мы разработали наш внутренний протокол, который представляет собой простой JSON, для маршрутизации запросов к Buddy; он обрабатывает эти запросы и отправляет нам соответствующий ответ для передачи обратно оригинальному клиенту.

Реализация протокола связи

При начале нового проекта важно оставаться гибким и не планировать слишком подробно. В нашем случае мы начали с базовой реализации связи с использованием расширения sockets в PHP. Хотя это работало хорошо, это не было масштабируемым. Наша цель состояла в том, чтобы соединить Manticore Search с Buddy, и эта простая реализация позволила нам подтвердить эту идею.

Вместо того чтобы изобретать велосипед, мы изучили варианты для повышения масштабируемости и неблокирующей работы системы. Мы изначально рассматривали OpenSwoole, но из-за лицензионной проблемы мы не могли его использовать. Затем мы нашли ReactPHP, у которого была подходящая лицензия. Поэтому мы решили остановиться на ReactPHP.

ReactPHP — это простой PHP-фреймворк, который позволяет нам запускать TCP-сервер в асинхронном режиме.

Этот выбор хорошо работал для нас, поскольку он позволил нам обрабатывать несколько запросов одновременно и легко масштабировать систему.

Затем мы переписали наш простой Buddy MVP и создали основную структуру, которая упростит добавление обработки новых SQL-команд в будущем. Процесс таков:

  • Manticore Search получает SQL-запрос от пользователя и пытается его обработать.
  • Если Manticore Search может обработать запрос, он возвращает ответ клиенту без привлечения Buddy.
  • Если Manticore Search не может обработать запрос, он отправляет специальную структуру со всей информацией о входном запросе и любых ошибках в Buddy.
  • Buddy парсит структуру и проверяет, есть ли обработчик для него. Если его нет, он возвращает ту же ошибку, которую бы отправил Manticore Search клиенту.
  • Если все хорошо и у нас есть реализация для обработки запроса, мы разбиваем процесс на два шага: подготовка запроса с необходимыми данными и его обработка с логикой обработчика. Запрос представляет собой простую структуру, которая представляет класс с предопределёнными переменными и входными параметрами, разобранными из входного SQL-запроса. Если что-то пойдёт не так, он может завершиться неудачно и вернуть ошибку клиенту.
  • Обработчик затем получает запрос, выполняет необходимые действия и возвращает окончательный результат HTTP-запросу.

Эта система проста в обслуживании, проста и может быть легко расширена новой функциональностью. Однако есть одна проблема с этим подходом. Если у нас есть что-то тяжелое или необходимо подождать в Buddy, это может замедлить конкурирующие запросы. Это связано с тем, что, хотя асинхронный код не является параллельным, PHP по-прежнему блокирует код, и ReactPHP использует fibers для эмуляции асинхронного подхода. Мы обсудим эту проблему более подробно в следующем разделе.

Проблема асинхронности в PHP и масштабирование для параллелизма

Чтобы справиться с большими нагрузками, нашей команде в Manticore Search нужно было решение, которое могло бы обрабатывать больше запросов, чем ReactPHP. Хотя ReactPHP работал для реализации асинхронного HTTP-сервера и обработки некоторой конкуренции, его масштабируемости было недостаточно. После быстрого поиска мы решили использовать расширение parallel вместо pthreads из-за его поддерживаемости и надежности.

Но что такое параллель? Это о параллелизации, создании независимых Виртуальных Машин, которые представляют собой потоки, работающие параллельно. Эти потоки могут общаться через каналы, которые предоставляет Parallel, позволяя нам отправлять данные из основного процесса ReactPHP в параллельный и отсоединенный поток в другой Виртуальной Машине без изобретения колеса.

Этот подход стал нашим серебряным пули для обработки высоких уровней параллелизма и поддержания низкого времени отклика. Чтобы это реализовать, мы внедрили компонент Task для выполнения задач в параллельном потоке на уровне Обработчика. Таким образом, основной процесс не блокировался, позволяя нам легко обрабатывать множество параллельных запросов.

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

Хотя мы легко решили проблему производительности и параллелизма, мы столкнулись с другой задачей: развертыванием. Не все операционные системы поддерживают последнюю версию PHP 8, и некоторые редкие расширения не включены в стандартную установку. Но мы нашли решение, и вы тоже можете.

Поздороваться с manticore-executor

Мы провели исследование, чтобы найти простой способ доставки нового инструмента клиентам и обнаружили отличный подход - компилирование как <strong>PHP</strong>, так и Buddy в один статический бинарный файл . Это включало в себя внедрение PHP в его исходные файлы и создание бинарного файла, который мог бы работать. Однако мы столкнулись с препятствием, потому что хотели смешать различные лицензии - PHP 3.01 и GPL 2.0 - что было невозможно. В результате мы выбрали предкомпилированный PHP, связывая его статически, и назвали его manticore-executor.

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

Почему мы использовали GCC? Это было необходимо для компиляции PHP и его расширений. Проблема заключалась в том, что нам нужен был статически собранный GCC для статической линковки, что не просто и требует много работы. В результате мы искали альтернативы.

К счастью, мы обнаружили MUSL и Alpine, которые позволили нам собрать полностью статическую версию PHP со всеми необходимыми расширениями и библиотеками без особых трудностей! Кроме того, это работает на любой дистрибутиве Linux.

Alpine Linux является отличным выбором для компиляции C-программ благодаря своему небольшому размеру и легковесности, что делает его подходящим для систем с ограниченными ресурсами, такими как встроенные устройства или контейнеры. Кроме того, Alpine Linux безопасен. Он использует защищенное ядро и немного пакетов, ограничивая поверхность атаки и делая его менее уязвимым для угроз безопасности. Это особенно важно для C-программ, которые могут быть подвержены уязвимостям безопасности.

Кроме того, Alpine Linux использует MUSL libc в качестве своей стандартной C-библиотеки, которая является легковесной и эффективной C-библиотекой, что приводит к более быстрому и эффективному коду, чем другие C-библиотеки.

В результате мы использовали его и настроили действия для использования образа Alpine и сборки его в Docker. Прелесть этого подхода в том, что он также упростил нам сборку для ARM, потому что Docker имеет команду buildx, позволяя нам использовать QEMU в готовой схеме сборки и достигать того же потока для сборки как для архитектуры AMD, так и ARM на одной машине! Ознакомьтесь с нашим потоком сборки здесь .

Github Actions автоматизирует сборку и развертывание пакетов для всех поддерживаемых операционных систем. Для пользователей установка проста: просто запустите apt-get install manticore-executor или yum install manticore-executor, и у вас будет версия PHP, готовая к использованию, со всеми необходимыми пакетами, предустановленными для запуска любого проекта PHP, поставляемого Manticore. Легко!

Как мы поставляем наш исходный код

В Manticore Search мы столкнулись с задачей предоставления нашего PHP-приложения, состоящего из нескольких файлов исходного кода, пользователю. У нас было много файлов, разбросанных по отдельным папкам, и зависимости, которые нужно было устанавливать с помощью Composer, что усложняло процесс установки.

Как вы помните, мы разработали пользовательскую версию PHP, называемую manticore-executor , которую можно было легко установить из репозиториев. Однако это все еще не решало проблему предоставления всего PHP приложения пользователю.

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

Чтобы сделать пакет исполняемым, мы решили использовать скрипт Bash и Shebang с нашим пакетом Manticore-Executor. Этот скрипт проверяет дату изменения PHAR в временной папке системы и извлекает данные PHAR там, позволяя несколько запусков оставаться производительными и актуальными по новым установленным версиям. Для получения дополнительной информации о том, как мы реализовали это, вы можете обратиться к нашему проекту phar_builder на GitHub.

Извлеченные уроки

  1. Начинайте с простого, базового решения, не используя паттерны проектирования, когда не уверены в будущем успехе проекта. Приоритизируйте валидацию сначала, а затем рефакторите и вносите изменения в обновления.
  2. Параллелизм в PHP может быть сложным, но использование потоков и асинхронных фреймворков может помочь достичь высокой пропускной способности. Для оптимальной производительности рекомендуется использовать оба. Предварительное выделение времени выполнения для потоков может помочь достичь желаемой производительности.
  3. Упростите процесс доставки для пользователей. Сократите количество необходимых инструкций. В нашем случае один PHAR-архив и один бинарный файл со всеми включенными расширениями для нашего кастомного PHP решали проблему.
  4. Используйте самые последние версии PHP или других инструментов, чтобы быть в курсе последних разработок и обеспечивать безопасность ваших данных. Устаревшее программное обеспечение может быть уязвимо для хакерских атак и нарушений безопасности. Обновление предлагает улучшенную производительность, новые функции и эффективный процесс кодирования.
  5. Ищите пакеты, которые могут решить вашу проблему, и изучайте их зависимости. Выбирайте пакеты с минимальными зависимостями, чтобы избежать адской зависимости. Используйте небольшие пакеты как строительные блоки, а не создавайте индивидуальные решения.

Итог

На протяжении разработки Buddy мы столкнулись с множеством проблем, которые мы преодолели с восторгом. Инструмент полностью написан на PHP и поставляется как пакет ОС, что делает его невероятно простым для установки пользователями и для нас в поддержке и автоматизации сборок благодаря GitHub Actions. Хотя есть еще возможности для улучшения, чтобы сделать инструмент еще проще, наша история демонстрирует, как можно создать легко поддерживаемый и легко устанавливаемый инструмент, используя всю мощь PHP.

Мы надеемся, что вам понравилось читать о нашем пути к выпуску Manticore 6.0.0 . Не забывайте следить за нашей следующей статьей, в которой мы исследуем новый плагинный дизайн в Buddy и его удобную для участия экосистему, benefiting все сообщество. Это действительно захватывающее время, и мы не можем дождаться, чтобы поделиться с вами еще больше.

Установить Manticore Search

Установить Manticore Search