Skip to content

feat: add aggregator consumer - #2284

Merged
katarinazaprazna merged 7 commits into
RedHatInsights:masterfrom
katarinazaprazna:add-aggregator-consumer
Jul 30, 2026
Merged

feat: add aggregator consumer#2284
katarinazaprazna merged 7 commits into
RedHatInsights:masterfrom
katarinazaprazna:add-aggregator-consumer

Conversation

@katarinazaprazna

@katarinazaprazna katarinazaprazna commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Aggregator: batch events from patchman.advisory.update by account, refresh account_advisory caches, send notifications
  • Drift check: compare account_advisory vs legacy advisory_account_data during migration
  • Make Notification.Context optional for aggregator use (no system context)

Follow-up

  • Ensure evaluator publishes advisory update events to patchman.advisory.update
  • Verify aggregator is processing (check logs for drift check warnings)
  • Run backfill job
  • Implement cleanup job for account_advisory

Secure Coding Practices Checklist GitHub Link

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Introduce an advisory update aggregator that batches events per account to refresh advisory caches, perform drift checks against legacy data, and emit account-level notifications.

New Features:

  • Add buffered advisory update consumer that groups events by account and advisory IDs and triggers batch processing based on size and timeout.
  • Add account-level advisory notification flow that publishes new advisories for installable systems and marks them as notified.
  • Add drift check mechanism comparing account_advisory data with legacy advisory_account_data to surface migration inconsistencies.

Enhancements:

  • Make notification context optional by changing it to a pointer and adding a helper for account-scoped notifications, and centralize the new-advisory event type in the notification package.

Tests:

  • Add unit and integration tests for advisory grouping, buffered processing, notification publishing behavior, and drift check logging under various scenarios.

Summary by Sourcery

Introduce an advisory update aggregator that buffers and batches advisory update events per account to refresh caches, perform drift checks, and emit account-level notifications.

New Features:

  • Add buffered advisory update consumer that groups advisory update events by account and advisory IDs and flushes based on batch size or timeout.
  • Introduce account-scoped advisory notifications sourced from account_advisory data, marking advisories as notified after publishing.
  • Add drift check logic comparing aggregated account_advisory counts against legacy advisory_account_data to detect migration inconsistencies.

Enhancements:

  • Make notification Context optional and introduce an account-level notification constructor and shared new-advisory event constant.
  • Wire aggregator configuration for batch size, flush timeout, and notification enablement via pod config.

Tests:

  • Add unit tests for advisory grouping and buffered batch processing behavior in the aggregator consumer.
  • Add tests covering advisory notification publishing, skipping already-notified advisories, and marking advisories as notified.
  • Add drift check tests validating warning logs for count mismatches and missing advisories between legacy and new tables.

Added `checkAdvisoryDrift` to compare advisory data between new and
legacy tables. Logs discrepancies for missing or mismatched records.
The aggregator sends account-level notifications without system-specific
context. Make Context a pointer so it can be omitted, add
MakeAccountNotification constructor for this use case, and move
NewAdvisoryEvent to a shared constant.
@sourcery-ai

sourcery-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements an advisory update aggregator that buffers Kafka events, groups them per account, refreshes account-level advisory caches, performs drift checks against legacy data, and emits account-scoped notifications using an updated, more flexible notification API.

File-Level Changes

Change Details Files
Add buffered advisory update consumer that batches Kafka events and processes grouped advisory updates per account.
  • Introduce global advisory buffer with configurable batch size and flush timeout, protected by a mutex and driven by a timer.
  • Implement advisoryUpdateHandler to deserialize events, append to the buffer, and trigger flushing when batch size is reached or timeout fires.
  • Add groupAdvisoryUpdates to deduplicate advisory IDs per account and processAdvisoryBatch to refresh caches, run drift checks, and publish notifications.
aggregator/events.go
aggregator/aggregator.go
aggregator/events_test.go
Implement account-level notification publishing for new advisories and centralize notification event type and construction helpers.
  • Add getUnnotifiedAdvisories and markAdvisoriesNotified DB helpers and publishNewAdvisoryNotification to send Kafka notifications and mark advisories as notified in a transaction.
  • Switch evaluator notifications to use the shared NewAdvisoryEvent constant in the notification package and adjust tests accordingly.
  • Update notification.Notification to use a pointer Context, introduce NewAdvisoryEvent constant, and add MakeAccountNotification for account-scoped messages without system context.
aggregator/notifications.go
evaluator/notifications.go
evaluator/notifications_test.go
base/notification/notification.go
aggregator/notifications_test.go
Add drift check between new account_advisory data and legacy advisory_account_data during migration.
  • Introduce advisoryCounts struct and checkAdvisoryDrift to compare per-advisory applicable/installable counts between the two tables and log warnings on mismatches or missing rows.
  • Add tests that manipulate account_advisory data and verify that appropriate warning logs are emitted for count mismatches and missing data.
aggregator/drift_check.go
aggregator/drift_check_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@katarinazaprazna katarinazaprazna changed the title Add aggregator consumer feat: add aggregator consumer Jul 29, 2026
@katarinazaprazna
katarinazaprazna marked this pull request as ready for review July 29, 2026 21:30
@katarinazaprazna
katarinazaprazna requested a review from a team as a code owner July 29, 2026 21:30

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="aggregator/aggregator.go" line_range="23-24" />
<code_context>
 	consumerCount = utils.PodConfig.GetInt("consumer_count", 1)
-	enableNotifications = utils.PodConfig.GetBool("instant_notifications", false)
+	enableNotifications = utils.PodConfig.GetBool("enable_notifications", false)
+	batchSize = utils.PodConfig.GetInt("advisory_batch_size", 4000)
+	flushTimeout = time.Duration(utils.PodConfig.GetInt("advisory_flush_timeout_ms", 500)) * time.Millisecond
 }

</code_context>
<issue_to_address>
**suggestion (bug_risk):** Guard against non-positive batch sizes from configuration to avoid degenerate buffering behavior

If `advisory_batch_size` is 0 or negative, `cap(batchSize+1)` and `len(advisoryBuffer) >= batchSize` will make `shouldFlush` true immediately/on every append, effectively disabling batching. Consider clamping `batchSize` to at least 1 (or another small default) after reading the config to avoid this misconfiguration causing unexpected behavior.
</issue_to_address>

### Comment 2
<location path="aggregator/events_test.go" line_range="64-73" />
<code_context>
+func TestBufferedEventsProcessedOnBatchThreshold(t *testing.T) {
</code_context>
<issue_to_address>
**suggestion (testing):** Avoid leaking global buffer state and timers across tests by resetting them and stopping the timer

This test depends on package-level globals (`advisoryBuffer`, `batchSize`, `flushTimeout`, `flushTimer`), which are shared across all tests and can cause cross-test interference (e.g., a long-lived `flushTimer` firing in other tests or `batchSize` remaining at 3). Please add reset/cleanup logic (for example via `t.Cleanup`) to stop the timer and restore these globals to their defaults, or centralize this in a small helper that reinitializes the buffer and timer for each test.
</issue_to_address>

### Comment 3
<location path="aggregator/events.go" line_range="14" />
<code_context>
 )

-// TODO: stub - will process advisory update events in batches and update account_advisory table
+var (
+	batchSize      int
+	flushTimeout   time.Duration
</code_context>
<issue_to_address>
**issue (complexity):** Consider encapsulating the buffering and timer logic into an advisoryBatcher type to avoid package-level mutable state and simplify the handler behaviorally unchanged.

Encapsulating the buffering/timer logic into a type will reduce global cross-cutting state, make initialization explicit, and keep advisoryUpdateHandler simpler without changing behavior.

You can wrap the current globals into a small `advisoryBatcher` and wire it from whatever config/bootstraps the handler:

```go
type advisoryBatcher struct {
	batchSize    int
	flushTimeout time.Duration

	mu      sync.Mutex
	buffer  []mqueue.AdvisoryUpdateEvent
	timer   *time.Timer
}

func newAdvisoryBatcher(batchSize int, flushTimeout time.Duration) *advisoryBatcher {
	b := &advisoryBatcher{
		batchSize:    batchSize,
		flushTimeout: flushTimeout,
		buffer:       make([]mqueue.AdvisoryUpdateEvent, 0, batchSize+1),
	}
	b.timer = time.AfterFunc(87600*time.Hour, func() {
		utils.LogInfo("flushing advisory buffer after timeout")
		b.flush()
	})
	return b
}
```

Then move buffer/timer operations into methods, keeping lock usage local and avoiding globals:

```go
func (b *advisoryBatcher) handle(m mqueue.KafkaMessage) error {
	var event mqueue.AdvisoryUpdateEvent
	if err := sonic.Unmarshal(m.Value, &event); err != nil {
		utils.LogError("err", err, "could not deserialize advisory update event")
		return nil
	}

	b.mu.Lock()
	b.buffer = append(b.buffer, event)
	b.timer.Reset(b.flushTimeout)
	shouldFlush := len(b.buffer) >= b.batchSize
	b.mu.Unlock()

	if shouldFlush {
		utils.LogInfo("flushing full advisory buffer")
		b.flush()
	}
	return nil
}

func (b *advisoryBatcher) flush() {
	b.mu.Lock()
	if len(b.buffer) == 0 {
		b.mu.Unlock()
		return
	}
	batch := make([]mqueue.AdvisoryUpdateEvent, len(b.buffer))
	copy(batch, b.buffer)
	b.buffer = b.buffer[:0]
	b.mu.Unlock()

	grouped := groupAdvisoryUpdates(batch)
	processAdvisoryBatch(grouped)
}
```

And the handler wiring becomes an explicit dependency instead of relying on `initBuffer` + globals:

```go
var advisoryBatcherInstance = newAdvisoryBatcher(batchSize, flushTimeout)

func advisoryUpdateHandler(m mqueue.KafkaMessage) error {
	return advisoryBatcherInstance.handle(m)
}
```

This keeps all current behavior (batching, timeout-based flush, grouping, DB work) but:

- Removes package-level mutable state.
- Makes initialization explicit and tied to handler setup.
- Localizes locking and timer interaction inside one type, reducing concurrency complexity.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread aggregator/aggregator.go
Comment thread aggregator/events_test.go
Comment thread aggregator/events.go
@katarinazaprazna
katarinazaprazna force-pushed the add-aggregator-consumer branch from 790b18c to 8687006 Compare July 29, 2026 21:49
Batch incoming AdvisoryUpdateEvent messages by rh_account_id,
deduplicate advisory IDs, and update the account_advisory table. After
each batch, run the drift check against the legacy table and publish
new-advisory notifications (gated behind enable_notifications flag).
@katarinazaprazna
katarinazaprazna force-pushed the add-aggregator-consumer branch from 8687006 to 7abc4d9 Compare July 29, 2026 22:04
@MichaelMraka MichaelMraka self-assigned this Jul 30, 2026

@MichaelMraka MichaelMraka left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR looks fine, please check failing unit test.

Aggregator tests delete account_advisory rows for rh_account_id=1 during
 cleanup. When manager reads from DB, it is empty, causing
 TestAdvisoriesExportJSON to fail. Add TestMain to aggregator to
 repopulate account_advisory after all tests complete.
@codecov-commenter

codecov-commenter commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.83851% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.27%. Comparing base (dd02a9f) to head (a0130d8).

Files with missing lines Patch % Lines
aggregator/notifications.go 73.58% 7 Missing and 7 partials ⚠️
aggregator/events.go 74.00% 9 Missing and 4 partials ⚠️
base/notification/notification.go 0.00% 13 Missing ⚠️
aggregator/drift_check.go 72.50% 7 Missing and 4 partials ⚠️
aggregator/aggregator.go 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2284      +/-   ##
==========================================
+ Coverage   59.10%   59.27%   +0.16%     
==========================================
  Files         147      148       +1     
  Lines        9332     9488     +156     
==========================================
+ Hits         5516     5624     +108     
- Misses       3244     3276      +32     
- Partials      572      588      +16     
Flag Coverage Δ
unittests 59.27% <65.83%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@katarinazaprazna
katarinazaprazna merged commit 3a30727 into RedHatInsights:master Jul 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants