Files
warmbly/internal/app/consumer/service.go
T
Matthew Meszaros 7e02bb2a5a feat(consumer): hourly risk rebalancer migrates mailboxes between risk pools
New background job in the consumer process:

  1. Pulls up to 1000 mailbox candidates joined with their worst warmup
     health state (across all pools they participate in) and their
     current worker's risk_pool. Dedicated workers are excluded — single
     tenant, segregation not applicable.
  2. Recomputes risk_band from health state via RiskBandFromHealth.
     If it changed, writes the new band.
  3. If the band's matching pool doesn't equal the worker's pool, picks
     a new worker via SelectSharedWorkerForBand and migrates the mailbox.
     Increments/decrements account counts.
  4. Logs each migration to admin_audit_log with action=
     "risk_rebalance_migrate" so operators see what moved and why.

Boot-time run + hourly ticker. Rebalancing is intentionally batch, not
event-driven: warmup health states change on a slow rolling-window basis
(warmup_health_sweep is also hourly), so reacting in real time gains
nothing and would cause thundering-herd migrations.

JobsService gets an AssignmentService dep. Nil disables the job (lets
self-hosters opt out by simply not wiring it).
2026-05-19 05:39:27 +00:00

53 lines
1.9 KiB
Go

package jobs
import (
"context"
"github.com/warmbly/warmbly/internal/app/advanced"
warmupapp "github.com/warmbly/warmbly/internal/app/warmup"
workerapp "github.com/warmbly/warmbly/internal/app/worker"
"github.com/warmbly/warmbly/internal/events"
"github.com/warmbly/warmbly/internal/infrastructure/cache"
"github.com/warmbly/warmbly/internal/infrastructure/kafka"
"github.com/warmbly/warmbly/internal/infrastructure/pubsub"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/repository"
)
type JobsService struct {
Consumer *kafka.Consumer
UniboxRepository repository.UniboxRepository
MailboxRepository repository.MailboxRepository
EmailRepository repository.EmailRepository
EmailHistoryIDRepository repository.EmailHistoryIDRepository
EmailAccountErrorRepository repository.EmailAccountErrorRepository
WarmupRepo repository.WarmupRepository
WarmupService warmupapp.Service
WorkerRepo repository.WorkerRepository
// Publisher for sending events to workers
Publisher events.Publisher
// Pub/Sub for real-time notifications to users
StreamingPublisher *pubsub.StreamingPublisher
AdvancedService advanced.Service
// Cache for dead worker detection
Cache *cache.Cache
// AdminRepo for writing audit-log rows when the dead-worker job
// auto-reassigns email accounts (optional — heartbeat sync also writes
// here so admins can see why their fleet moved). Nil disables logging.
AdminRepo repository.AdminRepository
// AssignmentService is used by the risk rebalancer to pick replacement
// workers when a mailbox's risk band changes. Nil disables the job.
AssignmentService workerapp.WorkerAssignmentService
eventHandlers map[models.JobEventType]func(ctx context.Context, body any) error
}
func (s *JobsService) Start(ctx context.Context) {
s.Consumer.Consume(ctx, s.Receive)
}