Skip to content

Commit 2deb9fe

Browse files
committed
feat(submitqueue): activate request status materialization
Route synchronous, queued, and DLQ request-log writes through the shared materializer so authoritative and queue projections converge with retained history. Preserve the original DLQ failure reason as the terminal request last error. Validation: make fmt && make build && make test && make e2e-test
1 parent 1803e8e commit 2deb9fe

26 files changed

Lines changed: 455 additions & 299 deletions

File tree

service/submitqueue/gateway/server/main.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,9 @@ func run() error {
215215
// Build the topic registry. The gateway publishes to the start of the
216216
// orchestrator pipeline (TopicKeyStart) and the cancel topic (TopicKeyCancel) —
217217
// both publish-only. It additionally consumes the log topic (TopicKeyLog):
218-
// the gateway is the sole writer of the request log, persisting entries that
219-
// the orchestrator publishes there.
218+
// the gateway persists normal pipeline entries that the orchestrator
219+
// publishes there. Orchestrator DLQ reconciliation may repair terminal
220+
// request-log projections directly.
220221
registry, err := consumer.NewTopicRegistry([]consumer.TopicConfig{
221222
{Key: topickey.TopicKeyStart, Name: "start", Queue: mysqlQueue},
222223
{Key: topickey.TopicKeyCancel, Name: "cancel", Queue: mysqlQueue},
@@ -249,8 +250,8 @@ func run() error {
249250

250251
// Initialize storage from the shared app database connection. The land
251252
// controller writes to this store directly; cancel/status use the request
252-
// log store directly. The log consumer (registered below) is the sole
253-
// persister of request log entries published by the orchestrator.
253+
// log store directly. The log consumer registered below persists request
254+
// log entries published by the orchestrator's normal pipeline.
254255
store, err := mysqlstorage.NewStorage(appDB, scope.SubScope("storage"))
255256
if err != nil {
256257
return fmt.Errorf("failed to create storage: %w", err)
@@ -271,7 +272,7 @@ func run() error {
271272
// Create controllers and wrap them for gRPC
272273
pingController := controller.NewPingController(logger, scope)
273274
landController := controller.NewLandController(logger.Sugar(), scope, cnt, store, queueConfigs, registry)
274-
cancelController := controller.NewCancelController(logger.Sugar(), scope, requestLogStore, registry)
275+
cancelController := controller.NewCancelController(logger.Sugar(), scope, store, registry)
275276
statusController := controller.NewStatusController(logger.Sugar(), scope, requestLogStore)
276277
gatewayServer := &GatewayServer{
277278
pingController: pingController,
@@ -285,9 +286,10 @@ func run() error {
285286
// Register reflection service for debugging with grpcurl
286287
reflection.Register(grpcServer)
287288

288-
// Create the queue consumer and register the log controller. The gateway is
289-
// the sole persister of the request log: the orchestrator publishes entries
290-
// to the log topic and this consumer writes them to storage.
289+
// Create the queue consumer and register the log controller. The orchestrator
290+
// publishes normal pipeline entries to the log topic and this consumer writes
291+
// them to storage. Orchestrator DLQ reconciliation repairs terminal entries
292+
// directly so reconciliation does not depend on another asynchronous path.
291293
logConsumer := consumer.New(logger.Sugar(), scope.SubScope("consumer"), registry,
292294
errs.NewClassifierProcessor(
293295
// Storage (submitqueue/extension/storage/mysql) and queue (platform/extension/messagequeue/mysql)

service/submitqueue/orchestrator/server/main.go

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,9 @@ func newTopicRegistry(q extqueue.Queue, subscriberName string) (consumer.TopicRe
427427
})
428428
}
429429

430-
// Publish-only: the orchestrator emits request log entries to the log
431-
// topic but never persists them. The gateway is the sole consumer that
432-
// writes the request log to storage, so the orchestrator registers no
433-
// consuming subscription (and therefore no log DLQ) for this topic.
430+
// Publish-only: the orchestrator emits request-log entries to the log topic.
431+
// The gateway is the sole consumer and writer of request logs and public
432+
// projections, so the orchestrator registers no consuming subscription.
434433
configs = append(configs, consumer.TopicConfig{
435434
Key: topickey.TopicKeyLog,
436435
Name: "log",
@@ -752,19 +751,19 @@ func registerDLQControllers(c consumer.Consumer, logger *zap.SugaredLogger, scop
752751
name string
753752
ctl consumer.Controller
754753
}{
755-
{"start_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, dlq.DecodeLandRequestID, dlq.TopicKey(topickey.TopicKeyStart), "orchestrator-start-dlq")},
756-
{"cancel_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, dlq.DecodeCancelRequestID, dlq.TopicKey(topickey.TopicKeyCancel), "orchestrator-cancel-dlq")},
757-
{"validate_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, dlq.DecodeRequestID, dlq.TopicKey(topickey.TopicKeyValidate), "orchestrator-validate-dlq")},
758-
{"mergeconflictsignal_dlq", dlq.NewDLQMergeConflictSignalController(logger, dlqScope, store, dlq.TopicKey(runwaymq.TopicKeyMergeConflictCheckSignal), "orchestrator-mergeconflictsignal-dlq")},
759-
{"batch_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, dlq.DecodeRequestID, dlq.TopicKey(topickey.TopicKeyBatch), "orchestrator-batch-dlq")},
760-
{"score_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, dlq.TopicKey(topickey.TopicKeyScore), "orchestrator-score-dlq")},
761-
{"speculate_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, dlq.TopicKey(topickey.TopicKeySpeculate), "orchestrator-speculate-dlq")},
754+
{"start_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, registry, dlq.DecodeLandRequestID, dlq.TopicKey(topickey.TopicKeyStart), "orchestrator-start-dlq")},
755+
{"cancel_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, registry, dlq.DecodeCancelRequestID, dlq.TopicKey(topickey.TopicKeyCancel), "orchestrator-cancel-dlq")},
756+
{"validate_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, registry, dlq.DecodeRequestID, dlq.TopicKey(topickey.TopicKeyValidate), "orchestrator-validate-dlq")},
757+
{"mergeconflictsignal_dlq", dlq.NewDLQMergeConflictSignalController(logger, dlqScope, store, registry, dlq.TopicKey(runwaymq.TopicKeyMergeConflictCheckSignal), "orchestrator-mergeconflictsignal-dlq")},
758+
{"batch_dlq", dlq.NewDLQRequestController(logger, dlqScope, store, registry, dlq.DecodeRequestID, dlq.TopicKey(topickey.TopicKeyBatch), "orchestrator-batch-dlq")},
759+
{"score_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, registry, dlq.TopicKey(topickey.TopicKeyScore), "orchestrator-score-dlq")},
760+
{"speculate_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, registry, dlq.TopicKey(topickey.TopicKeySpeculate), "orchestrator-speculate-dlq")},
762761
{"prioritize_dlq", dlq.NewDLQQueueController(logger, dlqScope, registry, dlq.TopicKey(topickey.TopicKeyPrioritize), "orchestrator-prioritize-dlq")},
763-
{"build_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, dlq.TopicKey(topickey.TopicKeyBuild), "orchestrator-build-dlq")},
764-
{"buildsignal_dlq", dlq.NewDLQBuildSignalController(logger, dlqScope, store, dlq.TopicKey(topickey.TopicKeyBuildSignal), "orchestrator-buildsignal-dlq")},
765-
{"merge_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, dlq.TopicKey(topickey.TopicKeyMerge), "orchestrator-merge-dlq")},
766-
{"mergesignal_dlq", dlq.NewDLQMergeSignalController(logger, dlqScope, store, dlq.TopicKey(runwaymq.TopicKeyMergeSignal), "orchestrator-mergesignal-dlq")},
767-
{"conclude_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, dlq.TopicKey(topickey.TopicKeyConclude), "orchestrator-conclude-dlq")},
762+
{"build_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, registry, dlq.TopicKey(topickey.TopicKeyBuild), "orchestrator-build-dlq")},
763+
{"buildsignal_dlq", dlq.NewDLQBuildSignalController(logger, dlqScope, store, registry, dlq.TopicKey(topickey.TopicKeyBuildSignal), "orchestrator-buildsignal-dlq")},
764+
{"merge_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, registry, dlq.TopicKey(topickey.TopicKeyMerge), "orchestrator-merge-dlq")},
765+
{"mergesignal_dlq", dlq.NewDLQMergeSignalController(logger, dlqScope, store, registry, dlq.TopicKey(runwaymq.TopicKeyMergeSignal), "orchestrator-mergesignal-dlq")},
766+
{"conclude_dlq", dlq.NewDLQBatchController(logger, dlqScope, store, registry, dlq.TopicKey(topickey.TopicKeyConclude), "orchestrator-conclude-dlq")},
768767
}
769768
var count int
770769
for _, reg := range dlqRegs {

submitqueue/gateway/controller/cancel.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
entityqueue "github.com/uber/submitqueue/platform/base/messagequeue"
2424
"github.com/uber/submitqueue/platform/consumer"
2525
"github.com/uber/submitqueue/platform/errs"
26+
requestcore "github.com/uber/submitqueue/submitqueue/core/request"
2627
"github.com/uber/submitqueue/submitqueue/core/topickey"
2728
"github.com/uber/submitqueue/submitqueue/entity"
2829
"github.com/uber/submitqueue/submitqueue/extension/storage"
@@ -35,21 +36,23 @@ import (
3536
// and returns a response. The orchestrator-side cancel controller performs the actual
3637
// state transitions and emits the terminal RequestStatusCancelled log entry.
3738
type CancelController struct {
38-
logger *zap.SugaredLogger
39-
metricsScope tally.Scope
40-
requestLogStore storage.RequestLogStore
41-
registry consumer.TopicRegistry
39+
logger *zap.SugaredLogger
40+
metricsScope tally.Scope
41+
requestSummaryStore storage.RequestSummaryStore
42+
materializer *requestcore.Materializer
43+
registry consumer.TopicRegistry
4244
}
4345

4446
// NewCancelController creates a new instance of the gateway cancel controller.
45-
// The controller writes a RequestStatusCancelling log entry through requestLogStore and
47+
// The controller writes a RequestStatusCancelling log entry through the shared materializer and
4648
// publishes cancel requests to the topic registered under topickey.TopicKeyCancel.
47-
func NewCancelController(logger *zap.SugaredLogger, scope tally.Scope, requestLogStore storage.RequestLogStore, registry consumer.TopicRegistry) *CancelController {
49+
func NewCancelController(logger *zap.SugaredLogger, scope tally.Scope, store storage.Storage, registry consumer.TopicRegistry) *CancelController {
4850
return &CancelController{
49-
logger: logger,
50-
metricsScope: scope,
51-
requestLogStore: requestLogStore,
52-
registry: registry,
51+
logger: logger,
52+
metricsScope: scope,
53+
requestSummaryStore: store.GetRequestSummaryStore(),
54+
materializer: requestcore.NewMaterializer(store),
55+
registry: registry,
5356
}
5457
}
5558

@@ -78,18 +81,13 @@ func (c *CancelController) Cancel(ctx context.Context, req entity.CancelRequest)
7881
"reason", req.Reason,
7982
)
8083

81-
// Verify the sqid exists before recording intent or publishing. Cancel is opt-in
82-
// by sqid; an unknown sqid is a user error and must never leave a cancelling log
83-
// row or a queue message behind for a request that never existed. The Land
84-
// controller writes its "accepted" log entry synchronously to the same store, so
85-
// a NotFound here reliably means "this sqid was never accepted by the gateway"
86-
// rather than "in flight" — there is no false-negative race window.
87-
if _, err := c.requestLogStore.List(ctx, req.ID); err != nil {
84+
// Verify the sqid exists before recording intent or publishing.
85+
if _, err := c.requestSummaryStore.Get(ctx, req.ID); err != nil {
8886
if storage.IsNotFound(err) {
8987
c.metricsScope.Counter("cancel_request_not_found").Inc(1)
9088
return errs.NewUserError(&RequestNotFoundError{Sqid: req.ID})
9189
}
92-
return fmt.Errorf("CancelController failed to look up request log for sqid=%s: %w", req.ID, err)
90+
return fmt.Errorf("CancelController failed to look up request summary for sqid=%s: %w", req.ID, err)
9391
}
9492

9593
// Record the user's intent in the request log before publishing. Writing direct to the
@@ -100,7 +98,7 @@ func (c *CancelController) Cancel(ctx context.Context, req entity.CancelRequest)
10098
metadata["reason"] = req.Reason
10199
}
102100
logEntry := entity.NewRequestLog(req.ID, entity.RequestStatusCancelling, 0, "", metadata)
103-
if err := c.requestLogStore.Insert(ctx, logEntry); err != nil {
101+
if err := c.materializer.PersistLog(ctx, logEntry); err != nil {
104102
return fmt.Errorf("CancelController failed to insert cancelling log for sqid=%s: %w", req.ID, err)
105103
}
106104

0 commit comments

Comments
 (0)