From 899ba7a06a5c509d9f2a7821550be12211db2906 Mon Sep 17 00:00:00 2001 From: Albert Wu Date: Wed, 8 Jul 2026 15:50:20 -0700 Subject: [PATCH] feat(storage): add request read-model schema Summary: Add gateway request summary entities and additive SQL tables keyed for sqid, queue receipt history, and change URI lookup. The tables are initially unused and may be deployed empty before any runtime writer is enabled. Test Plan: make fmt make gazelle make lint-license ./tool/bazel test //submitqueue/entity:go_default_test //test/integration/submitqueue/extension/storage/mysql:go_default_test --test_output=errors Revert Plan: Revert this commit. Empty additive tables may remain without affecting existing behavior. API Changes: None. Monitoring and Alerts: N/A --- submitqueue/entity/BUILD.bazel | 1 + submitqueue/entity/request_summary.go | 70 +++++++++++++++++++ .../extension/storage/mysql/schema/README.md | 19 +++++ .../mysql/schema/request_by_change_uri.sql | 6 ++ .../storage/mysql/schema/request_summary.sql | 13 ++++ .../mysql/schema/request_summary_by_queue.sql | 11 +++ 6 files changed, 120 insertions(+) create mode 100644 submitqueue/entity/request_summary.go create mode 100644 submitqueue/extension/storage/mysql/schema/request_by_change_uri.sql create mode 100644 submitqueue/extension/storage/mysql/schema/request_summary.sql create mode 100644 submitqueue/extension/storage/mysql/schema/request_summary_by_queue.sql diff --git a/submitqueue/entity/BUILD.bazel b/submitqueue/entity/BUILD.bazel index 47f52b4d..a2514139 100644 --- a/submitqueue/entity/BUILD.bazel +++ b/submitqueue/entity/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "queue_config.go", "request.go", "request_log.go", + "request_summary.go", "speculation_tree.go", ], importpath = "github.com/uber/submitqueue/submitqueue/entity", diff --git a/submitqueue/entity/request_summary.go b/submitqueue/entity/request_summary.go new file mode 100644 index 00000000..913e4226 --- /dev/null +++ b/submitqueue/entity/request_summary.go @@ -0,0 +1,70 @@ +// 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 + +// RequestSummary is the gateway-owned materialized current view of a request. +// RequestID is exposed as sqid by the gateway API. +type RequestSummary struct { + // RequestID is the globally unique request identifier. + RequestID string + // Queue is the queue supplied at receipt. + Queue string + // ChangeURIs are the change URIs supplied at receipt in caller order. + ChangeURIs []string + // ReceivedAtMs is the immutable receipt timestamp in Unix milliseconds. + ReceivedAtMs int64 + // Status is the current customer-facing request status. + Status RequestStatus + // RequestVersion is the orchestrator request version carried by the winning log entry, or zero when unavailable. + RequestVersion int32 + // StatusTimestampMs is the timestamp of the winning log entry in Unix milliseconds. + StatusTimestampMs int64 + // ProjectionVersion is the optimistic-lock version of this materialized view. + ProjectionVersion int32 + // LastError is the error associated with the current status, or empty when absent. + LastError string + // Metadata is display and debugging metadata associated with the current status. + Metadata map[string]string +} + +// RequestQueueSummary is the queue-ordered projection returned by List. +type RequestQueueSummary struct { + // RequestID is the globally unique request identifier. + RequestID string + // Queue is the queue supplied at receipt. + Queue string + // ChangeURIs are the change URIs supplied at receipt in caller order. + ChangeURIs []string + // ReceivedAtMs is the immutable receipt timestamp in Unix milliseconds. + ReceivedAtMs int64 + // Status is the current customer-facing request status. + Status RequestStatus + // ProjectionVersion is copied from the authoritative RequestSummary and guards stale projection writers. + ProjectionVersion int32 + // LastError is the error associated with the current status, or empty when absent. + LastError string + // Metadata is display and debugging metadata associated with the current status. + Metadata map[string]string +} + +// RequestURI maps one change URI to one received request. +type RequestURI struct { + // ChangeURI is the exact canonical URI supplied at receipt. + ChangeURI string + // ReceivedAtMs is the immutable receipt timestamp in Unix milliseconds. + ReceivedAtMs int64 + // RequestID is the globally unique request identifier. + RequestID string +} diff --git a/submitqueue/extension/storage/mysql/schema/README.md b/submitqueue/extension/storage/mysql/schema/README.md index 9e8c36bf..b6bc16be 100644 --- a/submitqueue/extension/storage/mysql/schema/README.md +++ b/submitqueue/extension/storage/mysql/schema/README.md @@ -22,3 +22,22 @@ As the `batch` table grows, the secondary index will grow with it, increasing st The `change` table records per-URI claims by in-flight requests. `request_id` is part of the primary key so that concurrent claims on the same URI by different requests coexist as distinct rows — a same-request retry collides on the PK and is a no-op (`INSERT IGNORE`), while a different-request claim is a new row that `GetByURI` surfaces for overlap detection. `queue` leads the key so queue-scoped lookups are primary-key-prefix scans and the table is shardable by queue. +## Gateway request read model + +The gateway request read model uses three additive tables and requires no alteration of existing tables. Deployments create these tables empty and populate them only for requests received after rollout; historical request logs and orchestrator working tables are intentionally not backfilled. + +### `request_summary` + +`request_summary` is keyed by `request_id` and serves direct Status lookup. It stores immutable receipt context plus the current materialized request-log winner and its optimistic-lock projection version. + +### `request_summary_by_queue` + +`request_summary_by_queue` is keyed by `(queue, received_at_ms, request_id)`. This key covers the List predicate, descending sort, and keyset continuation for one bounded receipt-time window without a secondary index. The row duplicates the complete List response so one page is served by one range scan rather than one follow-up read per request ID. + +### `request_by_change_uri` + +`request_by_change_uri` is keyed by `(change_uri, received_at_ms, request_id)` and serves bounded newest-first Status lookup by change URI. The gateway reads at most 101 mappings to enforce the API maximum of 100 results without silently truncating. + +### JSON collections + +`change_uris` and `metadata` are non-null application values. MySQL JSON columns can contain the JSON value `null` despite `NOT NULL`, so stores normalize nil slices and maps to empty values on both write and read. diff --git a/submitqueue/extension/storage/mysql/schema/request_by_change_uri.sql b/submitqueue/extension/storage/mysql/schema/request_by_change_uri.sql new file mode 100644 index 00000000..9f10836f --- /dev/null +++ b/submitqueue/extension/storage/mysql/schema/request_by_change_uri.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS request_by_change_uri ( + change_uri VARCHAR(255) NOT NULL, + received_at_ms BIGINT NOT NULL, + request_id VARCHAR(255) NOT NULL, + PRIMARY KEY (change_uri, received_at_ms, request_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/submitqueue/extension/storage/mysql/schema/request_summary.sql b/submitqueue/extension/storage/mysql/schema/request_summary.sql new file mode 100644 index 00000000..1ab5becb --- /dev/null +++ b/submitqueue/extension/storage/mysql/schema/request_summary.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS request_summary ( + request_id VARCHAR(255) NOT NULL, + queue VARCHAR(255) NOT NULL, + change_uris JSON NOT NULL, + received_at_ms BIGINT NOT NULL, + status VARCHAR(64) NOT NULL, + request_version INT NOT NULL, + status_timestamp_ms BIGINT NOT NULL, + projection_version INT NOT NULL, + last_error TEXT NOT NULL, + metadata JSON NOT NULL, + PRIMARY KEY (request_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/submitqueue/extension/storage/mysql/schema/request_summary_by_queue.sql b/submitqueue/extension/storage/mysql/schema/request_summary_by_queue.sql new file mode 100644 index 00000000..813e533f --- /dev/null +++ b/submitqueue/extension/storage/mysql/schema/request_summary_by_queue.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS request_summary_by_queue ( + queue VARCHAR(255) NOT NULL, + received_at_ms BIGINT NOT NULL, + request_id VARCHAR(255) NOT NULL, + change_uris JSON NOT NULL, + status VARCHAR(64) NOT NULL, + projection_version INT NOT NULL, + last_error TEXT NOT NULL, + metadata JSON NOT NULL, + PRIMARY KEY (queue, received_at_ms, request_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;