8000 feat(storage,deploy): complete PostgreSQL backend and add AWS EKS island-mode deployment by Szlaski · Pull Request #1455 · google/xmc · GitHub
[go: up one dir, main page]

Skip to content

feat(storage,deploy): complete PostgreSQL backend and add AWS EKS island-mode deployment - #1455

Draft
Szlaski wants to merge 9 commits into
mainfrom
szlaski/postgres-island
Draft

Szlaski wants to merge 9 commits into
mainfrom
szlaski/postgres-island

Conversation

@Szlaski
@Szlaski Szlaski commented Oct 28, 2026
Copy link
Copy Markdown
Collaborator

Summary

Completes the end-to-end PostgreSQL backend (DB_BACKEND=postgres) and adds an in-cluster PostgreSQL 16 addon and AWS EKS "island-mode" deployment procedure (zero GCP dependencies).

Changes Included

  • internal/storage/postgres/{xid,statushistory,pausedtask,artifact,experiment,dashboard,workunit}: Implements the remaining 7 PostgreSQL repositories and postgrestest integration suites on top of origin/main (which already includes pausestate, operation, clilogincode, statusmessage, dbconfig, and factory).
  • cmd/experiment_state_server/server/xidfilter.go: Selects xid= instead of bit_reversed_xid= when DB_BACKEND=postgres in ListExperiments and ListArtifacts ACL resolution (bit_reversed_xid is a Spanner-only view column).
  • deploy/addons/postgres/:
    • manifests/postgres.yaml & setup_postgres_addon.sh: In-cluster PostgreSQL 16 StatefulSet (ess and xid databases) + migration Job runner using postgres-migrator.
    • deploy_aws_eks.sh & manifests/gateway-nlb.yaml: End-to-end deployment and optional IP-allowlisted AWS NLB gateway for running XMC on AWS EKS (DB_BACKEND=postgres) without any GCP dependencies.

Verification

  • All //internal/storage/postgres/... unit and integration tests and //cmd/experiment_state_server/server:server_test pass.
  • Verified end-to-end on AWS EKS (eu-north-1, Kubernetes 1.34) with in-cluster PostgreSQL 16, Redis, Dex OIDC, JobSet 0.12.0, and Kueue 0.18.2: experiment creation, work unit scheduling on cpu-queue, state transitions (INITIALIZING -> STARTING -> RUNNING -> COMPLETED), and UI/REST/gRPC access via the NLB gateway.

TAG=agy
CONV=9cd11755-1c0f-4576-8304-43044a6d7f9a

@github-actions
github-actions Bot requested a review from janzak-dev October 28, 2026 00:25
@gemini-code-assist gemini-code-assist Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for a PostgreSQL backend for the experiment state server, including necessary schema migrations, infrastructure deployment scripts for AWS EKS, and the required repository implementation. The review identified several actionable improvements: a critical permission issue for PostgreSQL on EKS due to volume ownership, a performance optimization for environment variable parsing in the XID filter, a missing cleanup trap for temporary directories in the deployment script, and a more portable method for decoding secrets in kubectl.

Comment on lines +63 to +65
spec:
containers:
- name: postgres
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

On AWS EKS, EBS volumes mounted via the EBS CSI driver are owned by root:root by default. Since the official PostgreSQL Alpine image runs as a non-root user (postgres, UID/GID 999), the container will fail to initialize the database with a "permission denied" error when writing to /var/lib/postgresql/data/pgdata. To prevent this, configure securityContext.fsGroup to 999 on the Pod spec so Kubernetes automatically changes the ownership of the mounted volume.

    spec:
      securityContext:
        fsGroup: 999
      containers:
      - name: postgres

Comment on lines +33 to +45
func xidLookupFilter(xids []int64) string {
backend, err := dbconfig.ParseBackend(os.Getenv("DB_BACKEND"))
usePlainXid := err == nil && backend == dbconfig.BackendPostgres
terms := make([]string, 0, len(xids))
for _, xid := range xids {
if usePlainXid {
terms = append(terms, fmt.Sprintf("xid=%d", xid))
continue
}
terms = append(terms, fmt.Sprintf("bit_reversed_xid=%d", data.ReverseBits(xid)))
}
return strings.Join(terms, " OR ")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Reading the environment variable DB_BACKEND on every call to xidLookupFilter can introduce a performance bottleneck under high concurrency due to internal locking in os.Getenv. Since the database backend configuration is static for the lifetime of the process, this value should be parsed once and cached.

Suggested change
func xidLookupFilter(xids []int64) string {
backend, err := dbconfig.ParseBackend(os.Getenv("DB_BACKEND"))
usePlainXid := err == nil && backend == dbconfig.BackendPostgres
terms := make([]string, 0, len(xids))
for _, xid := range xids {
if usePlainXid {
terms = append(terms, fmt.Sprintf("xid=%d", xid))
continue
}
terms = append(terms, fmt.Sprintf("bit_reversed_xid=%d", data.ReverseBits(xid)))
}
return strings.Join(terms, " OR ")
}
var usePlainXid = func() bool {
backend, err := dbconfig.ParseBackend(os.Getenv("DB_BACKEND"))
return err == nil && backend == dbconfig.BackendPostgres
}()
func xidLookupFilter(xids []int64) string {
terms := make([]string, 0, len(xids))
for _, xid := range xids {
if usePlainXid {
terms = append(terms, fmt.Sprintf("xid=%d", xid))
continue
}
terms = append(terms, fmt.Sprintf("bit_reversed_xid=%d", data.ReverseBits(xid)))
}
return strings.Join(terms, " OR ")
}

JOBSET_CHART_VERSION="${JOBSET_CHART_VERSION:-0.12.0}"
KUEUE_CHART_VERSION="${KUEUE_CHART_VERSION:-0.18.2}"
NAMESPACE="${NAMESPACE:-xmc}"
WORK_DIR="${WORK_DIR:-$(mktemp -d)}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The script creates a temporary directory using mktemp -d but does not clean it up on exit. To prevent cluttering the host system with orphaned temporary directories, add a trap to clean up WORK_DIR on script exit.

Suggested change
WORK_DIR="${WORK_DIR:-$(mktemp -d)}"
WORK_DIR="${WORK_DIR:-$(mktemp -d)}"
trap 'rm -rf "${WORK_DIR}"' EXIT

DEX_ISSUER="http://localhost:5556/dex"
fi
local pg_password
pg_password="$(kubectl get secret xmc-postgres-credentials -n "${NAMESPACE}" -o jsonpath='{.data.password}' | base64 -d)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using base64 -d is not portable across all operating systems (e.g., macOS uses base64 -D or base64 --decode). A more robust and native way to decode the secret value directly within kubectl is to use Go templates with the base64decode function. This avoids any external piping and ensures cross-platform compatibility.

Suggested change
pg_password="$(kubectl get secret xmc-postgres-credentials -n "${NAMESPACE}" -o jsonpath='{.data.password}' | base64 -d)"
pg_password="$(kubectl get secret xmc-postgres-credentials -n "${NAMESPACE}" -o go-template='{{.data.password | base64decode}}')"

@AntonTyb
AntonTyb marked this pull request as draft October 28, 2026 09:18
@AntonTyb
AntonTyb added this pull request to stack #1470 October 28, 2026 14:52

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0