Skip to content

Производительность и оптимизации

fast_ack

На hot path сервера (ack паблишеру при получении сообщения) вызывается Packet::fast_ack(nonce) вместо Packet::ack():

// Обычный ack: SystemTime::now() + rand::random::<u64>() → ~50ns
pub fn ack(&self) -> Packet { Packet::new(PacketType::Durable, Data::Ack(self.nonce)) }

// fast_ack: timestamp=0, nonce=0 → ~0ns overhead
pub fn fast_ack(nonce: u64) -> Packet {
    Packet { data: Data::Ack(nonce), packet_type: PacketType::Durable,
             timestamp: 0, nonce: 0, seq: None }
}

При 1M сообщений это экономит ~50ms только на ack generation.

Arc в delivery tracking

Сервер оборачивает входящий пакет в Arc<Packet> один раз. Все подписчики получают клоны Arc (O(1) atomic increment) вместо клонов самого пакета (O(n) копия payload). Для 16KB сообщений и 10 подписчиков: 160KB → 80 bytes.

Zero-copy serialization

serialize_into(&mut BytesMut) пишет напрямую в буфер кодека через BytesMut::put_*() методы. Нет промежуточного Vec<u8>.

Десериализация в PacketCodec::decode() использует BytesMut::freeze()Bytes (O(1), Arc bump). Message хранит Bytes — данные не копируются, ссылаются на оригинальный буфер.

Батчинг через NotifyChannel

При высокой нагрузке несколько push() происходят между poll'ами. drain() забирает все накопленные пакеты за один lock. Результат: несколько start_send() за один wakeup вместо wakeup-per-packet.

Антипаттерны (learned the hard way)

Антипаттерн Проблема Решение
select! + framed.feed().await TCP deadlock при backpressure poll_fn с неблокирующими фазами
DashMap iter через .await Shard lock held across suspension → stall Collect-then-process
NotifyChannel::recv() без loop Spurious Notify permit → missed items Внутренний loop в recv()
try_send() на delivery paths Молча дропает сообщения Unbounded send или NotifyChannel
Две таски reader+writer Двойной waker overhead Единый poll_fn loop