Repository navigation
Felix v0.6.0-preview.7
Pre-releaseThe seventh preview of 0.6.0. Subscriber memory on the broker is now bounded
by bytes end to end: each subscriber queue, and the writer lanes behind it,
stop at FELIX_SUB_QUEUE_MAX_BYTES (256 MiB by default) as well as at their
entry count. The bound is sized so a subscriber keeping up at line rate with
4 MiB batches never reaches it. Under the block policy, one full subscriber
no longer holds up a batch's fanout to the others. Revoking a principal now
ends the streams its tokens already opened, with the new revoked error code,
and a subscription whose stream is deleted ends with stream_deleted for
clients that offer it. ClusterClient::with_identity lets a gateway act as
another user over shared cluster connections, and a delegating gateway can
hold a connection-only broker token instead of a grant it never uses.
Replication and placement fixes: group and dead-letter logs ride each
follower's exchange, a new Quorum copy is seated only once the leader names
it caught up, and a counters-only cache restore is no longer dropped as
stalled.
Upgrade notes.
- A Raft control plane needs every member upgraded before revocation (metadata
level 7) and counter-offset reports (level 8) take effect. Until then the
revoke endpoint answers409after revoking the refresh tokens, and reports
are recorded without counter offsets. - A Postgres control plane runs migrations
0025and0026on start.
Added
- Subscriber queues on the broker are bounded by bytes as well as entries.
An entry is a whole published batch, so the count alone let one slow
subscriber hold up to 4096 batches of up to 16 MiB each. Each queue now also
stops atFELIX_SUB_QUEUE_MAX_BYTESof payload (256 MiB by default,0for
none), and the queue policy handles a full byte bound as it handles a full
count:drop_newdrops the batch for that subscriber, with the same offset
gap and drop counters, andblockwaits. A batch larger than the bound
still goes into an empty queue.FELIX_SUB_QUEUE_MAX_BYTES_TOTAL(off by
default) caps the bytes held across all subscriber queues, at the cost of
isolation between subscribers. New metrics:felix_sub_queue_bytesand
felix_sub_queue_byte_dropped_total{bound}.Brokergains
with_subscriber_queue_max_bytesandwith_subscriber_queue_max_bytes_total
(#1157, #1189). - A delegating gateway no longer needs a broker grant it never uses. A
principal holdingtoken.delegate:tenant:{tenant_id}can exchange for a
connection-only broker token, its own subject with no permissions, by
sending"requested": [], "permissions": []to/token/exchange(or the
dev-token bootstrap endpoint). Its shared connections authenticate with it,
and every request still runs under the user's delegated token. Refresh
checkstoken.delegateagain and ends the chain once it is removed. Anyone
else asking for no permissions still gets403no permissions(#1130). ClusterClient::with_identity(tenant_id, token_provider)and
ClusterClient::with_identity_tokenact as another user over the cluster
client's connections (#1129), asClient::with_identitydoes for one
broker. The result is aClusterClientwith its own routing state that
follows moved shards, redirects and failovers, reaching each broker over the
parent's connection to it, so a gateway sharing connections keeps a user's
subscription across a shard move and still holds one connection per broker.
The PythonClient.with_identityandAsyncClient.with_identityand the
TypeScriptClient.withIdentitywrap it with a fixed token. No wire change.- Revoking a principal ends what its tokens already opened, on every broker,
within about one control-plane sync interval (#1132). Until now a revoked
principal kept its open subscriptions, cache watches, publish streams and
Kafka connections until its token expired.POST /v1/tenants/{t}/refresh-tokens/revokenow also records a revocation, and so
does removing an RBAC policy or role assignment, for everyone the rule
reached (the whole tenant when it reached an IdP group). Brokers poll the new
GET /v1/principal-revocations/changesfeed, refuse broker tokens issued at
or before a revocation, and end the streams they opened with the new
revokederror code (16,fatal): anerroron a control stream, or as the
last frame on a subscription's or cache watch's event stream. A Kafka
connection is closed at its next request. Under the Raft store this is
metadata version 7, and until every member has it the revoke endpoint
answers409after revoking the refresh tokens. See
docs/auth.md. CacheWatch::refusalin the Rust client says why the broker ended a watch
with an error, such asrevoked. A subscription ended that way returns the
BrokerErrorfromnext_eventinstead of an "unexpected message" error
(#1132).- The subscriber byte bounds also cover the broker's writer lanes. A batch's
bytes stay held until the frame carrying it is written to the subscriber's
QUIC stream, so frames queued behind a flow-controlled subscription count
towardFELIX_SUB_QUEUE_MAX_BYTESinstead of piling up in the lanes by entry
count alone (up to 64 frames of up to 16 MiB each per subscription). Under
drop_newthe next batch is dropped at the subscriber queue as before;
publishers still never wait.SubscriptionReceivergainsrecv_heldand
try_recv_held, andHeldBytesis public (#1163). - A subscription ended because its stream was deleted now says so. The broker
sendsstream_deletedas its last frame to a client that offered the new
extended feature bitFEATURE_STREAM_DELETED; any other client sees the
stream end as before. Removing a stream used to leave its subscribers
waiting on a feed nothing would end. The Rust client reports it as
Subscription::stream_deletedandClusterSubscription::stream_deleted,
and a sharded subscription yieldsShardEvent::StreamDeletedfor each shard
instead of trying to resubscribe it (StreamDeletedin Python,
streamDeletedin TypeScript) (#1119).
Changed
- A broker closing a shard it no longer has any role in tells a reader still
attached that the shard moved, naming its current leader, rather than just
ending the stream.Broker::close_stream_shardtakes the handoff (#1119).
Fixed
- Under the
blockqueue policy, one subscriber at its byte or entry bound no
longer holds up the rest of a batch's fanout. The broker queues the batch
for every subscriber with room first and waits only on the full ones, so a
fast subscriber gets each batch as soon as it is durable. Each subscriber
still receives batches in publish order. The latency demo now passes
FELIX_SUB_QUEUE_MAX_BYTESandFELIX_SUB_QUEUE_MAX_BYTES_TOTALto the
broker it builds (#1179). - The Node client returns
null, asindex.d.tssays, for an absent field on
a delivered item:laggedResumeFrom,changeandshardMovedon a cache
watch item,valueon a cache change,offseton an event,event,
lostErrorandrecoveredon a sharded item, andresumeFrom,nodeIdand
addron a shard move. They came backundefined, so a caller testing
!== nullread every cache watch item as a lag notice (#1140). - The cluster test harness's
wait_for_replicationnow waits until the
control plane holds a report naming a follower at the leader's current tail.
It used to return once the leader had shipped anything, which the harness's
start-up publish already guaranteed, so a test that killed the leader next
could fail over without the records it had just published (#1136). - Right after a broker died, a stream with consumer groups could hold each
replication pass on it for a request timeout or a dial: the group cursor and
dead-letter logs shipped to every follower at the end of the pass, and the
next publish waited behind that pass. They now ride each follower's
exchange, as aQuorumcache's counters do since #1137, and so do a
non-Quorumcache's counters (#1143). - A new copy of a
Quorumcache is no longer seated before it holds the
cache's counter log. Seating checked only the cache log's offsets, so the
set could count toward its majority a copy that promotion would refuse.
A seat on aQuorumstream or cache now also waits until the leader's
report names the copy caught up (#1139). - A restore of a cache used only for counters is no longer dropped as stalled
while it copies a large counter log. The stall window
(FELIX_SHARD_RESTORE_STALL_MS) watched only the cache log's offset, which
stays at 0 for such a cache. Replica reports now carry each replica's
counter-log position (replica_counter_offsets, omitted for streams and by
older brokers), and placement counts a move there as progress. The control
plane stores it in the report (Postgres migration
0026_replica_report_counter_offsets, Raft metadata level 8; below level 8
reports are recorded without it) (#1164).