perf(cmpp): decouple supplier result callbacks

This commit is contained in:
hectorzhao
2026-08-20 14:19:42 +08:00
parent 485af688d2
commit 67b760a599
27 changed files with 1000 additions and 66 deletions
+47 -6
View File
@@ -36,6 +36,7 @@ type Worker struct {
Limiter ratelimit.Limiter
Submit func(context.Context, queue.SubmitCommand) (queue.SubmitResult, error)
ReportDeadLetter func(context.Context, DeadLetterEvent) error
ResultOutbox SubmitResultOutbox
Stream string
Group string
Consumer string
@@ -50,6 +51,17 @@ type Worker struct {
inFlight atomic.Int64
}
type SubmitResultOutbox interface {
PublishSubmitResultAndAck(
context.Context,
string,
string,
string,
queue.SubmitCommand,
queue.SubmitResult,
) error
}
type DeadLetterEvent struct {
StreamMessageID string `json:"streamMessageId"`
TraceID string `json:"traceId,omitempty"`
@@ -82,6 +94,9 @@ func (w *Worker) Run(ctx context.Context) error {
if w.Upstream == nil {
return fmt.Errorf("upstream manager is required")
}
if w.ResultOutbox == nil {
return fmt.Errorf("submit result Outbox is required")
}
pool := newMessageWorkPool(ctx, w, w.concurrency())
defer pool.wait()
for {
@@ -292,7 +307,8 @@ func (w *Worker) processMessage(ctx context.Context, message redis.XMessage) err
if !command.CreatedAt.IsZero() {
metrics.ObserveSubmitStage("stream_wait", true, time.Since(command.CreatedAt))
}
if err := w.handleCommand(ctx, command); err != nil {
result, err := w.executeCommand(ctx, command)
if err != nil && (result.SubmitStatus == "" || result.SubmitStatus == "accepted") {
if ctx.Err() != nil {
return ctx.Err()
}
@@ -308,29 +324,50 @@ func (w *Worker) processMessage(ctx context.Context, message redis.XMessage) err
}
return err
}
return w.ackAndClearFailure(ctx, message.ID)
if err := w.ResultOutbox.PublishSubmitResultAndAck(
ctx,
w.stream(),
w.group(),
message.ID,
command,
result,
); err != nil {
return fmt.Errorf("persist aggregate submit result: %w", err)
}
// The command was ACKed atomically with the aggregate result event. Clearing the
// auxiliary retry counter may lag without affecting delivery correctness.
w.clearFailure(ctx, message.ID)
return nil
}
func (w *Worker) handleCommand(ctx context.Context, command queue.SubmitCommand) error {
func (w *Worker) executeCommand(ctx context.Context, command queue.SubmitCommand) (queue.SubmitResult, error) {
startedAt := time.Now()
limitStartedAt := time.Now()
if w.Limiter != nil {
if _, err := w.Limiter.Wait(ctx, command.ChannelID, command.Route.RateLimitPerSecond); err != nil {
metrics.ObserveSubmitStage("rate_limit_wait", false, time.Since(limitStartedAt))
return err
return queue.SubmitResult{}, err
}
}
metrics.ObserveSubmitStage("rate_limit_wait", true, time.Since(limitStartedAt))
submit := w.Submit
if submit == nil {
if w.Upstream == nil {
return fmt.Errorf("upstream manager is required")
return queue.SubmitResult{}, fmt.Errorf("upstream manager is required")
}
submit = w.Upstream.Submit
}
result, err := submit(ctx, command)
accepted := err == nil && (result.SubmitStatus == "" || result.SubmitStatus == "accepted")
metrics.ObserveSubmit(accepted, time.Since(startedAt))
return result, err
}
// handleCommand remains a narrow compatibility seam for focused tests and the
// control path. Stream consumption uses executeCommand so it can persist the
// exact terminal result before acknowledging the command.
func (w *Worker) handleCommand(ctx context.Context, command queue.SubmitCommand) error {
result, err := w.executeCommand(ctx, command)
if err != nil && (result.SubmitStatus == "" || result.SubmitStatus == "accepted") {
return err
}
@@ -456,10 +493,14 @@ func (w *Worker) ackAndClearFailure(ctx context.Context, messageID string) error
if err := w.Redis.XAck(ctx, w.stream(), w.group(), messageID).Err(); err != nil {
return err
}
w.clearFailure(ctx, messageID)
return nil
}
func (w *Worker) clearFailure(ctx context.Context, messageID string) {
if err := w.Redis.HDel(ctx, w.failureAttemptsKey(), messageID).Err(); err != nil {
w.logf("gateway submit worker clear failure %s failed: %v", messageID, err)
}
return nil
}
func (w *Worker) stream() string {