Repository navigation
[server] Add consumer group owner discovery - #4716
Conversation
|
|
||
| private FindConsumerGroupOwnerResponse buildFindConsumerGroupOwnerResponse( | ||
| BucketMetadata bucketMetadata, String listenerName) { | ||
| int leaderId = bucketMetadata.getLeaderId().getAsInt(); |
There was a problem hiding this comment.
It may throw exception if getLeaderId() returns Optional.empty()
There was a problem hiding this comment.
Thanks for pointing this out. The leader ID is checked immediately before
calling buildFindConsumerGroupOwnerResponse:
if (!bucketMetadata.getLeaderId().isPresent()) { throw new ConsumerGroupOwnerLoadingException(...); }
BucketMetadata is immutable and its leaderId field is final. The helper
is also private and currently has only this call site, so the leader ID
cannot become empty between the presence check and getAsInt(). Therefore,
getAsInt() is safe here and I prefer to keep the current code.
| * access). Empty if this server's snapshot does not know the table or bucket yet. | ||
| */ | ||
| public Optional<BucketMetadata> getCachedBucketMetadata(TablePath tablePath, int bucketId) { | ||
| ServerMetadataSnapshot snapshot = serverMetadataSnapshot; |
There was a problem hiding this comment.
This snapshot may lag behind the current bucket leadership.
But the owner discovery now has no Zookeeper fallback on a cache miss. Retries may keep returning owner unavailable.
There was a problem hiding this comment.
Good point. The TabletServer metadata cache is eventually updated when bucket
leadership changes, so a stale cached owner is temporary. The actual
consumer-offset operations will validate local leadership and return the
retriable NOT_CONSUMER_GROUP_OWNER error when necessary, allowing the client
to invalidate its owner cache and rediscover the owner.
For the cache-miss case, I will add an asynchronous ZooKeeper fallback so
retries can make progress before the local metadata snapshot is populated.
The fallback will also refresh the local metadata cache.
| bucketCount, replicationFactor, servers); | ||
|
|
||
| try { | ||
| metadataManager.createTable( |
There was a problem hiding this comment.
Multiple TS can pass the initial existence checks and call createTableconcurrently; the atomic creation of the first schema is inside this function zookeeperClient.registerFirstSchema().
I think we catch the NodeExistsException will be better than the existing generic FlussRuntimeException, and it can avoid an additional tableExists check.
| bucketCount, replicationFactor, servers); | ||
|
|
||
| try { | ||
| metadataManager.createTable( |
There was a problem hiding this comment.
If the TS crashes after registering the first schema but before completing table registration, tableExist() remains false, while every subsequent creation attempt fails with NodeExistsException on the existing schema. This leaves automatic table creation stuck, even after a TS restart。
swuferhong
left a comment
There was a problem hiding this comment.
Hi, @pisceslj thanks for your contribution! I left some comments. Could you also rebase onto the latest main to help avoid flaky CI failures? Thanks!
| Optional<ServerNode> cachedOwner = | ||
| metadataCache.getTabletServer( | ||
| cachedBucket.get().getLeaderId().getAsInt(), listenerName); | ||
| if (cachedOwner.isPresent()) { |
There was a problem hiding this comment.
Cache miss fallback covers missing metadata, but not stale metadata. If leadership moves from A to B while A remains alive and this TS misses the metadata update, this branch keeps returning A. How will repeated discovery refresh this server-side cache?
There was a problem hiding this comment.
The TabletServer metadata cache is eventually updated when bucket
leadership changes, so a stale cached owner is temporary. The actual
consumer-offset operations will validate local leadership and return the
retriable NOT_CONSUMER_GROUP_OWNER error when necessary, allowing the client
to invalidate its owner cache and rediscover the owner.
| .setTablePath() | ||
| .setDatabaseName(CONSUMER_OFFSET_TABLE_PATH.getDatabaseName()) | ||
| .setTableName(CONSUMER_OFFSET_TABLE_PATH.getTableName()); | ||
| coordinatorGateway.createTable(createTableRequest).get(); |
There was a problem hiding this comment.
Routing creation through the coordinator still reaches the same non-atomic MetadataManager.createTable implementation. If schema registration succeeds but table registration fails, subsequent retries still fail on the existing schema. Could we cover recovery from this partially created state?
| .setTablePath() | ||
| .setDatabaseName(CONSUMER_OFFSET_TABLE_PATH.getDatabaseName()) | ||
| .setTableName(CONSUMER_OFFSET_TABLE_PATH.getTableName()); | ||
| coordinatorGateway.createTable(createTableRequest).get(); |
There was a problem hiding this comment.
This now goes through validateTableCreationPermission, so allow.create.kv.tables=false also prevents creating sys.consumer_offsets. This limitation should be explicit rather than reported as a transient owner-unavailable error for this case.
There was a problem hiding this comment.
Thanks for pointing this out. I updated the validation so internally created system tables bypass the user-facing allow.create.kv.tables restriction, while external requests cannot create system tables. Regular KV tables remain subject to the setting.
I also added an integration test with allow.create.kv.tables=false that triggers findConsumerGroupOwner and verifies that sys.consumer_offsets is created successfully, so this case no longer surfaces as a transient owner-unavailable error.
| public int bucketFor(String groupId, int bucketCount) { | ||
| validateGroupId(groupId); | ||
| byte[] bucketKey = ConsumerOffsetSchema.encodeGroupPrefix(groupId); | ||
| return FlussBucketingFunction.bucketForRowKey(bucketKey, bucketCount); |
There was a problem hiding this comment.
Could we avoid inheriting the cluster's lake format for sys.consumer_offsets? here uses Fluss bucketing, while standard writers/lookups follow the table's lake format, even when tiering is disabled. This mismatch could cause future offset lookups to miss data. Please keep both routing paths consistent and add a test with datalake.format configured.
There was a problem hiding this comment.
Thanks for catching this. I updated the creation defaults so system tables, including sys.consumer_offsets, do not inherit the cluster’s lake format. This keeps GroupManager and standard KV writers/lookups consistently using Fluss bucketing.
I also added an integration test with datalake.format=PAIMON and tiering disabled. The test verifies that sys.consumer_offsets has no lake format property and that both routing paths calculate the same bucket.
b47ee83 to
b4672e6
Compare
b4672e6 to
d258eb3
Compare
d258eb3 to
197c5a5
Compare
AI-Contributed/Feature: 0/6 AI-Contributed/UT: 0/2
wuchong
left a comment
There was a problem hiding this comment.
LGTM.
I pushed a commit to revert using offsets retention for log ttl.
Purpose
Linked issue: close #4714
Part of #4608.
Implement the server-side consumer-group owner discovery required by
FIP-53.
Brief change log
ConsumerOffsetTableManagerto lazily create and resolve thesys.consumer_offsetssystem table.GroupManagerto validate group IDs and map them to buckets.FindConsumerGroupOwnerinTabletService.Tests
GroupManagerTestTabletServerMetadataCacheTest#testGetCachedBucketMetadataTabletServiceITCase#testFindConsumerGroupOwner./mvnw verify -pl fluss-server -amAPI and Format
This PR implements the previously introduced
FIND_CONSUMER_GROUP_OWNERRPC.It creates the internal
sys.consumer_offsetsprimary-key table usingthe tagged KV value layout for row-level TTL metadata. No new public API
keys or protobuf messages are introduced.
Documentation
No additional user-facing documentation is required in this PR. The
consumer-offset configuration options were introduced in the table
foundation PR.