Skip to content

Centrifugo PRO v6.9.7

Choose a tag to compare

@FZambia FZambia released this 29 Sep 04:29
0b35c13

Centrifugo PRO is an extended version of Centrifugo with a set of additional features. These features can provide your business with unique benefits – drastically save development time, reduce resource usage on a server, protect your backend from misusing, and put the system observability to the next level.

More details and feature description can be found in Centrifugo PRO docs.

What's changed

  • Everything from Centrifugo OSS v6.9.7
  • Publications now reach the broker in groups, by default. This applies to every publication to Redis or the memory engine. Single publications – from the publish API, async consumers and clients – are sent together when they arrive under load, and a broadcast or batch request sends all of its publications as one call. With Redis this takes much less CPU: single publications at 50,000 per second use 13% less Centrifugo CPU and 17% less Redis CPU, a broadcast into 1000 channels uses 5.6x less Centrifugo CPU, and a sequential batch of 100 publish commands replies in 1.6 ms instead of 50 ms. Nothing needs to be configured. See Publication grouping for how it works, what it changes for applications, and the numbers.
  • New namespace option publication_grouping_max_delay, off by default. Lets single publications wait up to this long for others to join them, which saves more CPU at the cost of latency.
  • New namespace option publication_grouping_disabled. Sends the namespace's publications to the broker one by one, as before this release.
  • Fixed: Centrifugo PRO spent more CPU than Centrifugo OSS publishing from broadcasts and parallel batches. Since v6.8.0, channel options – which Centrifugo reads for every publication – had grown to three times their size in OSS. They now take about half the memory and are no longer copied on each lookup.
  • Channel batching with batch_max_delay uses much less CPU in channels with many subscribers. A publication used to start a timer and a goroutine for every subscriber of the channel; now the subscribers of a channel share one. Each subscriber still receives the same messages in the same order, and never waits longer than batch_max_delay.
  • Less work per publication when no named engines are used. Centrifugo looked up the options of every channel it published to, subscribed to or read presence of, to find a named broker or presence manager – even with none configured. It now does so only when brokers, presence_managers or map_brokers has an enabled entry. Broadcasts into many channels gain the most.
  • Redis user command throttling checks all limits of a command in one Redis round trip, instead of one after another. With client-side sharding over several standalone Redis instances, counters of a user's limits other than the first start from zero once after the upgrade.
  • With user_status enabled, a connection no longer waits for Redis on connect. The online status update runs in the background, so commands sent right after connect, such as subscriptions, are not held behind it.
  • ClickHouse analytics export reuses its buffers between flushes, allocating about 95% less memory per flush – less garbage collection at high event rates.
  • Changed: batch_flush_latest together with allowed_delta_types is now rejected on start. Flushing only the latest publication drops the ones deltas are computed against, so subscribers using delta compression ended up with wrong data. A config combining the two no longer starts: drop one of them.
  • Fixed: with group_sharded_pub_sub_by_node, delivery could stay broken after a Redis Cluster topology change if rebuilding the connections failed – nothing retried it until the topology changed again. It is now retried every second. Also fixed: replicas were counted as nodes, so each got idle PUB/SUB connections, and a replica joining or reconnecting restarted every PUB/SUB connection with a short delivery gap; and subscribing during a rebuild could panic.
  • Fixed: with sharded PUB/SUB, a publication with an idempotency key into a channel without history could be lost during a Redis Cluster slot migration – reported as successful, never delivered, and its retries suppressed. It now fails during the migration, so a retry succeeds.

Publication grouping

Grouping works with the Redis and memory brokers; other brokers (NATS, PostgreSQL) publish one by one as before.

  • Single publications – /api/publish over HTTP and gRPC, async consumers, client publications, and /api/broadcast into one channel. When Centrifugo is not busy, each one goes to the broker right away, as before. Under load, when two sends to the broker are already in progress, a new publication waits for one of them to finish, and everything waiting by then goes out together. No publication waits longer than 250 Β΅s for this.
  • Server API requests with many publications – /api/broadcast into several channels, and /api/batch, sequential or parallel, over HTTP and gRPC. The request hands its publications to the broker together, without waiting for other requests, and Redis receives them in a few pipelined calls, each covering many publications, instead of one command per publication. In a batch, only publish commands next to each other are handed over together; another command between them splits them.

Each publish call returns after its own publication is sent, so publications made one after another keep their order. In a batch, a command that is not a publish, such as a history call, runs only after the publications before it are sent.

What changes for applications. A channel's publications always take effect in the order you wrote them. A sequential batch no longer waits for one publication before starting the next, so publications to different channels may take effect in another order. Centrifugo never promised that order, and with Redis it did not hold even before: publications coming back from Redis are processed in concurrent goroutines chosen by channel name, so a client subscribed to both channels could already receive them in another order. To get the previous behaviour back for a namespace, set publication_grouping_disabled: its publications go to the broker one by one again, from every API, and a sequential batch writes them to Redis in the order written.

The delay option. publication_grouping_max_delay lets a single publication wait up to the delay for others to join it, or until the group is full. At 50,000 publications per second, 250us uses 24% less Centrifugo CPU and 32% less Redis CPU than v6.9.6 for 0.3 ms more at the median; 1ms saves more for about 1.3 ms. At lower rates groups fill more slowly, so the saving is smaller and the wait closer to the full delay. Batches, and broadcasts into several channels, never wait. With the default client.concurrency, a client's next command waits for its delayed publication.

Also worth knowing. A cluster with sharded PUB/SUB groups publications within each partition; a cluster without it can only group publications of one channel. Legacy list history (use_lists) sends a separate command for each publication with history. A grouped publish no longer sets centrifugo.channel on the OpenTelemetry span, which used to show only the last channel of a batch. Publications that share a Redis call share its outcome, so a problem with one channel's Redis keys can fail the others in that call.

Numbers

A 16-core Linux machine, Centrifugo, the load generator and Redis pinned to separate cores, calling the HTTP API at a fixed rate that both versions held, so these are costs of identical work. 256-byte payloads; a broadcast carried 1000 channels, a batch 100 publish commands. Median of three alternated runs. Nothing was subscribed except in the memory engine table.

Standalone Redis, v6.9.6 β†’ this release

Centrifugo CPU, cores Redis CPU, cores latency p50
/api/broadcast, 100,000 publications/sec 3.11 β†’ 0.56 (5.6x less) 0.21 β†’ 0.18 6.1 β†’ 4.4 ms
/api/broadcast with history, 30,000/sec 1.47 β†’ 0.35 (4.2x less) 0.51 β†’ 0.50 15 β†’ 19 ms
/api/batch, sequential, 25,000/sec 1.08 β†’ 0.41 (2.6x less) 0.21 β†’ 0.07 (3.1x less) 50 β†’ 1.6 ms
/api/batch, sequential, with history, 10,000/sec 1.13 β†’ 0.23 (5x less) 0.40 β†’ 0.22 (1.8x less) 18 β†’ 3.9 ms
/api/batch, parallel, 25,000/sec 1.22 β†’ 0.39 (3.1x less) 0.09 β†’ 0.06 1.8 β†’ 1.6 ms
/api/publish with history, 20,000/sec 2.79 β†’ 2.79 0.56 β†’ 0.49 (12% less) 0.29 β†’ 0.38 ms
/api/publish with history, 50,000/sec 4.16 β†’ 3.62 (13% less) 0.86 β†’ 0.72 (17% less) 0.34 β†’ 0.42 ms
same, with publication_grouping_max_delay: 250us 4.16 β†’ 3.15 (24% less) 0.86 β†’ 0.58 (32% less) 0.34 β†’ 0.64 ms
/api/broadcast into one channel, 30,000/sec 3.35 β†’ 2.85 (15% less) 0.71 β†’ 0.59 (17% less) 0.29 β†’ 0.35 ms

Flat out, with 256 concurrent callers, single publications went from 86,500 to 97,300 per second. A broadcast into 1000 channels also used 2.7x less memory and 12x fewer goroutines. A broadcast with history replies later: one call carrying many channels takes Redis longer than many small ones.

With 3 ms of network latency between Centrifugo and Redis the savings hold, a sequential batch replies in 4.7 ms instead of 341 ms – one round trip instead of a hundred – and single publications take about 0.5 ms longer at the median at most.

Redis Cluster, 3 nodes, sharded PUB/SUB

Centrifugo CPU, cores Redis CPU, cores latency p50
/api/broadcast 3.21 β†’ 0.97 (3.3x less) 0.70 β†’ 0.53 5.6 β†’ 4.8 ms
/api/broadcast with history 1.54 β†’ 0.63 (2.4x less) 1.10 β†’ 0.89 14 β†’ 14 ms
/api/batch 1.89 β†’ 0.87 (2.2x less) 0.72 β†’ 0.22 (3.2x less) 14 β†’ 1.9 ms
/api/batch with history 1.26 β†’ 0.59 (2.1x less) 0.76 β†’ 0.45 (1.7x less) 22 β†’ 3.7 ms

Same rates as the standalone table, single runs. With group_sharded_pub_sub_by_node the results are similar: up to 3.9x less Centrifugo CPU and 3.4x less Redis CPU.

Memory engine, 10 connections subscribed to every channel

Centrifugo CPU, cores latency p50
/api/broadcast, 20,000 publications/sec 1.54 β†’ 1.18 (23% less) 11.9 β†’ 9.9 ms
/api/broadcast with history 1.91 β†’ 1.51 (21% less) 14.2 β†’ 12.4 ms
/api/batch, 20,000 publications/sec 2.00 β†’ 1.37 (32% less) 2.1 β†’ 2.1 ms
/api/batch with history 2.71 β†’ 1.69 (38% less) 2.7 β†’ 2.1 ms