feat: implement unlimited pagination for message browsing#2128
feat: implement unlimited pagination for message browsing#2128
Conversation
|
The latest Buf updates on your PR. Results from workflow Buf CI / validate (pull_request).
|
f0131ec to
ef4921d
Compare
Add PageToken struct with encode/decode, validation, and cursor management for multi-partition offset tracking. Supports both ascending and descending order pagination.
Add page_token, page_size, next_page_token, and has_more fields to support cursor-based pagination in ListMessages API.
- Add PageToken and PageSize to ListMessagesRequest - Pass next_page_token and has_more in completion message - Validate pagination mode (maxResults=-1) and reject filters
- Add calculateConsumeRequestsWithPageToken with round-robin message distribution - Support both ascending and descending order pagination - Reverse messages per-partition for descending order - Fix offset boundary check (inclusive EndOffset) - Track finished partitions to prevent over-consumption
- Test first page and multi-page pagination - Verify descending order within partitions - Test filter rejection with pagination mode - Verify legacy mode compatibility
- Add pageToken/nextPageToken to message search state - Auto-load next page when user reaches last loaded page - Reset pagination when navigating back to page 1 - Disable sorting in unlimited mode (maxResults=-1) - Hide unlimited option in Latest/Live mode - Auto-reset to 50 when switching to Latest/Live from unlimited
…eter Remove hasMore as an explicit parameter from IListMessagesProgress.OnComplete(). The hasMore field is now automatically derived from whether nextPageToken is empty. This simplifies the interface by removing redundant state - the information about whether more pages exist is already encoded in the presence/absence of nextPageToken. Changes: - Remove hasMore parameter from OnComplete() interface (4 params -> 3 params) - Derive HasMore automatically: HasMore = nextPageToken != "" - Update all call sites to pass 3 parameters instead of 4 - Update integration tests to check nextPageToken instead of hasMore - Update mocks to match new 3-parameter signature The protobuf has_more field is still populated for wire compatibility, but is no longer passed as a separate parameter through the application layers.
- Fix mock expectations in integration tests to use 3 parameters - Remove HasMore field from stream completion message struct - Fix page token test to handle unordered partition iteration
Frontend fixes: - Fix stale closure race condition in auto-load effect by capturing messageSearch reference and validating before state update - Add proper cleanup for loadMore operations on component unmount - Fix circular effect dependency in page reset by using ref instead of state in dependency array - Add retry limit (max 3 attempts) for loadMore failures to prevent infinite retry loops on network errors - Track mounted state to prevent state updates on unmounted component Backend fix: - Change pagination logging from INFO to DEBUG level to reduce log verbosity in production
ef4921d to
2f955cf
Compare
The append parameter was referenced but never declared, causing loadMore to always clear messages instead of appending them.
|
@claude review |
|
Claude finished @malinskibeniamin's task in 2m 1s —— View job Code Review Complete ✅
SummaryThis is a well-architected implementation of stateless pagination for Kafka message browsing. The approach using base64-encoded page tokens is solid, and the round-robin distribution ensures fair partition representation. The code is well-tested with comprehensive integration tests. However, I've identified several issues and improvements across the implementation. Critical Issues1. Race Condition in Offset Calculation (backend/pkg/console/list_messages.go:1081)Issue: The condition // Current code (line 1081)
if record.Offset >= partitionReq.EndOffset {Problem: If Fix: Change to if record.Offset > partitionReq.EndOffset {2. Potential Integer Overflow in Token Validation (backend/pkg/console/page_token.go:104)Issue: The page size validation uses hardcoded if pt.PageSize <= 0 || pt.PageSize > 500 {Concern: With many partitions (e.g., 1000 partitions × 500 messages), the system could attempt to fetch 500,000 messages in memory. This could cause OOM issues. Recommendation: Add a warning or consider validating 3. Unchecked Error in Frontend LoadMore (frontend/src/components/pages/topics/Tab.Messages/index.tsx:759)Issue: The capturedMessageSearch
.loadMore()
.then(() => {
if (isMountedRef.current && currentMessageSearchRef.current === capturedMessageSearch) {
setMessages([...capturedMessageSearch.messages]);Problem: If the search parameters changed between pages, stale data could be displayed. The check for Recommendation: Add validation that search parameters haven't changed, or clear messages on parameter change. High Priority Issues4. Missing Watermark Update Validation (backend/pkg/console/list_messages.go:598)Issue: When watermarks change between pages, the code only checks if partition is exhausted but doesn't validate that if token.Direction == DirectionDescending {
isExhausted = cursor.NextOffset < updatedLowWaterMark
} else {
isExhausted = cursor.NextOffset >= updatedHighWaterMark
}Problem: If low watermark moved forward due to compaction, Fix: Add validation and adjustment: if token.Direction == DirectionDescending {
if cursor.NextOffset < updatedLowWaterMark {
isExhausted = true
}
} else {
if cursor.NextOffset < updatedLowWaterMark {
// Adjust NextOffset to new low watermark
cursor.NextOffset = updatedLowWaterMark
}
if cursor.NextOffset >= updatedHighWaterMark {
isExhausted = true
}
}5. Inconsistent Direction Validation (backend/pkg/console/list_messages.go:250-254)Issue: The direction determination logic only considers direction := DirectionDescending
if listReq.StartOffset == StartOffsetOldest {
direction = DirectionAscending
}Problem: This is inconsistent with the PR description stating "Newest messages first" for descending. If Recommendation:
6. Memory Leak in Frontend Auto-Load (frontend/src/components/pages/topics/Tab.Messages/index.tsx:732-801)Issue: The auto-load effect doesn't clean up the abort controller if the effect re-runs before the previous load completes. useEffect(() => {
// ... auto-load logic
const abortController = new AbortController();
loadMoreAbortRef.current = abortController;Problem: If the effect re-runs (e.g., pageIndex changes), a new abort controller is created but the old one isn't aborted, potentially leaving orphaned requests. Fix: Add cleanup: useEffect(() => {
return () => {
if (loadMoreAbortRef.current) {
loadMoreAbortRef.current.abort();
loadMoreAbortRef.current = null;
}
};
}, [pageIndex, maxResults, /* other deps */]);Medium Priority Issues7. Token Encoding Size Not Validated (backend/pkg/console/page_token.go:41)Issue: No validation that the encoded token size is reasonable for URL transmission. Concern: With 1000 partitions, the token could be several KB, potentially exceeding URL length limits or gRPC metadata limits. Recommendation: Add size check after encoding and return error if token exceeds ~8KB. 8. Missing Test Coverage for Edge Cases (backend/pkg/console/page_token_test.go)Observed: Tests cover basic scenarios well, but missing tests for:
Recommendation: Add integration tests for these scenarios. 9. Frontend Retry Logic Could Loop Indefinitely (frontend/src/components/pages/topics/Tab.Messages/index.tsx:741)Issue: The retry counter if (loadMoreFailures >= MAX_LOAD_MORE_RETRIES) {
return;
}Problem: If load fails 3 times, no more auto-loading will occur even if the user navigates away and back. The counter is only reset on success or when resetting pagination (line 813). Recommendation: Reset 10. Descending Order Reversal Could Be Memory Intensive (backend/pkg/console/list_messages.go:877-893)Issue: Messages are collected in memory per-partition and then reversed. messagesPerPartition := make(map[int32][]*TopicMessage)
// ... collect messages ...
// Reverse messages in each partition
for partitionID, messages := range messagesPerPartition {Concern: With 500 messages × 100 partitions, this could temporarily double memory usage during reversal. Recommendation: Consider reversing as messages are sent rather than collecting all first, or use a more memory-efficient reversal method. Low Priority / Style Issues11. Magic Numbers in Tests (backend/pkg/api/handle_topic_messages_integration_test.go:1071)const messageCount = 150Recommendation: Extract to a constant with clear name explaining why 150 (e.g., 12. Inconsistent Error Messages (backend/pkg/console/page_token.go)Observation: Some validation errors include context (partition ID, values) while others don't. Example: Line 117 includes partition info, but line 84 doesn't include the actual partition count. Recommendation: Standardize error messages to always include relevant context values. 13. Frontend: Unused pageToken in Legacy Mode (frontend/src/state/backend-api.ts:3160)Issue: When calling Observation: Not a bug per se, but the code structure could be clearer about mode separation. Positive Observations
Recommendations for Follow-up
Overall Assessment: This is a solid implementation with good test coverage. The critical issues (#1, #2) should be addressed before merging. The high-priority issues (#4-6) are important for production reliability. Medium and low priority items can be addressed in follow-up PRs. |
Summary
Implements unlimited pagination for Kafka message browsing, allowing users to navigate through arbitrarily large topics without loading all messages into memory.
Changes
Backend
ListMessagesproto (next_page_token)Frontend
Key Features