Если вы создаете бэкенд-сервисы на Rust, то знаете, что производительность и безопасность памяти — основные преимущества этого языка. Но даже при наличии абстракций с нулевой стоимостью ваше приложение может может столкнуться с узким местом, если постоянно создает и уничтожает ресурсоемкие сложные объекты.

Будь то открытие соединения с базой данных, создание сетевого клиента или выделение больших байтовых буферов для обработки данных — накладные расходы на инициализацию могут серьезно снизить пропускную способность.

Решить проблему поможет Rust-порт esox_objectpool. Точно адаптированный из высокопроизводительной .NET-библиотеки с тем же названием, этот крейт обеспечит потокобезопасный пулинг объектов в экосистему Rust.

Именно поэтому стоит рассмотреть его для управления ресурсоемкими задачами  приложения.

Проблема сложных объектов

Поговорим о соединениях с базами данных. Создание нового соединения с базой данных — не просто выделение структуры в памяти. Этот процесс включает в себя, среди прочего, разрешение DNS-имен, обмен пакетами TCP, согласование TLS и аутентификацию в базе данных. Выполнение этих действий при каждом входящем HTTP-запросе приведет к резкому увеличению задержки.

Аналогичная ситуация возникает, когда приложение обрабатывает большие файлы или данные изображений. Постоянное выделение и освобождение больших буферов Vec<u8> приведет к фрагментации кучи и неоправданным затратам процессорного времени на управление памятью.

Пулинг объектов решает эту проблему, поддерживая резервуар инициализированных, готовых к использованию объектов. Вместо создания нового ресурса, вы берете его из пула. Когда заканчиваете, просто возвращаете его.

Почему esox_objectpool идеально подходит для ресурсоемких задач

Этот крейт не просто обеспечивает очередь объектов; он специально разработан для сложностей современной бэкенд-архитектуры. Вот как именно он помогает управлять соединениями с базами данных и сложными структурами:

  • Возврат без лишних усилий через RAII. В некоторых языках невозвращение объекта в пул (по причине вашей забывчивости, например) оборачивается утечкой ресурсов. Этот крейт использует модель владения Rust и трейт Drop. Когда интеллектуальный указатель PooledObject<T> выходит за пределы области видимости, он автоматически и безопасно возвращается в пул. Вы просто не можете его потерять.
  • Динамические фабрики и прогрев. Для соединений с БД редко требуется инициализировать 100 соединений при запуске, если вам нужно только 5. DynamicObjectPool<T> позволяет определить фабричную функцию, которая создает соединения по требованию. И наоборот, можно использовать методы warmup() или warmup_async() для предварительного заполнения базового количества соединений, полностью устраняя задержку холодного старта для первых пользователей (или первых запросов, в зависимости от ситуации).
  • Запрашиваемые пулы (queryable pools) для интеллектуальной маршрутизации. Представьте себе пул соединений с БД, где одни соединения ведут на первичный сервер для записи, а другие — на реплики только для чтения. QueryableObjectPool<T> позволяет запрашивать объект на основе заданного предиката (условия).
  • Устойчивость и самовосстановление. Соединения с базами данных обрываются. Сетевые клиенты зависают. Этот пул включает встроенные паттерны Circuit Breaker для защиты приложения от каскадных отказов. Кроме того, можно настроить TTL (время жизни) и тайм-ауты бездействия, чтобы устаревшие или неработающие соединения автоматически удалялись и заменялись.

Нативная асинхронность и бескомпромиссная производительность

Если вы работаете с соединениями с базами данных на Rust, то наверняка используете tokio. Этот пул обеспечивает первоклассную асинхронную поддержку с async/await, включая возможности тайм-аутов и отмены операций.

«Под капотом» он отказывается идти на компромисс в скорости. Все операции являются потокобезопасными и безблокировочными, используя:

  • crossbeam::queue::ArrayQueue для неблокирующей многопоточной очереди MPMC (multiple-producer, multiple-consumer);
  • dashmap::DashMap для быстрого многопоточного хеш-отображения;
  • parking_lot::RwLock для высокооптимизированных блокировок чтения-записи.

Получение и возврат объекта выполняется со сложностью O(1).

Вот как это работает

Этот краткий обзор наглядно демонстрирует, как можно настроить динамический пул для соединений с базами данных, с тайм-аутами, максимальной емкостью и политиками вытеснения:

use objectpool::{DynamicObjectPool,PoolConfiguration};
use std::time::Duration;
use std::thread;
use rand::Rng;

#[tokio::main]
async fn main() {
    let config=PoolConfiguration::new()
        .with_max_pool_size(100)
        .with_max_active_objects(50)
        .with_idle_timeout(Duration::from_mins(10))
        .with_timeout(Duration::from_secs(5));

    let pool=DynamicObjectPool::new(|| {
        println!("Establihing new database connection...");
        establish_expensive_connection()
    }, config);

    pool.warmup_async(10).await.unwrap();

    {
        let connection=pool.get_object_async().await.unwrap();
        let value=connection.unwrap();
        println!("{:?}", value); //object automatically returned to pool
    }
}

fn establish_expensive_connection() -> u64 {
    let secs = rand::thread_rng().gen_range(1..=5);
    println!("Connecting... (will take {} second(s))", secs);
    thread::sleep(Duration::from_secs(secs));
    println!("Connection established.");
    secs
}

Добавьте эти три строки в раздел зависимостей вашего файла Cargo.toml:

[dependencies]
objectpool={ package="esox_objectpool", version="1" }
tokio={ version = "1", features = ["full"] }
rand = "0.8"

Заключение

Если приложение, создаваемое на Rust, должно управлять соединениями с базами данных, сетевыми клиентами или большими буферами данных без потери производительности, esox_objectpool предоставляет надежное, проверенное и идиоматическое решение.

Имейте в виду, это первая версия, поэтому в ней могут быть ошибки. 


Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи The Code Nomad: Stop Reallocating: Supercharge Your Rust Backend with the esox_objectpool crate

Предыдущая статьяМост под названием «Инженерия»