-
Notifications
You must be signed in to change notification settings - Fork 3
feat(stovepipe): extend Request entity to support new process step #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mnoah1/stovepipe-queue-store
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // Copyright (c) 2025 Uber Technologies, Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package entity | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
| ) | ||
|
|
||
| // CompareRequestID compares ingest order of two request IDs in the same queue. | ||
| // Returns -1 if a is older than b, 0 if equal, 1 if a is newer than b. | ||
| // IDs must follow the format request/<queue>/<counter>. | ||
| func CompareRequestID(queue, a, b string) (int, error) { | ||
| if a == b { | ||
| return 0, nil | ||
| } | ||
| aCounter, err := requestCounter(a, queue) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| bCounter, err := requestCounter(b, queue) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| switch { | ||
| case aCounter < bCounter: | ||
| return -1, nil | ||
| case aCounter > bCounter: | ||
| return 1, nil | ||
| default: | ||
| return 0, nil | ||
| } | ||
| } | ||
|
|
||
| // requestIDPrefix returns the expected prefix for request ids in queue: "request/<queue>/". | ||
| func requestIDPrefix(queue string) string { | ||
| return "request/" + queue + "/" | ||
| } | ||
|
|
||
| // requestCounter extracts the per-queue counter suffix from a request id. | ||
| func requestCounter(id, queue string) (int64, error) { | ||
| prefix := requestIDPrefix(queue) | ||
| if !strings.HasPrefix(id, prefix) { | ||
| return 0, fmt.Errorf("request id %q does not match queue %q", id, queue) | ||
| } | ||
| return strconv.ParseInt(id[len(prefix):], 10, 64) | ||
| } | ||
|
Comment on lines
+53
to
+60
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to understand the format the ID? can we treat it string which is separate from seq?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going back and forth a bit on this (initially I was thinking we'll track sequence and ID as two separate fields). But as I worked on this branch, realized we could avoid a separate sequence field if we require sequence info to be encoded in the ID and use a comparison function. Do you have any thoughts on one approach vs. the other? Obviously there is the cost to parse the int on each comparison, but doing that a handful of times per request will be far from a bottleneck. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| // Copyright (c) 2025 Uber Technologies, Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package entity | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestCompareRequestID(t *testing.T) { | ||
| const queue = "monorepo/main" | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| a string | ||
| b string | ||
| want int | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "older vs newer", | ||
| a: "request/monorepo/main/7", | ||
| b: "request/monorepo/main/10", | ||
| want: -1, | ||
| }, | ||
| { | ||
| name: "newer vs older", | ||
| a: "request/monorepo/main/10", | ||
| b: "request/monorepo/main/7", | ||
| want: 1, | ||
| }, | ||
| { | ||
| name: "equal", | ||
| a: "request/monorepo/main/42", | ||
| b: "request/monorepo/main/42", | ||
| want: 0, | ||
| }, | ||
| { | ||
| name: "numeric not lexicographic", | ||
| a: "request/monorepo/main/9", | ||
| b: "request/monorepo/main/10", | ||
| want: -1, | ||
| }, | ||
| { | ||
| name: "wrong queue prefix", | ||
| a: "request/other/1", | ||
| b: "request/monorepo/main/2", | ||
| wantErr: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got, err := CompareRequestID(queue, tt.a, tt.b) | ||
| if tt.wantErr { | ||
| require.Error(t, err) | ||
| return | ||
| } | ||
| require.NoError(t, err) | ||
| assert.Equal(t, tt.want, got) | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| -- queue holds per-queue coordination state for the validation pipeline: the last-green | ||
| -- bookmark, in-flight gate count, and latest-request sequence pointer. | ||
| -- bookmark, in-flight gate count, and latest-request id pointer. | ||
| CREATE TABLE IF NOT EXISTS queue ( | ||
| name VARCHAR(255) NOT NULL, | ||
| last_green_uri VARCHAR(255) NOT NULL DEFAULT '', | ||
| in_flight_count INT NOT NULL DEFAULT 0, | ||
| latest_request_seq BIGINT NOT NULL DEFAULT 0, | ||
| latest_request_id VARCHAR(255) NOT NULL DEFAULT '', | ||
| version INT NOT NULL, | ||
| PRIMARY KEY (name) | ||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need this in request itself? would we publish to build only when the last one is done? i guess question i have is where to we decide latest last green?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We currently propose doing this just before starting builds (process steps - 7a).
Once selected above, we need to store it somewhere for retrieval once the builds start. I think
Requestis reasonable for the MVP, but alternatively we could put it in a separate table of planned builds. The latest green value stored in the Queue entity represents more of a moving bookmark of latest green, while the value here serves as the fixed base for that specific request.Yes - when the concurrent builds is 1, that is effectively true. When increased, it would be whenever an available build slot has become free.