Skip to content

Commit 316aa7d

Browse files
committed
refactor(id): Let queue operate on IDs and controller on database
Instead of serializing all fo the Request or Batch entity to the queue, only serialize the appropriate ID. Let controller to get the latest state of the entity from the database directly. This prevents stale entities and improves optimistic concurrency timing.
1 parent e8eb455 commit 316aa7d

27 files changed

Lines changed: 678 additions & 333 deletions

entity/batch.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,21 @@ func BatchFromBytes(data []byte) (Batch, error) {
8989
err := json.Unmarshal(data, &batch)
9090
return batch, err
9191
}
92+
93+
// BatchID is a lightweight entity for publishing and consuming just the batch identifier via the queue.
94+
type BatchID struct {
95+
// ID is the globally unique identifier for the batch.
96+
ID string `json:"id"`
97+
}
98+
99+
// ToBytes serializes the BatchID to JSON bytes for queue message payload.
100+
func (b BatchID) ToBytes() ([]byte, error) {
101+
return json.Marshal(b)
102+
}
103+
104+
// BatchIDFromBytes deserializes a BatchID from JSON bytes.
105+
func BatchIDFromBytes(data []byte) (BatchID, error) {
106+
var bid BatchID
107+
err := json.Unmarshal(data, &bid)
108+
return bid, err
109+
}

entity/request.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,21 @@ func RequestFromBytes(data []byte) (Request, error) {
104104
err := json.Unmarshal(data, &req)
105105
return req, err
106106
}
107+
108+
// RequestID is a lightweight entity for publishing and consuming just the request identifier via the queue.
109+
type RequestID struct {
110+
// ID is the globally unique identifier for the land request.
111+
ID string `json:"id"`
112+
}
113+
114+
// ToBytes serializes the RequestID to JSON bytes for queue message payload.
115+
func (r RequestID) ToBytes() ([]byte, error) {
116+
return json.Marshal(r)
117+
}
118+
119+
// RequestIDFromBytes deserializes a RequestID from JSON bytes.
120+
func RequestIDFromBytes(data []byte) (RequestID, error) {
121+
var rid RequestID
122+
err := json.Unmarshal(data, &rid)
123+
return rid, err
124+
}

example/server/orchestrator/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ func registerControllers(c consumer.Consumer, logger *zap.SugaredLogger, scope t
390390
validateController := validate.NewController(
391391
logger,
392392
scope,
393+
store,
393394
registry,
394395
mc,
395396
consumer.TopicKeyValidate,
@@ -415,6 +416,7 @@ func registerControllers(c consumer.Consumer, logger *zap.SugaredLogger, scope t
415416
scoreController := score.NewController(
416417
logger,
417418
scope,
419+
store,
418420
registry,
419421
consumer.TopicKeyScore,
420422
"orchestrator-score",
@@ -426,6 +428,7 @@ func registerControllers(c consumer.Consumer, logger *zap.SugaredLogger, scope t
426428
speculateController := speculate.NewController(
427429
logger,
428430
scope,
431+
store,
429432
registry,
430433
consumer.TopicKeySpeculate,
431434
"orchestrator-speculate",
@@ -437,6 +440,7 @@ func registerControllers(c consumer.Consumer, logger *zap.SugaredLogger, scope t
437440
buildController := build.NewController(
438441
logger,
439442
scope,
443+
store,
440444
registry,
441445
consumer.TopicKeyBuild,
442446
"orchestrator-build",
@@ -448,6 +452,7 @@ func registerControllers(c consumer.Consumer, logger *zap.SugaredLogger, scope t
448452
buildsignalController := buildsignal.NewController(
449453
logger,
450454
scope,
455+
store,
451456
registry,
452457
consumer.TopicKeyBuildSignal,
453458
"orchestrator-buildsignal",
@@ -459,6 +464,7 @@ func registerControllers(c consumer.Consumer, logger *zap.SugaredLogger, scope t
459464
mergeController := merge.NewController(
460465
logger,
461466
scope,
467+
store,
462468
registry,
463469
consumer.TopicKeyMerge,
464470
"orchestrator-merge",

orchestrator/controller/batch/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ go_test(
2222
embed = [":batch"],
2323
deps = [
2424
"//core/consumer",
25-
"//core/errs",
2625
"//entity",
2726
"//entity/queue",
2827
"//extension/counter/mock",

orchestrator/controller/batch/batch.go

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package batch
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021

2122
"github.com/uber-go/tally/v4"
@@ -72,12 +73,18 @@ func (c *Controller) Process(ctx context.Context, delivery consumer.Delivery) er
7273

7374
msg := delivery.Message()
7475

75-
// Deserialize request entity
76-
request, err := entity.RequestFromBytes(msg.Payload)
76+
// Deserialize request ID from payload
77+
rid, err := entity.RequestIDFromBytes(msg.Payload)
7778
if err != nil {
7879
c.metricsScope.Counter("deserialize_errors").Inc(1)
79-
// Non-retryable: malformed messages will never succeed regardless of retry count
80-
return fmt.Errorf("failed to deserialize request: %w", err)
80+
return fmt.Errorf("failed to deserialize request ID: %w", err)
81+
}
82+
83+
// Fetch request from storage
84+
request, err := c.store.GetRequestStore().Get(ctx, rid.ID)
85+
if err != nil {
86+
c.metricsScope.Counter("storage_errors").Inc(1)
87+
return fmt.Errorf("failed to get request %s: %w", rid.ID, err)
8188
}
8289

8390
c.logger.Infow("received batch event",
@@ -139,11 +146,18 @@ func (c *Controller) Process(ctx context.Context, delivery consumer.Delivery) er
139146
if err == nil {
140147
bd.Dependents = append(existing.Dependents, bd.Dependents...)
141148
}
149+
150+
if err := c.store.GetBatchDependentStore().Create(ctx, bd); err != nil && !errors.Is(err, storage.ErrAlreadyExists) {
151+
c.metricsScope.Counter("batch_dependent_store_errors").Inc(1)
152+
return fmt.Errorf("failed to create batch dependent for batchID=%s: %w", dep.ID, err)
153+
}
142154
}
143155

144-
// TODO:
145-
// - Add batch to DB
146-
// - Add to batch dependent DB
156+
// Persist batch to storage (idempotent — ErrAlreadyExists means a retry)
157+
if err := c.store.GetBatchStore().Create(ctx, batch); err != nil && !errors.Is(err, storage.ErrAlreadyExists) {
158+
c.metricsScope.Counter("batch_store_errors").Inc(1)
159+
return fmt.Errorf("failed to create batch: %w", err)
160+
}
147161

148162
c.logger.Infow("batch created",
149163
"batch_id", batch.ID,
@@ -153,7 +167,7 @@ func (c *Controller) Process(ctx context.Context, delivery consumer.Delivery) er
153167
)
154168

155169
// Publish to score topic
156-
if err := c.publish(ctx, consumer.TopicKeyScore, batch); err != nil {
170+
if err := c.publish(ctx, consumer.TopicKeyScore, batch.ID, batch.Queue); err != nil {
157171
c.metricsScope.Counter("publish_errors").Inc(1)
158172
return fmt.Errorf("failed to publish to score: %w", err)
159173
}
@@ -168,14 +182,15 @@ func (c *Controller) Process(ctx context.Context, delivery consumer.Delivery) er
168182
return nil // Success - message will be acked
169183
}
170184

171-
// publish publishes a batch to the specified topic key.
172-
func (c *Controller) publish(ctx context.Context, key consumer.TopicKey, batch entity.Batch) error {
173-
payload, err := batch.ToBytes()
185+
// publish publishes a batch ID to the specified topic key.
186+
func (c *Controller) publish(ctx context.Context, key consumer.TopicKey, batchID string, partitionKey string) error {
187+
bid := entity.BatchID{ID: batchID}
188+
payload, err := bid.ToBytes()
174189
if err != nil {
175-
return fmt.Errorf("failed to serialize batch: %w", err)
190+
return fmt.Errorf("failed to serialize batch ID: %w", err)
176191
}
177192

178-
msg := entityqueue.NewMessage(batch.ID, payload, batch.Queue, nil)
193+
msg := entityqueue.NewMessage(batchID, payload, partitionKey, nil)
179194

180195
q, ok := c.registry.Queue(key)
181196
if !ok {

0 commit comments

Comments
 (0)