Skip to content

Commit 531e508

Browse files
committed
refactor: optimize Spanner batching and standardize OpenTelemetry deployment
This commit optimizes Spanner write performance and standardizes OpenTelemetry (OTel) deployment across the backend API, background workers, and ingestion jobs. 1. Spanner Batching: * Refactored UpsertWPTRunFeatureMetrics in lib/gcpspanner/wpt_run_feature_metric.go to execute two batch queries at the transaction start instead of executing queries inside a loop, reducing database roundtrips. * Extracted mutation building to buildWPTRunFeatureMetricMutations to reduce cognitive complexity. 2. Centralized Terraform Telemetry Configuration: * Created root-level infra/telemetry.tf to define a single shared Secret Manager secret containing the OTel collector configuration. * Swapped the custom in-tree OTel collector image for the official Google-managed image (otelcol-google:0.151.0). This is to match this new documentation: https://docs.cloud.google.com/stackdriver/docs/instrumentation/opentelemetry-collector-cloud-run * Defined a centralized otel_collector_config_mount_path local variable in infra/telemetry.tf set to "/etc/otelcol-google" and propagated it to all submodules, replacing hardcoded paths. 3. OTel Sidecar Deployment (Go & Terraform): * Added opentelemetry.MaybeSetup helper to lib/opentelemetry/setup.go to encapsulate environment checks and OTel SDK initialization. * Refactored backend/cmd/server/main.go and all 11 background workers and daily ingestion scraper entrypoints to call MaybeSetup and defer shutdown. * Deployed the OTel sidecar container and mounted the shared config secret across all 4 worker pools and the reusable job module. * Granted roles/cloudtrace.agent, roles/monitoring.metricWriter, and roles/logging.logWriter to the backend and worker service accounts. 4. GCP Error Reporting & Structured Logging: * Updated the custom slog handler in lib/opentelemetry/slog.go to capture and append runtime/debug.Stack() to ERROR logs. * Structured logs to enable automatic GCP Error Reporting aggregation and trace-log linking using the trace field. 5. Go Startup Logging & Refactoring: * Added verbose BOOT: log statements before each client initialization phase (Datastore, Spanner, Valkey, OTel) in backend/cmd/server/main.go to provide startup phase visibility. * Refactored the inline OpenTelemetry setup block in main.go to use the new opentelemetry.MaybeSetup helper, keeping the startup sequence synchronous. 6. Repository Cleanup: * Deleted the unused custom in-tree OTel collector Dockerfile (otel/Dockerfile). * Removed the /otel Docker package-ecosystem update entry from .github/dependabot.yml. BUG=526562255
1 parent a5319d7 commit 531e508

63 files changed

Lines changed: 2369 additions & 311 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/dependabot.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,6 @@ updates:
7878
- '>=23'
7979
- '<24'
8080

81-
- package-ecosystem: 'docker'
82-
directory: '/otel'
83-
schedule:
84-
interval: 'weekly'
85-
8681
- package-ecosystem: 'docker'
8782
directory: '/.dev/auth'
8883
schedule:

backend/cmd/server/main.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func main() {
5959
datastoreDB = &value
6060
}
6161
projectID := os.Getenv("PROJECT_ID")
62+
slog.InfoContext(context.TODO(), "BOOT: Creating Datastore client...")
6263
fs, err := gds.NewDatastoreClient(projectID, datastoreDB)
6364
if err != nil {
6465
slog.ErrorContext(context.TODO(), "failed to create datastore client", "error", err.Error())
@@ -69,6 +70,7 @@ func main() {
6970

7071
spannerDB := os.Getenv("SPANNER_DATABASE")
7172
spannerInstance := os.Getenv("SPANNER_INSTANCE")
73+
slog.InfoContext(ctx, "BOOT: Creating Spanner client...")
7274
spannerClient, err := gcpspanner.NewSpannerClient(projectID, spannerInstance, spannerDB)
7375
if err != nil {
7476
slog.ErrorContext(ctx, "failed to create spanner client", "error", err.Error())
@@ -106,6 +108,7 @@ func main() {
106108
slog.InfoContext(ctx, "cache settings", "duration", cacheTTL, "prefix", cacheKeyPrefix,
107109
"aggregatedFeaturesStatsTTL", aggregatedFeaturesStatsTTL)
108110

111+
slog.InfoContext(ctx, "BOOT: Creating Valkey cache...")
109112
cache, err := valkeycache.NewValkeyDataCache[string, []byte](
110113
cacheKeyPrefix,
111114
valkeyHost,
@@ -165,25 +168,22 @@ func main() {
165168
}),
166169
}
167170

168-
if os.Getenv("OTEL_SERVICE_NAME") != "" {
169-
slog.InfoContext(ctx, "opentelemetry settings detected.")
170-
otelProjectID := os.Getenv("OTEL_GCP_PROJECT_ID")
171-
if otelProjectID == "" {
172-
slog.ErrorContext(ctx, "missing project id for opentelemetry")
173-
os.Exit(1)
174-
}
175-
shutdown, err := opentelemetry.SetupOpenTelemetry(ctx, otelProjectID)
176-
if err != nil {
177-
slog.ErrorContext(ctx, "failed to setup opentelemetry", "error", err.Error())
178-
os.Exit(1)
179-
}
171+
slog.InfoContext(ctx, "BOOT: Running OTel setup...")
172+
shutdown, err := opentelemetry.MaybeSetup(ctx)
173+
if err != nil {
174+
slog.ErrorContext(ctx, "failed to setup opentelemetry", "error", err.Error())
175+
os.Exit(1)
176+
}
177+
if shutdown != nil {
180178
defer func() {
181-
err := shutdown(ctx)
182-
if err != nil {
183-
slog.ErrorContext(ctx, "unable to shutdown opentelemetry")
179+
if err := shutdown(ctx); err != nil {
180+
slog.ErrorContext(ctx, "unable to shutdown opentelemetry", "error", err)
184181
}
185182
}()
186-
// Prepend the opentelemtry middleware
183+
}
184+
185+
if os.Getenv("OTEL_SERVICE_NAME") != "" {
186+
// Prepend the opentelemetry middleware
187187
preRequestMiddlewares = slices.Insert(preRequestMiddlewares, 0, opentelemetry.NewOpenTelemetryChiMiddleware())
188188
}
189189

infra/backend/service.tf

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,11 @@ resource "docker_registry_image" "backend_remote_image" {
4646
data "google_project" "host_project" {
4747
}
4848

49-
module "otel_sidecar" {
50-
source = "../modules/otel"
51-
providers = {
52-
google.project = google.public_project
53-
}
54-
docker_repository_details = var.docker_repository_details
55-
service_account = google_service_account.backend.member
49+
resource "google_secret_manager_secret_iam_member" "backend_otel_config_secret_access" {
50+
provider = google.internal_project
51+
secret_id = var.otel_config_secret_id
52+
role = "roles/secretmanager.secretAccessor"
53+
member = "serviceAccount:${google_service_account.backend.email}"
5654
}
5755

5856
resource "google_cloud_run_v2_service" "service" {
@@ -125,7 +123,7 @@ resource "google_cloud_run_v2_service" "service" {
125123
}
126124
env {
127125
name = "OTEL_EXPORTER_OTLP_ENDPOINT"
128-
value = "http://localhost:4318"
126+
value = var.otel_collector_endpoint
129127
}
130128
env {
131129
name = "OTEL_SERVICE_NAME"
@@ -150,7 +148,16 @@ resource "google_cloud_run_v2_service" "service" {
150148
}
151149
containers {
152150
name = "otel"
153-
image = module.otel_sidecar.otel_image
151+
image = var.otel_collector_image
152+
args = ["--config=${var.otel_collector_config_mount_path}/config.yaml"]
153+
env {
154+
name = "OTEL_COLLECTOR_REGION"
155+
value = each.key
156+
}
157+
volume_mounts {
158+
name = "otel-config"
159+
mount_path = var.otel_collector_config_mount_path
160+
}
154161
liveness_probe {
155162
http_get {
156163
port = 4319
@@ -162,7 +169,7 @@ resource "google_cloud_run_v2_service" "service" {
162169
timeout_seconds = 10
163170
}
164171
startup_probe {
165-
initial_delay_seconds = 0
172+
initial_delay_seconds = 5
166173
timeout_seconds = 1
167174
period_seconds = 3
168175
failure_threshold = 10
@@ -178,6 +185,16 @@ resource "google_cloud_run_v2_service" "service" {
178185
}
179186
egress = "PRIVATE_RANGES_ONLY"
180187
}
188+
volumes {
189+
name = "otel-config"
190+
secret {
191+
secret = var.otel_config_secret_id
192+
items {
193+
version = "latest"
194+
path = "config.yaml"
195+
}
196+
}
197+
}
181198
service_account = google_service_account.backend.email
182199
}
183200

@@ -188,6 +205,7 @@ resource "google_cloud_run_v2_service" "service" {
188205

189206
depends_on = [
190207
google_project_iam_member.gcp_datastore_user,
208+
google_secret_manager_secret_iam_member.backend_otel_config_secret_access,
191209
]
192210

193211
deletion_protection = var.deletion_protection
@@ -327,3 +345,29 @@ resource "google_compute_managed_ssl_certificate" "lb_default" {
327345
domains = var.domains
328346
}
329347
}
348+
349+
# --- Telemetry IAM Permissions for Backend Service Account ---
350+
351+
# Grant Cloud Trace Agent role to allow exporting traces directly or via OTel sidecar.
352+
resource "google_project_iam_member" "backend_trace_agent" {
353+
provider = google.public_project
354+
project = var.projects.public
355+
role = "roles/cloudtrace.agent"
356+
member = "serviceAccount:${google_service_account.backend.email}"
357+
}
358+
359+
# Grant Monitoring Metric Writer role to allow exporting metrics.
360+
resource "google_project_iam_member" "backend_metric_writer" {
361+
provider = google.public_project
362+
project = var.projects.public
363+
role = "roles/monitoring.metricWriter"
364+
member = "serviceAccount:${google_service_account.backend.email}"
365+
}
366+
367+
# Grant Logging Log Writer role to allow exporting structured logs.
368+
resource "google_project_iam_member" "backend_log_writer" {
369+
provider = google.public_project
370+
project = var.projects.public
371+
role = "roles/logging.logWriter"
372+
member = "serviceAccount:${google_service_account.backend.email}"
373+
}

infra/backend/variables.tf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,23 @@ variable "backend_api_url" {
110110

111111
variable "pubsub_project_id" { type = string }
112112
variable "ingestion_topic_id" { type = string }
113+
114+
variable "otel_config_secret_id" {
115+
type = string
116+
description = "The Secret Manager secret ID containing the OTel collector configuration"
117+
}
118+
119+
variable "otel_collector_image" {
120+
type = string
121+
description = "The container image to use for the OTel collector sidecar"
122+
}
123+
124+
variable "otel_collector_config_mount_path" {
125+
type = string
126+
description = "The volume mount path for the OTel collector configuration"
127+
}
128+
129+
variable "otel_collector_endpoint" {
130+
type = string
131+
description = "The endpoint for the application to export OTLP metrics/traces to the local collector"
132+
}

infra/ingestion/variables.tf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,28 @@ variable "notification_channel_ids" {
9494
description = "A list of notification channel ids to send alerts to."
9595
type = list(string)
9696
}
97+
98+
variable "otel_config_secret_id" {
99+
type = string
100+
description = "The Secret Manager secret ID containing the OTel collector configuration"
101+
}
102+
103+
variable "otel_project_id" {
104+
type = string
105+
description = "The GCP project ID where telemetry traces/metrics will be exported"
106+
}
107+
108+
variable "otel_collector_image" {
109+
type = string
110+
description = "The container image to use for the OTel collector sidecar"
111+
}
112+
113+
variable "otel_collector_config_mount_path" {
114+
type = string
115+
description = "The volume mount path for the OTel collector configuration"
116+
}
117+
118+
variable "otel_collector_endpoint" {
119+
type = string
120+
description = "The endpoint for the application to export OTLP metrics/traces to the local collector"
121+
}

infra/ingestion/workflows.tf

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ module "web_features_workflow" {
5959
value = "262980h" # 30 years in hours (365.25*30*24)
6060
}
6161
]
62+
otel_config_secret_id = var.otel_config_secret_id
63+
otel_project_id = var.otel_project_id
64+
otel_collector_image = var.otel_collector_image
65+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
66+
otel_collector_endpoint = var.otel_collector_endpoint
6267
}
6368

6469
module "wpt_workflow" {
@@ -105,9 +110,14 @@ module "wpt_workflow" {
105110
},
106111
{
107112
name = "DATA_WINDOW_DURATION"
108-
value = "17520h" # 2 years
113+
value = "720h" # 30 days
109114
}
110115
]
116+
otel_config_secret_id = var.otel_config_secret_id
117+
otel_project_id = var.otel_project_id
118+
otel_collector_image = var.otel_collector_image
119+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
120+
otel_collector_endpoint = var.otel_collector_endpoint
111121
}
112122

113123
module "bcd_workflow" {
@@ -144,6 +154,11 @@ module "bcd_workflow" {
144154
value = var.spanner_datails.instance
145155
}
146156
]
157+
otel_config_secret_id = var.otel_config_secret_id
158+
otel_project_id = var.otel_project_id
159+
otel_collector_image = var.otel_collector_image
160+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
161+
otel_collector_endpoint = var.otel_collector_endpoint
147162
}
148163

149164
module "chromium_enum_workflow" {
@@ -180,6 +195,11 @@ module "chromium_enum_workflow" {
180195
value = var.spanner_datails.instance
181196
}
182197
]
198+
otel_config_secret_id = var.otel_config_secret_id
199+
otel_project_id = var.otel_project_id
200+
otel_collector_image = var.otel_collector_image
201+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
202+
otel_collector_endpoint = var.otel_collector_endpoint
183203
}
184204

185205
module "uma_export_workflow" {
@@ -216,6 +236,11 @@ module "uma_export_workflow" {
216236
value = var.spanner_datails.instance
217237
}
218238
]
239+
otel_config_secret_id = var.otel_config_secret_id
240+
otel_project_id = var.otel_project_id
241+
otel_collector_image = var.otel_collector_image
242+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
243+
otel_collector_endpoint = var.otel_collector_endpoint
219244
}
220245

221246
module "developer_signals_workflow" {
@@ -252,6 +277,11 @@ module "developer_signals_workflow" {
252277
value = var.spanner_datails.instance
253278
}
254279
]
280+
otel_config_secret_id = var.otel_config_secret_id
281+
otel_project_id = var.otel_project_id
282+
otel_collector_image = var.otel_collector_image
283+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
284+
otel_collector_endpoint = var.otel_collector_endpoint
255285
}
256286

257287
module "web_features_mapping_workflow" {
@@ -288,4 +318,9 @@ module "web_features_mapping_workflow" {
288318
value = var.spanner_datails.instance
289319
}
290320
]
321+
otel_config_secret_id = var.otel_config_secret_id
322+
otel_project_id = var.otel_project_id
323+
otel_collector_image = var.otel_collector_image
324+
otel_collector_config_mount_path = var.otel_collector_config_mount_path
325+
otel_collector_endpoint = var.otel_collector_endpoint
291326
}

infra/main.tf

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ module "ingestion" {
9696
developer_signals_region_schedules = var.developer_signals_region_schedules
9797
web_features_mapping_region_schedules = var.web_features_mapping_region_schedules
9898
notification_channel_ids = var.notification_channel_ids
99+
otel_config_secret_id = google_secret_manager_secret.otel_config.id
100+
otel_project_id = var.projects.public
101+
otel_collector_image = local.otel_collector_image
102+
otel_collector_config_mount_path = local.otel_collector_config_mount_path
103+
otel_collector_endpoint = local.otel_collector_endpoint
99104
}
100105

101106
module "backend" {
@@ -124,8 +129,12 @@ module "backend" {
124129
firebase_settings = {
125130
tenant_id = module.auth.tenant_id
126131
}
127-
pubsub_project_id = var.projects.internal
128-
ingestion_topic_id = module.pubsub.ingestion_topic_id
132+
pubsub_project_id = var.projects.internal
133+
ingestion_topic_id = module.pubsub.ingestion_topic_id
134+
otel_config_secret_id = google_secret_manager_secret.otel_config.id
135+
otel_collector_image = local.otel_collector_image
136+
otel_collector_config_mount_path = local.otel_collector_config_mount_path
137+
otel_collector_endpoint = local.otel_collector_endpoint
129138
}
130139

131140
module "frontend" {
@@ -205,6 +214,11 @@ module "workers" {
205214

206215
chime_details = var.chime_details
207216

208-
email_service_account_email = var.email_service_account_email
209-
deletion_protection = var.deletion_protection
217+
email_service_account_email = var.email_service_account_email
218+
deletion_protection = var.deletion_protection
219+
otel_config_secret_id = google_secret_manager_secret.otel_config.id
220+
otel_project_id = var.projects.public
221+
otel_collector_image = local.otel_collector_image
222+
otel_collector_config_mount_path = local.otel_collector_config_mount_path
223+
otel_collector_endpoint = local.otel_collector_endpoint
210224
}

0 commit comments

Comments
 (0)