⚠️ Эта страница автоматически переведена, и перевод может быть несовершенным.
blog-post

Manticore Buddy: challenges and solutions

Hey there! 🤗 We hope y'all have already checked out our Buddy Intro and have a good understanding of how it works. We want to share our journey and experiences developing it and the challenges we faced.

At Manticore Software, we encountered two main challenges:

  • Expanding Manticore Search with non-performance critical features without modifying C++.
  • Making it easier to contribute to enhancements and new feature implementations.

We were determined to find a solution. So, let's dive into our journey to develop Buddy and the issues we faced. Ready? Let's go!

The beginning of the journey

Our journey started by examining the issue closely. Although Manticore Search is an exceptional search database product, we faced difficulties in releasing new features at the desired speed because the codebase is written in C++. Writing C++ code requires low-level interaction with data structures, bytes, deep knowledge of how the machine works, compilation, debugging, and finding the best approach to write it, which takes a great deal of time but results in faster program execution. This drastically delayed the development process. Although C++ is a great option for performance, it takes time to develop. We wanted

to move quickly, ship more features, and do so consistently.

  • We came up with the idea of creating a companion for our primary searchd process, which could process failed queries from Manticore Search and return results to the original client. We didn't take long to decide which language to use and settled on PHP for several reasons:
  • Most of the core team was familiar with it, so it would take less time to make it work.
  • PHP is fast, especially with the newest version (8+), even when we did not require performant execution from the Buddy. It's faster than Python or JavaScript, so it was a good fit for our requirements.

PHP is not only fast but also simple, reducing the level of expertise needed to contribute to the future ecosystem.

That's why we chose PHP and began implementing basic code to understand what we would need later.

We still use C++ to develop speed-critical features. C++ is ideal for tasks that require speed. For tasks that don't need much speed, Buddy is the optimal choice.

This is how Buddy was created. To make this possible on the C++ side of Manticore Search, we implemented a separate loop and communication between the searchd daemon and the Buddy PHP process using the CURL extension. We developed our internal protocol, which is a simple JSON, to route queries to Buddy; it handles these queries and sends us an appropriate response to be passed back to the original client.

Implementing the communication protocol

When starting a new project, it's important to stay flexible and not overplan. In our case, we began with a basic implementation of communication using the sockets extension in PHP. While it worked well, it wasn't

scalable. Our goal was to connect Manticore Search with Buddy, and this simple implementation
allowed us to validate that idea.

Instead of reinventing the wheel, we researched options for making the system more scalable and non-blocking. We initially considered OpenSwoole, but due to a license issue, we couldn't use it. We then found ReactPHP, which had a suitable license. So, we decided to go with ReactPHP.

ReactPHP is a plain PHP framework that allows us to run a TCP server in async mode.

  • This choice worked well for us since it allowed us to handle multiple requests simultaneously and easily scale the system.
  • Next, we rewrote our simple Buddy MVP and created a core structure that would make it easy to add handling of new SQL commands in the future. The process is as follows:
  • Manticore Search receives an SQL query from the user and attempts to handle it.
  • If Manticore Search can handle the query, it returns the response to the client without involving Buddy.
  • If Manticore Search cannot handle the query, it sends a special structure with all information about the input query and any errors to Buddy.

Buddy parses the structure and checks if there is a handler for it. If there isn't, it returns the same error that Manticore Search would send to the client.

If everything is good and we have an implementation to handle the query, we split the process into two steps: preparing the request with the required data and handling it with the handler logic. The request is a simple structure that represents a class with predefined variables and input parameters parsed from the input SQL query. If anything goes wrong, it may fail and return an error to the client.

The handler then receives the request, does the necessary work, and returns the final result to the HTTP request.

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

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

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

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

В результате мы использовали её и настроили действия для использования образа 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