Производительность и оптимизации¶
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 |