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-сервер в асинхронном режиме.

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

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

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

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

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

Хотя мы легко решили проблему производительности и параллелизма, мы столкнулись с другой проблемой: развертыванием. Не все операционные системы поддерживают последнюю версию 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 и его легкую для участия экосистему, которая приносит пользу всему сообществу. Это действительно захватывающее время, и мы не можем дождаться, чтобы поделиться с вами еще больше.

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

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