Skip to content

Postgres_GKE PR2-sysbench - Standalone PostgreSQL Sysbench benchmark on GKE#6563

Open
manojcns wants to merge 7 commits into
GoogleCloudPlatform:masterfrom
manojcns:postgres-gke-pr2-sysbench
Open

Postgres_GKE PR2-sysbench - Standalone PostgreSQL Sysbench benchmark on GKE#6563
manojcns wants to merge 7 commits into
GoogleCloudPlatform:masterfrom
manojcns:postgres-gke-pr2-sysbench

Conversation

@manojcns
Copy link
Copy Markdown
Contributor

@manojcns manojcns commented Mar 29, 2026

Summary

This PR introduces standalone PostgreSQL Sysbench benchmark for GKE, enabling repeatable OLTP performance testing using Sysbench workloads against a single-instance PostgreSQL deployment on Kubernetes.

This is the second in a series of three PRs for GKE PostgreSQL benchmarking:

  • PR1 (merged): Core GKE infrastructure hardening (HugePages flag gating,
    LoadBalancer endpoint refactor, additive Kubernetes cluster methods)
  • PR2 (this PR): Standalone PostgreSQL Sysbench benchmark on GKE
  • PR3 (upcoming): High-Availability PostgreSQL benchmark using CloudNativePG operator

All new files. Zero modifications to existing benchmark modules.


Files Changed

1. [perfkitbenchmarker/linux_benchmarks/postgres_sysbench_gke_benchmark.py) (New)

  • Deploys PostgreSQL as a Kubernetes StatefulSet using a Jinja2-rendered manifest
  • Client pod runs Sysbench from within the cluster (no external VM needed)
  • Configurable storage classes: hyperdisk-balanced, pd-ssd
  • Supports optimization profiles ('baseline', 'infra-tuned', 'kernel-tuned', 'postgres-tuned', 'infra+postgres', 'infra+postgres+hugepages') covering shared_buffers, HugePages, kernel params, and WAL tuning
  • HugePages provisioned via --system-config-from-file on the GKE nodepool — existing
    benchmarks that do not set gke_node_system_config are completely unaffected
  • All flags namespaced under postgres_gke_* — no collisions with existing flags

2. data/container/postgres_sysbench/ (New — 2 manifest templates)

  • postgres_all.yaml.j2: StatefulSet + Service + PVC for the Postgres server pod
  • client_pod.yaml.j2: Sysbench client pod spec

3. docs/ (New — 2 documentation files)

  • GKE_PostgreSQL_Quickstart_generic.MD: Quickstart guide with example PKB commands
  • Technical_Architecture_PostgreSQL_PKB.md: Architecture overview

Backward Compatibility

  • No existing files modified. All changes are purely additive new files.
  • Flag namespace postgres_gke_* does not conflict with any existing PKB flags.
  • HugePages logic is gated: only activates when an optimization profile with
    hugepages key is selected. Benchmarks not using this flag are unaffected.
  • sysbench_benchmark.py (VM-based MySQL/Postgres) is not touched — the new
    benchmark is GKE-only and independently implemented.

Comment thread docs/GKE_PostgreSQL_Quickstart_generic.MD Outdated
Comment thread docs/GKE_PostgreSQL_Quickstart_generic.MD Outdated
```


## HA (CloudNativePG) Tests
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Again, please remove all HA references.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Acknowledged, will update

```

### Architecture & Logic
1. **Pod as VM Abstraction**: PKB's Kubernetes provider treats Kubernetes pods as Virtual Machines. When the benchmark runs, PKB provisions:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You mention "Pod as VM", but it looks like the code supports both that as well as k8s-native.

Is there a strong reason to support both? I'd prefer just the k8s-native way, since that's the way users would run a real workload.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Acknowledged, will update code

Comment on lines +48 to +55
* **v1 (Infrastructure)**: Uses Container-Optimized OS (COS) for nodes and Ubuntu 24.04 for the client.
* **v2 (Startup)**: Uses Ubuntu node image and removes the init container for faster startup (at the cost of less robust permission handling).
* **v3 (Kernel)**: Applies sysctl tuning (`vm.swappiness=1`, `vm.dirty_ratio=10`, etc.) to the node.
* **v4 (HugePages)**: Enables HugePages (2MB) on the node and configures PostgreSQL (`huge_pages=on`) to use them. This reduces TLB misses and improves memory management efficiency.
* **v6 (Postgres Tuning)**: Applies aggressive PostgreSQL configuration tuning (e.g., `shared_buffers=35GB`, `effective_io_concurrency=200`, `max_worker_processes=32`).
* **v1+v6+v4 (All-in-One)**: Combines Infrastructure, Postgres Tuning, and HugePages for maximum performance.
* **v1+v6+v4+hostnetwork (HostNetwork Optimized)**: Extends the "All-in-One" profile by enabling Host Networking (`hostNetwork: true`) for the PostgreSQL pods. This bypasses the Kubernetes CNI/Overlay network stack, allowing the database to use the node's native network interface for maximum throughput and reduced latency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can the profiles be renamed using words instead of arbitrary numbers? It'd be easier to understand something like infra+hugepages+pg (or similar).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey Hank, This can be done, would below work? Thanks

v1 -> infra-tuned (COS nodes + Ubuntu clients)
v2 -> fast-startup (No init container)
v3 -> kernel-tuned (Sysctl params)
v4 -> hugepages (HugePages on)
v6 -> postgres-tuned (Aggressive PG memory/worker tuning)
v1+v6 -> infra+postgres
v1+v6+v4 -> infra+postgres+hugepages
v1+v6+v4+hostnetwork -> infra+postgres+hugepages+hostnetwork

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great, thanks!

Comment on lines +20 to +25
requests:
cpu: "4"
memory: "10Gi"
limits:
cpu: "8"
memory: "20Gi"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand the original intent of this benchmark was very narrow in scope, but I think it'd be worthwhile to provide configurability so users can run the benchmark generically on other machine types, rather than being limited to the original ones tested.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Acknowledged, let me review

- name: PGUSER
value: benchmark
- name: PGPASSWORD
value: {{ password }}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can a k8s secret be used instead? The client is exposing the password here to anyone that can kubectl describe pod.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Acknowledged, let me review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this used anywhere? Can it be removed? I see the node-config.yaml next, which is the preferred way of handling this setup.


# Wait a bit for resources to be created
logging.info('Waiting 30 seconds for resources to be created...')
time.sleep(30)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Better to poll for availability with timeout than to sleep arbitrarily.


# Give it more time to stabilize (important for large shared_buffers)
logging.info('Waiting 60 seconds for PostgreSQL to fully stabilize...')
time.sleep(60)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a way to poll here instead of waiting?

@manojcns manojcns force-pushed the postgres-gke-pr2-sysbench branch from 908c8b2 to a5ba694 Compare April 9, 2026 22:23
@manojcns
Copy link
Copy Markdown
Contributor Author

@hankfreund - this PR is ready for re-review; please take a look when time permits

Copy link
Copy Markdown
Member

@hankfreund hankfreund left a comment

Choose a reason for hiding this comment

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

Thanks Manoj! I'll ping the team to take a look.

@manojcns manojcns marked this pull request as ready for review April 22, 2026 18:27

# PostgreSQL configuration flags
flags.DEFINE_string(
'postgres_gke_shared_buffers',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could you just have 2 sets of defaults for baseline and optimized? This will limit the choices for the pkb users. See SHARED_BUFFERS_CONF in perfkitbenchmarker/linux_packages/postgresql.py.

'PostgreSQL shared_buffers size (baseline: 15GB, optimized: 35GB)',
)
flags.DEFINE_integer(
'postgres_gke_max_connections', 1000, 'PostgreSQL max_connections'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just hard code into default if you do not plan to use other numbers as variables.

)
flags.DEFINE_string(
'postgres_gke_effective_cache_size',
'30GB',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

see comment above

"""

# Machine type to disk type mapping
MACHINE_DISK_MAPPING = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pls do not add this mapping here.
This file should be cloud agnostic.
We usually manage this complexity at scheduling time.


# Optimization profiles
# NOTE: These profile memory and CPU values are tuned for c4-standard-16 and n2-standard-16 only.
OPTIMIZATION_PROFILES = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since you have this, why have the flags above?

]['GCP']['machine_type']

# Calculate dynamic HugePages needed mapped to the architecture
machine_family = server_machine.split('-')[0]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we dont put gcp specific code in benchmark

if machine_family in ['c4a', 'n4', 'n4a', 'n4d']:
node_mem_gb = node_cpus * 4.0
elif machine_family == 'c4d':
node_mem_gb = node_cpus * 3.875
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not familiar with GKE, but can you pull the accessible memory using gke version of 'free -h'?
This is not maintainable.

hugepage_mb = int(pod_mem_gb * 0.45) * 1024
hugepage_size2m = int(hugepage_mb / 2)

import os
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

imports at top of file.

if not machine_type:
machine_type = 'c4-standard-16'

parts = machine_type.split('-')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

prefer you query from within the pod/node.

].vm_spec['GCP']['machine_type']
except (KeyError, AttributeError):
# Default to c4-standard-16 if we can't find it
machine_type = 'c4-standard-16'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fail if you cannot find it

vm_util.IssueCommand(cmd)

# 2. Delete the Client Pod
cmd = [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does the container cluster resource not clean this up automatically?

]
vm_util.IssueCommand(cmd)

# 3. Explicitly delete all PVCs to ensure disks are released
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does deleting the cluster not delete the pvcs?

- Enforce profile-based tuning by removing granular config flags.
- Deduplicate base PostgreSQL configurations across profiles.
- Standardize storage using native PKB data_disk flags.
- Replace hardcoded GCP machine mappings with dynamic K8s API queries.
- Rely on PKB's native cluster lifecycle manager for teardown.
- Renamed 'postgres_sysbench_gke' to 'kubernetes_postgres_sysbench' to adhere to the official PKB naming convention for K8s benchmarks.
- Updated module filename to kubernetes_postgres_sysbench_benchmark.py.
- Updated BENCHMARK_NAME and BENCHMARK_CONFIG root keys internally.
- Refactored module docstrings and markdown documentation to accurately reflect cloud-agnostic Kubernetes capability rather than being exclusively GKE-focused.
@manojcns manojcns force-pushed the postgres-gke-pr2-sysbench branch from 9d3ed13 to b8ac5f5 Compare May 6, 2026 19:09
@manojcns manojcns requested a review from jellyfishcake May 7, 2026 00:54
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.

4 participants