Недавно PHP-проекты Avito перешли на версию PHP 7.1. По этому случаю мы решили вспомнить, как происходил переход на PHP 7.0 у нас и наших коллег из OLX. Дела давно минувших дней, но остались красивые графики, которые хочется показать миру.
Первая часть рассказа основана на статье PHP’s not dead! PHP7 in practice, которую написал наш коллега из OLX Łukasz Szymański (Лукаш Шиманьски): переход OLX на PHP 7. Во второй части — опыт перехода Avito на PHP 7.0 и PHP 7.1: процесс, трудности, результаты с графиками.
Часть 1. PHP 7 в OLX
Компания OLX Europe управляет десятью сайтами, самый большой из которых — OLX.pl. Все наши сайты должны работать максимально эффективно, поэтому миграция на PHP 7 стала для нас основным приоритетом.
В этом посте расскажем, с какими проблемами пришлось столкнуться и чего удалось получить с переходом на PHP 7. Про переход было рассказано на конференции PHPers Summit 2016.
Переход
Вопреки нашим опасениям, миграция прошла гладко. За исключением стандартного списка необходимых изменений из официальной документации, пришлось внести лишь некоторые правки, связанные с нашей архитектурой.
Стоит упомянуть, что десять наших сайтов работают в разных странах. И изменения мы выкатываем последовательно: на один сайт за другим. Такой подход особенно важно применять при серьёзных изменениях.
Мы начали обновление версии с самого маленького сайта и переходили ко всё более крупным, поглядывая, чтобы тесты проходили успешно. Это позволило следить за возникновением неожиданных проблем и снизило потенциальный ущерб.
Memcache
Отказ от поддержки Memcache в PHP 7 подтолкнул нас к переходу на Memcached. Пришлось поддержать две версии сайта: PHP 5 + Memcache и PHP 7 + Memcached.
Для решения задачи использовали простенькую обёртку. Она выбирает подходящий PHP-модуль для соединения с кэшом, исходя из информации о сервере, на котором выполняется код.
<?php
$factory = new CacheWrapperFactory();
$factory->createServer(
extension_loaded('memcache') ? 'memcache' : 'memcached',
$uri
);
Однако, приключения с Memcached на этом не закончились. Выяснилось, что объекты, сериализованные с помощью Memcache, не могут быть десериализованы с помощью Memcached.
Но кэш — он на то и кэш, что его можно легко удалить. Поэтому мы просто удалили старые проблемные объекты из кэша, и они были пересозданы с помощью нового модуля.
APC, APCu и OPCache
Немного о терминах.
APC (Alternative PHP Cache) — это кэш байт-кода и пользовательских данных.
APCu (APC User Cache) — только кэш пользовательских данных.
OPCache — только кэш байт-кода.
В PHP 7 нет APC, нам пришлось взять и APCu, и OPCache. Ранее мы использовали API APC во многих критичных частях нашего фреймворка, управляющего кэшем, поэтому мы прикрутили к APCu модуль apcu-bc, совместимый с API APC.
Результаты
Ниже представлены результаты самого большого из наших сайтов, OLX.pl.
CPU на Apache
Наши веб-серверы (Apache) крутятся на 20 физических машинах с 32 ядрами процессора каждая. Пиковое потребление процессора в результате миграции снизилось с 50% до 20%.
LA на Apache
Подобным образом снизился и Load Average на веб-серверах.
Числа говорят сами за себя: снижение нагрузки, экономия ресурсов, повышение эффективности. Если ваши проект-менеджеры или клиенты не дают достаточно времени на миграцию — эти графики наверняка смогут их убедить.
Причины эффективности PHP 7
Таких поразительных результатов удалось добиться благодаря оптимизации в трёх основных областях.
Меньше операций выделения памяти
PHP 5 расходовал 20% потребляемого им процессорного времени только на операции выделения памяти. Разработчики языка обратили на это внимание, уменьшили количество операций выделения памяти, чем значительно снизили потребление процессора.
Меньше потребление памяти
Этот набросок показывает путь, который процессору нужно пройти для доступа к оперативной памяти (RAM), с указанием времени на каждый шаг. Как видно, отрезок до оперативной памяти — самый длинный. Разработчики PHP снизили потребление памяти, ускорив отклик приложений.
Меньше промежуточных указателей
Последняя причина улучшения эффективности PHP 7 — уменьшение числа промежуточных указателей. Для этого разработчики PHP избавились от множества указателей, ссылающихся на другие указатели; вместо этого они заставили указатели ссылаться непосредственно на запрашиваемые сущности.
Код
Помимо улучшения эффективности, PHP 7 припас небольшую революцию структуры кода.
Скаляры в объявлении функции
До PHP 7 типами аргументов функции могли выступать только объект, интерфейс, массив или функция обратного вызова (callable). Возьмём для примера следующий код.
Очевидно, что использование таких методов сулит множество проблем, если вы не уверены в корректности входящих аргументов.
Помимо добавления проверок типов в каждый метод, можно использовать конструкт ValueObject из предметно-ориентированного программирования (DDD).
PHP 7 позволяет просто указать скаляры, такие как string, int, float, bool. Кроме того, можно указать тип возвращаемого значения.
Strict-режим
Но простое добавление вышеуказанных конструкций в код может не дать ожидаемых результатов. Всё потому, что PHP 7 по умолчанию работает в coercive-режиме, в котором происходят обычные преобразования типов. Если вы принудительно не включили strict-режим, можно переписать вышеуказанный метод следующим образом.
К сожалению, даже добавление конструкции declare(strict_types=1) в файл PHPisNotDead.php не включает strict-режим. Объяснение в примере ниже. В комментариях указаны возвращаемые значения.
Почему так происходит? Строгая типизация в методах класса PHPisNotDead включилась только для возвращаемых значений.
Если вам нужно включить строгую типизацию также и для аргументов, придётся добавить декларацию strict_types и во все файлы, в которых вызываются методы класса.
Больше информации об указании типов и его влиянии на выполнение программы можно прочитать в документации. Чтение этой документации убережёт от сюрпризов при выполнении вашего кода.
Будущее
Если даже сейчас вы всё ещё не решились перейти на PHP 7, загляните в список поддерживаемых версий PHP. Версия 5.6 уже не получает активной поддержки, а в конце 2018 года перестанут выходить даже исправления критических уязвимостей. Активная поддержка продолжается для версий 7.0 и выше.
Следите за новостями мира PHP и планами относительно новых верий. Наиболее интересные посты: дружественные классы, generic-типы и функции. Найти другие предложения о развитии языка можно в PHP RFC.
Итоги
PHP 7 впечатляет: помимо повышения эффективности, он помогает разработчикам писать более качественный код. Я набросал небольшое пособие, которое поможет вам принять решение о переходе.
— Когда стоит перейти на PHP 7?
— Прямо сейчас.
Часть 2. PHP 7 в Avito
Процесс перехода на PHP 7.0
Мы так же, как и OLX, осуществляли перевод наших сервисов на PHP 7 постепенно. Сначала перевели небольшие отдельностоящие сервисы, протестировали их, исправили возникшие ошибки. Далее перешли к последовательному обновлению серверов админки сайта, после чего раскатывали на PHP 7 оставшиеся сервисы и сайт.
Трудности перехода
Мы прочитали список обратно несовместимых изменений. Однако это не уберегло ото всех бед.
В старом коде нашёлся класс с названием String. PHP 7 выдал ошибку “Cannot use 'String' as class name as it is reserved”. Класс переименовали. Обратите внимание на список зарезервированных слов.
В PHP 7 изменился формат кеш-файла для WSDL-схемы в SoapClient. Можно настроить сохранение кэша в разные директории в зависимости от версии PHP или полностью очищать кэш перед сменой версии интерпретатора.
Расширение mongo, которое мы активно использовали, перестало поддерживаться в новом PHP. Вместо него мы стали использовать официальную библиотеку MongoDB PHP Library. Прошлись по всему коду и заменили MongoCollection::insert() на MongoDBCollection::insertOne(), MongoCollection::remove() на MongoDBCollection::deleteMany() и далее по списку. Стали использовать классы для работы с BSON из нового драйвера MongoDB, например, MongoDate вместо MongoDBBSONUTCDateTime.
Результат
Админке полегчало.
Бэкенд сайта тоже доволен.
Обновление до PHP 7.1
В PHP 7.1 появилось несколько очень приятных нововведений: тип void для возвращаемых значений, iterable, возможность вернуть null для типизированных возвращаемых значений, область видимости констант и прочее. К тому же, период активной поддержки PHP 7.0 заканчивается уже в конце этого года. Решили обновиться до PHP 7.1.
Неожиданности
При обновлении на ровном месте образовалась проблема. Пакет php-memcached для 7.1 потянул за собой пакет php-igbinary. Когда поставили PHP 7.1 на один из боевых серверов, с остальных серверов начали сыпаться ошибки, в которых фигурировало слово “igbinary”.
Старый друг Memcache, снова различия сериализации, но немного под другим соусом. Выяснилось, что модуль php-memcached по умолчанию использует первый доступный сериализатор из списка: igbinary (в отдельном модуле), msgpack (в отдельном модуле), php (не требует отдельного модуля, доступен всегда). И тот сервер, на котором мы поставили 7.1 с igbinary, начал записывать данные в мемкеш, сериализованные igbinary. А на остальных серверах не было поддержки этого сериализатора, и они не могли прочитать данные, записанные сервером с обновлённым PHP. Локализовали проблему, установили igbinary на всех остальных серверах, ошибки прекратились.
Послесловие
Разработчики PHP взяли хороший курс. Они добавляют полезные инструменты, избавляются от недостатков, связанных с наследием языка, всерьёз задумываются о производительности.
Ранее мы уже рассказывали о переходе Avito на сервисную архитектуру (раз, два). Такая архитектура позволяет писать на любых языках, и новые сервисы мы чаще всего пишем на Go или Python’е. Однако об отказе от PHP речи не идёт: основная логика сайта (монолит) всё ещё на PHP, а команда отлично знает, как с ним работать. Новые версии интерпретатора позволяют сделать код лучше, а его выполнение быстрее.
Делитесь вашим опытом перехода на PHP 7 и выше, будем рады обсудить открытия и грабли, которые встретились вам на этом пути.
Автор: pik4ez