
Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. После большого релиза ? userver прошло почти два года. За это время мы обзавелись большим количеством внешних пользователей — международных и российских. При этом и количество внутренних пользователей подросло: в Городских сервисах Яндекса появились стни новых сервисов на userver. Функциональность Такси, Еды, Лавки, Доставки, а также Маркета, Финтеха, Фантеха, Электро и Техплатформы обогатилась новыми возможностями и новыми пользователями. А значит, фреймворк стал ещё надёжнее и оттестированнее.
Мы не сидели сложа руки, и за два года реализовали, оптимизировали и добавили все обещанные в прошлой статье фичи, а также многое другое:
популярные запросы пользователей: OpenTelemetry Protocol (OTLP) для отправки метрик, кастомизирование логирования и JSON‑формат логирования, OpenAPI‑парсеры и сериализаторы для JSON Schema и так далее и тому подобное;
библиотеку easy для простого прототипирования;
потребление CPU и памяти;
базовую поддержку HTTP2;
рецепты для Conan;
множественные улучшения для gRPC;
веб‑интерфейс для сервиса динамических конфигов;
сотни других упрощений и улучшений.
Ах да, ещё мы решили отбросить поддержку C++17 и начали активно переводить кодовую базу на использование фич C++20.
C++20
На дворе 2026 год, однако разработчики компиляторов C++ живут по своему календарю! Вышедший шесть лет назад стандарт С++ только в следующем месяце перестанет считаться экспериментальным и будет дефолтом для GCC-16. И это не касается C++20 Modules.
Итак, есть только один компилятор, который «зуб даёт», что готов к C++20. Не рано ли переводить фреймворк на C++20?
А давайте посмотрим на результаты прошлогоднего опроса наших пользователей:

77% пользователей уже на C++20, а ещё 15% готовы переключиться! Для сравнения — вот общемировая статистика по использованию C++ от 2025 года:

Вывод: наши пользователи самые прошаренные. И они готовы. Списавшись с крупными потребителями напрямую, мы ещё раз убедились в репрезентативности опроса. Так что, если вдруг вы хотите обновить технологию, но опасаетесь, что ваши пользователи не готовы, проведите опрос. Возможно, его итоги вас приятно удивят.
А зачем вообще C++20, почему не хватает C++17?
Часть наших компонент уже требует C++20. Например, YDB SDK существует только под C++20. И наши асинхронные обёртки над ним, соответственно, тоже. Но вот другие компоненты уже долгое время живут на C++17 и бед не знают. Так зачем же C++20?
Всё очень просто. После того как стало понятно, что ничего не мешает нам перейти на C++20, мы стали замечать неприятные шероховатости C++17:
гетерогенные поиски для unordered‑контейнеров приходится эмулировать через Boost‑контейнеры;
std::void_tиstd::enable_ifнеудобны по сравнению с концептами в C++20;вместо
constevalприходится использовать макрос, который раскрывается вconstevalили вconstexpr(последний не позволяет обнаруживать часть проблем на этапе компиляции);std::spanнедоступен, приходится писать свойutils::span;самописные
rangesтрудозатратно поддерживать, аboost::transformтянет тяжёлые заголовочные хедеры в публичные заголовочные файлы userver;очень не хватает
std::atomic_ref;operator<=>() = default;— катастрофически не хватает в кодгене.
А ещё приходится тестировать фреймворк сразу в двух режимах — C++17 и C++20.
В общем, со временем эти и многие другие шероховатости стали добавлять всё больше головной боли.
Новинки userver
userver::easy
Если вы собираетесь делать микросервис с большой функциональностью и разрабатывать его отдельной командой разработчиков — userver тут как нельзя кстати. На Хабре есть даже введение в написание подобных сервисов.
Вот только не все задачи требуют столь вдумчивого подхода. Иногда нужно тяп‑ляп — и в продакшен запрототипировать решение. Другими словами, нужен аналог Flask:
from flask import Flask app = Flask(__name__) @app.route("/") def hello_world(): return "<p>Hello, World!</p>"
Да при том такой, что, если вдруг прототип выстрелит и разрастётся в популярный сервис, его можно было бы довести до более поддерживаемого состояния.
Именно так и появился userver::easy, где можно писать код в близком к Flask стиле:
#include <userver/easy.hpp> int main(int argc, char* argv[]) { userver::easy::HttpWith<>(argc, argv) .DefaultContentType(userver::http::content_type::kTextPlain) .Route("/", [](const userver::server::http::HttpRequest& /*req*/) { return "<p>Hello, World!</p>"; }); }
При этом библиотека достаточно функциональная. Её возможностей достаточно, чтобы писать CRUD«ы:»
#include <userver/easy.hpp> // schemas::KeyRequest и парсеры+сериализаторы, сгенерированные из JSON Schema #include "schemas/key_value.hpp" int main(int argc, char* argv[]) { using namespace userver; easy::HttpWith<easy::PgDep>(argc, argv) // Конкурентно обрабатывает множественные HTTP-запросы к /kv .Get("/kv", [](schemas::KeyRequest&& request, const easy::PgDep& dep) { // Асинхронное выполнение SQL-запроса в транзакции. Текущий поток // выполнения обрабатывает другие запросы, пока ответ от сервера // базы данных получаем по сети: auto res = dep.pg().Execute( storages::postgres::ClusterHostType::kSlave, // Запрос преобразуется в prepared statement. Последующие // запросы пошлют только параметр запроса в бинарном формате. "SELECT value FROM key_value_table WHERE key=$1", request.key ); return schemas::KeyValue{ .key=std::move(request.key), .value=res[0][0].As<std::string>(), }; }); }
Заметьте, все оптимизации userver присутствуют. Код выглядит как синхронный, а на самом деле это высокоэффективный асинхронный код с целой прорвой оптимизаций, в том числе для баз данных (некоторые из оптимизаций мы описывали в статье к прошлому релизу, другие — в отдельной статье, а ещё говорили о них на конференциях C++ Russia и C++ Zero Cost Conf).
В документации к userver::easy есть несколько разделов по расширению и постепенному приведению к непрототипному состоянию (если вдруг это нужно). Так что вы не окажетесь в ситуации «прототип хороший, но надо всё переделать с нуля».
Кстати, о переделывании...
Chaotic
Где чаще всего косячат программисты? В повторяющемся однотипном коде! Типичным таким «унылым местом» является парсинг JSON/YAML/BSON/... в структуру — взяли поле, сконвертировали в нужный тип, провалидировали, повторили с другим полем...
Рецепт, как косячить меньше, всем известен: доверить унылую работу машине. Просто описываем схему типа данных, а отдельная программа генерирует по схеме парсеры, сериализаторы и саму структуру.
Например, в прошлом примере часть JSON‑схемы выглядит так:
components: schemas: KeyValue: type: object additionalProperties: false properties: key: type: integer value: type: string
Программа для генерации кода на C++ из JSON Schema в userver называется Chaotic. Она сгенерирует приблизительно следующий заголовочный файл:
#pragma once #include <cstdint> #include <optional> #include <string> #include <userver/chaotic/object.hpp> #include <userver/chaotic/type_bundle_hpp.hpp> #include "key_value_fwd.hpp" namespace schemas { struct KeyValue { static constexpr userver::utils::StringLiteral kFieldNamekey = "key"; static constexpr userver::utils::StringLiteral kFieldNamevalue = "value"; std::optional<int> key{}; std::optional<std::string> value{}; }; bool operator==(const KeyValue& lhs, const KeyValue& rhs); userver::logging::LogHelper& operator<<( userver::logging::LogHelper& lh, const KeyValue& value); KeyValue Parse( userver::formats::json::Value json, userver::formats::parse::To<KeyValue>); KeyValue FromJsonString( std::string_view json, userver::formats::parse::To<KeyValue>); userver::formats::json::Value Serialize( const KeyValue& value, userver::formats::serialize::To<userver::formats::json::Value>); } // namespace schemas
Тут есть парсеры из DOM‑представления, чтобы можно было делать json_value.As<schemas::KeyRequest>(). И SAX‑парсеры из JSON‑строки, чтобы парсить в ту же структуру быстрее, минуя DOM‑представление. И сериализаторы.
У подобного подхода, помимо снижения уровня ошибок, есть ещё один большой плюс: у вас есть схема! А значит, с вашим сервисом смогут общаться другие сервисы на других языках программирования — надо только дать им эту схему.
Сборка и Conan
Для быстрого прототипирования недостаточно удобной библиотеки и генератора из JSON Schema. Фреймворку надо ещё уметь быстро собираться и легко устанавливаться.
За два года мы провели большую работу по ускорению сборки: уменьшали количество транзитивных инклюдов, упрощали код и шаблоны. А вот система сборки — это древняя проблема C++, у которой нет одного очевидного решения. Поэтому мы решили одним решением не ограничиваться:
сделали новые Docker‑контейнеры с предустановленными библиотеками и userver;
стали собирать DEB‑пакеты с каждым релизом уже под пару версий Ubuntu;
провели работу с CMake, чтобы некоторые зависимости можно было подтягивать через CPM;
стали тестироваться на намного большем количестве ОС;
запустили программу комьюнити мейнтейнерства и обзавелись крутым мейнтейнером для платформы Gentoo;
переработали и упростили Conan‑скрипты, реализовали автоматическое тестирование Cоnan на разных ОС и в разных окружениях.
Более того, мы находимся в процессе добавления userver в Conan Center, чтобы можно было начать использовать userver для своего проекта, просто написав в conanfile.txt...
[requires] userver/2.*
.. и запустив conan install.
Оптимизации
Бытует мнение, что самые лакомые задачи для разработчиков на С++ — это написание своего фреймворка и задачи на оптимизацию производительности.
Вот некоторые из оптимизаций:
Мы стали чаще использовать аналоги
std::string::resize_and_overwrite(). Казалось бы, компиляторы должны быть уже достаточно умными, чтобы самим оптимизировать dead store, но нет. Использование этой функции на «горячем пути» помогло нам увеличить максимальный RPS на 1–3%.В популярных типах данных userver мы перешли на использование view в
std::initializer_list. Это позволило избежать лишнего копирования данных в ряде краевых случаев.Старый добрый
std::FILE*на самом деле обладает одной не очень приятной особенностью: он захватывает мьютекс для защиты внутреннего буфера. Отключение этого мьютекса даёт до 0,2% ускорения логирования и ~40% ускорения при дампе кешей из чисел.Из C++29 мы тоже взяли некоторые идеи, в частности
zstring_view. Теперь большинство интерфейсов не требуютconst std::string&, а значит, будет создаваться меньше временных строк.Креативно перетрясли низкоуровневую работу с таймерами и балансировку нагрузки на потоки и обработчики событий. Сэкономили CPU и упростили настройку.
Инициализацию запросов сделали полностью compile time, убрав возможность получить
static initialization order fiasco.Kafka‑драйвер стал матёрым и полностью асинхронным и не блокирующим потоки ОС. Кажется, что мы единственные в мире, кто дошёл до этого (пишите в комментах, если ситуация изменилась).
Упрощение конфигурирования
Конфигурирование современных систем — весьма мудрёная задача. Многие серверные конфиги могут занимать несколько экранов и быть раскиданы по множеству файлов. Мы стараемся быть в этом месте проще.
Размеры конфигов в userver‑туториалах сократились ещё сильнее благодаря разумным дефолтам. А userver::easy позволяет не только завести сервис без явного написания конфига, но и дампнуть конфиг вашего прототипа, чтобы переопределить его новыми значениями при необходимости.
Контрибьюторы
Отдельное спасибо всем тем, кто работал над проектом! За два года было сделано невероятное количество улучшений от сторонних пользователей — как маленьких исправлений, так и огромных добавлений новой функциональности. На userver было сделано множество дипломных проектов, больших и крутых оптимизаций.
Из самого запоминающегося:
переиспользование SSL_CTX, что значительно ускоряет работу с шифрованными соединениями и существенно сокращает потребление памяти;
множество улучшений для Kafka, включая поддержку SSL, headers и seek;
SQLite‑драйвер;
S3 API клиент;
работа Valkey/Redis в режиме standalone;
асинхронный UDP Multicast;
множественные улучшения в документации, сервисах‑примерах и сервисе динамических конфигов;
безумное количество фикстов для сборки под используемые контрибьюторами платформы;
много других улучшений.
Ещё раз огромное спасибо всем контрибьюторам! Вы лучшие!
Дальнейшие планы
Разумеется, работа над фреймворком продолжится. Мы видим, что фреймворк людям интересен, а это мотивирует продолжать работу. Да и в Яндексе фреймворк крайне популярен.
В ближайших планах — сделать всё что можно для удобной и надёжной работы с gRPC.
На этом пока всё, до новых встреч! Заходите на наш github, мы теперь делаем минорные релизы каждые пару месяцев!
А ещё рекомендую заглянуть на следующие мероприятия, где можно встретиться с людьми, использующими или разрабатывающими userver:
Zero Cost Conf (ожидайте анонсов!)
Кстати, на конференциях мы иногда раздаём прикольных осьминожек. Это случается редко и спонтанно (даже для нас самих).
