From c9741221c1a776dbfcbab4102adf4143b292f1a3 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 4 Mar 2026 15:19:42 +0100 Subject: [PATCH 1/9] WIP: Updating the backups Signed-off-by: Nikolai Rodionov --- charts/db-operator/Chart.yaml | 4 +- .../crd/kinda.rocks_databases.yaml | 4 + .../templates/controller/config.yaml | 34 +------ charts/db-operator/values.yaml | 91 +++---------------- ci/helmfile.yaml.gotmpl | 52 ++++++++++- ci/kustomize/databases/kustomization.yaml | 1 + ci/kustomize/databases/mysql.yaml | 5 +- ci/kustomize/databases/postgres.yaml | 18 +--- ci/kustomize/databases/secret.yaml | 11 +++ 9 files changed, 92 insertions(+), 128 deletions(-) create mode 100644 ci/kustomize/databases/secret.yaml diff --git a/charts/db-operator/Chart.yaml b/charts/db-operator/Chart.yaml index 74946f8..9ce86cc 100644 --- a/charts/db-operator/Chart.yaml +++ b/charts/db-operator/Chart.yaml @@ -2,12 +2,12 @@ apiVersion: v2 type: application name: db-operator -version: 3.10.0 +version: 3.11.0 description: This operator lets you manage databases in a Kubernetes native way, even if they are not deployed to Kubernetes # All supported k8s versions are in the test: # https://github.com/db-operator/charts/blob/main/.github/workflows/integration-test.yaml kubeVersion: ">= 1.32-prerelease" -appVersion: "2.33.0" +appVersion: "161b6438857d77df8f91fb4dd8c04cdc3654b420" home: https://github.com/db-operator maintainers: - name: Nikolai Rodionov diff --git a/charts/db-operator/crd/kinda.rocks_databases.yaml b/charts/db-operator/crd/kinda.rocks_databases.yaml index 0927361..79aa271 100644 --- a/charts/db-operator/crd/kinda.rocks_databases.yaml +++ b/charts/db-operator/crd/kinda.rocks_databases.yaml @@ -372,10 +372,14 @@ spec: description: DatabaseBackup defines the desired state of backup and schedule properties: + bucket: + type: string cron: type: string enable: type: boolean + envFromSecret: + type: string required: - cron - enable diff --git a/charts/db-operator/templates/controller/config.yaml b/charts/db-operator/templates/controller/config.yaml index a9fa842..b81f541 100644 --- a/charts/db-operator/templates/controller/config.yaml +++ b/charts/db-operator/templates/controller/config.yaml @@ -6,36 +6,4 @@ metadata: {{- include "db-operator.controller.labels" . | nindent 4 }} data: config.yaml: | - instance: - google: - clientSecretName: "cloudsql-readonly-serviceaccount" - proxy: - nodeSelector: - {{- toYaml .Values.controller.config.instance.google.proxy.nodeSelector | nindent 12 }} - image: {{ .Values.controller.config.instance.google.proxy.image }} - metricsPort: {{ .Values.controller.config.instance.google.proxy.metricsPort }} - generic: {} - percona: - proxy: - image: {{ .Values.controller.config.instance.percona.proxy.image }} - backup: - activeDeadlineSeconds: {{ .Values.controller.config.backup.activeDeadlineSeconds }} - nodeSelector: - {{- toYaml .Values.controller.config.backup.nodeSelector | nindent 8 }} - postgres: - image: {{ .Values.controller.config.backup.postgres.image }} - mysql: - image: {{ .Values.controller.config.backup.mysql.image }} - resources: - {{- toYaml .Values.controller.config.backup.resources | nindent 8 }} - monitoring: - {{- if .Values.controller.config.monitoring.promPushGateway }} - promPushGateway: {{ .Values.controller.config.monitoring.promPushGateway }} - {{- end }} - nodeSelector: - {{- toYaml .Values.controller.config.monitoring.nodeSelector | nindent 8 }} - postgres: - image: {{ .Values.controller.config.monitoring.postgres.image }} - queries: |- - {{- toYaml .Values.controller.config.monitoring.postgres.queries | nindent 10 }} - mysql: {} + {{- .Values.controller.config | toYaml | nindent 4 }} diff --git a/charts/db-operator/values.yaml b/charts/db-operator/values.yaml index 56af962..99cdb99 100644 --- a/charts/db-operator/values.yaml +++ b/charts/db-operator/values.yaml @@ -30,72 +30,27 @@ controller: create: true rbac: create: true - args: - reconcileInterval: "60" - # watchNamespace value is comma-separated list of namespace names. It's necessary to set "" to watch cluster wide. - watchNamespace: "" - checkForChanges: false - serviceMonitor: - enabled: false config: - instance: - google: - proxy: - nodeSelector: {} - image: ghcr.io/db-operator/db-auth-gateway:v0.1.10 - metricsPort: 9090 - generic: {} - percona: - proxy: - image: severalnines/proxysql:2.0 - metricsPort: 9090 backup: - activeDeadlineSeconds: 600 # 10m + activeDeadlineSeconds: 600 # 10m nodeSelector: {} postgres: - image: "kloeckneri/pgdump-gcs:latest" + image: "ghcr.io/db-operator/pgdump-rclone:0.1.0" mysql: - image: "kloeckneri/mydump-gcs:latest" + image: "ghcr.io/db-operator/mydump-rclone:0.1.0" resources: requests: memory: 64Mi cpu: 0.2 monitoring: promPushGateway: "" - nodeSelector: {} - postgres: - image: wrouesnel/postgres_exporter:latest - queries: - pg_stat_statements: - query: "SELECT userid, pgss.dbid, pgdb.datname, queryid, query, calls, total_time, mean_time, rows FROM pg_stat_statements pgss LEFT JOIN (select oid as dbid, datname from pg_database) as pgdb on pgdb.dbid = pgss.dbid WHERE not queryid isnull ORDER BY mean_time desc limit 20" - metrics: - - userid: - usage: "LABEL" - description: "User ID" - - dbid: - usage: "LABEL" - description: "database ID" - - datname: - usage: "LABEL" - description: "database NAME" - - queryid: - usage: "LABEL" - description: "Query unique Hash Code" - - query: - usage: "LABEL" - description: "Query class" - - calls: - usage: "COUNTER" - description: "Number of times executed" - - total_time: - usage: "COUNTER" - description: "Total time spent in the statement, in milliseconds" - - mean_time: - usage: "GAUGE" - description: "Mean time spent in the statement, in milliseconds" - - rows: - usage: "COUNTER" - description: "Total number of rows retrieved or affected by the statement" + args: + reconcileInterval: "60" + # watchNamespace value is comma-separated list of namespace names. It's necessary to set "" to watch cluster wide. + watchNamespace: "" + checkForChanges: false + serviceMonitor: + enabled: false webhook: enabled: true @@ -159,25 +114,7 @@ annotations: {} podLabels: {} affinity: {} tolerations: [] -# interval: 10s -# scrapeTimeout: 10s -# jobLabel: db-operator -# selector: -# prometheus: my-prometheus -# ## metric relabel configs to apply to samples before ingestion. -# ## -# metricRelabelings: -# - sourceLabels: [__meta_kubernetes_service_label_cluster] -# targetLabel: cluster -# regex: (.*) -# replacement: ${1} -# action: replace -# ## relabel configs to apply to samples after ingestion. -# ## -# relabelings: -# - sourceLabels: [__meta_kubernetes_pod_node_name] -# separator: ; -# regex: ^(.*)$ -# targetLabel: nodename -# replacement: $1 -# action: replace +secrets: + gsql: {} +serviceMonitor: + enabled: false diff --git a/ci/helmfile.yaml.gotmpl b/ci/helmfile.yaml.gotmpl index 515b331..00b8e1d 100644 --- a/ci/helmfile.yaml.gotmpl +++ b/ci/helmfile.yaml.gotmpl @@ -15,6 +15,8 @@ repositories: - name: cloudpirates url: registry-1.docker.io/cloudpirates oci: true + - name: minio-standalone + url: https://charts.min.io/ releases: - name: cert-manager @@ -55,6 +57,53 @@ releases: serviceMonitorSelector: {} serviceMonitorSelectorNilUsesHelmValues: false + - name: minio + namespace: minio + chart: minio-standalone/minio + version: 5.4.0 + createNamespace: true + installed: {{ eq .Environment.Name "local-dev" }} + values: + - rootUser: 'root' + rootPassword: 123123!! + replicas: 1 + mode: standalone + resources: + requests: + memory: 512Mi + environment: + MINIO_SERVER_URL: "http://minio.minio.svc.cluster.local:9000" + tls: + enabled: false + persistence: + enabled: true + accessMode: ReadWriteOnce + size: 1Gi + service: + type: ClusterIP + clusterIP: ~ + port: '9000' + consoleService: + type: ClusterIP + clusterIP: ~ + port: '9001' + buckets: + - name: backups + policy: download + purge: false + policies: + - name: backups + statements: + - resources: + - 'arn:aws:s3:::backups' + - 'arn:aws:s3:::backups/*' + actions: + - 's3:*' + users: + - accessKey: backups + secretKey: qwertyu9 + policy: backups + - name: db-operator namespace: db-operator wait: true @@ -70,7 +119,8 @@ releases: values: - image: - repository: localhost/my-db-operator + registry: localhost + repository: my-db-operator tag: v1.0.0-dev pullPolicy: Never {{- end }} diff --git a/ci/kustomize/databases/kustomization.yaml b/ci/kustomize/databases/kustomization.yaml index 6de6cef..5c3e557 100644 --- a/ci/kustomize/databases/kustomization.yaml +++ b/ci/kustomize/databases/kustomization.yaml @@ -1,3 +1,4 @@ resources: - ./mysql.yaml - ./postgres.yaml + - ./secret.yaml diff --git a/ci/kustomize/databases/mysql.yaml b/ci/kustomize/databases/mysql.yaml index 7c04ed5..d85e060 100644 --- a/ci/kustomize/databases/mysql.yaml +++ b/ci/kustomize/databases/mysql.yaml @@ -4,7 +4,10 @@ metadata: name: mysql spec: backup: - enable: false + enable: true + cron: '*/5 * * * *' + bucket: backups + envFromSecret: s3-creds deletionProtected: false instance: mariadb secretName: mysql-creds diff --git a/ci/kustomize/databases/postgres.yaml b/ci/kustomize/databases/postgres.yaml index ec6dae2..388d403 100644 --- a/ci/kustomize/databases/postgres.yaml +++ b/ci/kustomize/databases/postgres.yaml @@ -4,22 +4,12 @@ metadata: name: postgres spec: backup: - enable: false + enable: true + cron: '*/5 * * * *' + bucket: backups + envFromSecret: s3-creds deletionProtected: false instance: postgres postgres: {} secretName: postgres-creds cleanup: true ---- -apiVersion: kinda.rocks/v1beta1 -kind: Database -metadata: - name: postgres-2 -spec: - backup: - enable: false - deletionProtected: false - instance: postgres - postgres: {} - secretName: postgres-2-creds - cleanup: true diff --git a/ci/kustomize/databases/secret.yaml b/ci/kustomize/databases/secret.yaml new file mode 100644 index 0000000..e82cdf3 --- /dev/null +++ b/ci/kustomize/databases/secret.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: s3-creds +stringData: + RCLONE_CONFIG_STORAGE_TYPE: s3 + RCLONE_CONFIG_STORAGE_PROVIDER: Minio + RCLONE_CONFIG_STORAGE_ACCESS_KEY_ID: backups + RCLONE_CONFIG_STORAGE_SECRET_ACCESS_KEY: qwertyu9 + RCLONE_CONFIG_STORAGE_ENDPOINT: http://minio.minio.svc.cluster.local:9000 From db81a6127986f62718ade344dc198c2705722f4a Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 4 Mar 2026 17:14:06 +0100 Subject: [PATCH 2/9] Add some debugging scripts Signed-off-by: Nikolai Rodionov --- ci/kustomize/databases/mysql.yaml | 2 +- ci/kustomize/databases/postgres.yaml | 2 +- ci/scripts/mysql | 7 ++ ci/scripts/mysql.sql | 142 +++++++++++++++++++++++++++ ci/scripts/pg_restore | 10 ++ ci/scripts/postgres.sql | 123 +++++++++++++++++++++++ ci/scripts/psql | 8 ++ 7 files changed, 292 insertions(+), 2 deletions(-) create mode 100755 ci/scripts/mysql create mode 100644 ci/scripts/mysql.sql create mode 100755 ci/scripts/pg_restore create mode 100644 ci/scripts/postgres.sql create mode 100755 ci/scripts/psql diff --git a/ci/kustomize/databases/mysql.yaml b/ci/kustomize/databases/mysql.yaml index d85e060..79480df 100644 --- a/ci/kustomize/databases/mysql.yaml +++ b/ci/kustomize/databases/mysql.yaml @@ -5,7 +5,7 @@ metadata: spec: backup: enable: true - cron: '*/5 * * * *' + cron: '*/40 * * * *' bucket: backups envFromSecret: s3-creds deletionProtected: false diff --git a/ci/kustomize/databases/postgres.yaml b/ci/kustomize/databases/postgres.yaml index 388d403..9e4b972 100644 --- a/ci/kustomize/databases/postgres.yaml +++ b/ci/kustomize/databases/postgres.yaml @@ -5,7 +5,7 @@ metadata: spec: backup: enable: true - cron: '*/5 * * * *' + cron: '*/40 * * * *' bucket: backups envFromSecret: s3-creds deletionProtected: false diff --git a/ci/scripts/mysql b/ci/scripts/mysql new file mode 100755 index 0000000..4377dec --- /dev/null +++ b/ci/scripts/mysql @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +export DBPASS=$(kubectl get secret mysql-creds -o yaml | yq .data.PASSWORD | base64 -d) +export DBNAME=$(kubectl get secret mysql-creds -o yaml | yq .data.DB | base64 -d) +export DBUSER=$(kubectl get secret mysql-creds -o yaml | yq .data.USER | base64 -d) + +mysql --host=localhost --port=3306 --user="${DBUSER}" --password="${DBPASS}" --database="${DBNAME}" "$@" diff --git a/ci/scripts/mysql.sql b/ci/scripts/mysql.sql new file mode 100644 index 0000000..7df4fcf --- /dev/null +++ b/ci/scripts/mysql.sql @@ -0,0 +1,142 @@ +-- ========================================================= +-- MariaDB Random Data Generator for Backup Testing +-- ========================================================= + +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS order_items; +DROP TABLE IF EXISTS orders; +DROP TABLE IF EXISTS products; +DROP TABLE IF EXISTS customers; +DROP TABLE IF EXISTS numbers; + +SET FOREIGN_KEY_CHECKS = 1; + +-- ========================================================= +-- Helper numbers table (FAST and MariaDB-friendly) +-- ========================================================= + +CREATE TABLE numbers ( + n INT PRIMARY KEY +) ENGINE=InnoDB; + +-- Fill numbers table (1..1,000,000 max capacity) +INSERT INTO numbers (n) +SELECT a.N + b.N*10 + c.N*100 + d.N*1000 + e.N*10000 + 1 +FROM + (SELECT 0 N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 + UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a, + (SELECT 0 N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 + UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b, + (SELECT 0 N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 + UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c, + (SELECT 0 N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 + UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d, + (SELECT 0 N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 + UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) e; + +-- ========================================================= +-- Tables +-- ========================================================= + +CREATE TABLE customers ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(50) NOT NULL, + last_name VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + created_at DATETIME NOT NULL +) ENGINE=InnoDB; + +CREATE TABLE products ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + price DECIMAL(10,2) NOT NULL, + created_at DATETIME NOT NULL +) ENGINE=InnoDB; + +CREATE TABLE orders ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + customer_id BIGINT NOT NULL, + order_date DATETIME NOT NULL, + status VARCHAR(20) NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(id) +) ENGINE=InnoDB; + +CREATE TABLE order_items ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + order_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + quantity INT NOT NULL, + unit_price DECIMAL(10,2) NOT NULL, + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id) +) ENGINE=InnoDB; + +-- ========================================================= +-- Insert customers (100k) +-- ========================================================= + +INSERT INTO customers (first_name, last_name, email, created_at) +SELECT + CONCAT('First', n), + CONCAT('Last', n), + CONCAT('user', n, '@example.com'), + NOW() - INTERVAL FLOOR(RAND()*365) DAY +FROM numbers +WHERE n <= 100000; + +-- ========================================================= +-- Insert products (5k) +-- ========================================================= + +INSERT INTO products (name, price, created_at) +SELECT + CONCAT('Product ', n), + ROUND(RAND()*500 + 5, 2), + NOW() - INTERVAL FLOOR(RAND()*365) DAY +FROM numbers +WHERE n <= 5000; + +-- ========================================================= +-- Insert orders (300k) +-- ========================================================= + +INSERT INTO orders (customer_id, order_date, status) +SELECT + FLOOR(RAND()*100000) + 1, + NOW() - INTERVAL FLOOR(RAND()*365) DAY, + ELT(FLOOR(RAND()*4)+1, 'pending','shipped','delivered','cancelled') +FROM numbers +WHERE n <= 300000; + +-- ========================================================= +-- Insert order items (≈1–5 per order) +-- ========================================================= + +INSERT INTO order_items (order_id, product_id, quantity, unit_price) +SELECT + o.id, + FLOOR(RAND()*5000) + 1, + FLOOR(RAND()*4) + 1, + ROUND(RAND()*500 + 5, 2) +FROM orders o +JOIN numbers r ON r.n <= 5 +WHERE RAND() < 0.6; + +-- ========================================================= +-- Indexes +-- ========================================================= + +CREATE INDEX idx_orders_customer ON orders(customer_id); +CREATE INDEX idx_order_items_order ON order_items(order_id); +CREATE INDEX idx_order_items_product ON order_items(product_id); + +-- ========================================================= +-- Summary +-- ========================================================= + +SELECT + (SELECT COUNT(*) FROM customers) AS customers, + (SELECT COUNT(*) FROM products) AS products, + (SELECT COUNT(*) FROM orders) AS orders, + (SELECT COUNT(*) FROM order_items) AS order_items; diff --git a/ci/scripts/pg_restore b/ci/scripts/pg_restore new file mode 100755 index 0000000..ad68a37 --- /dev/null +++ b/ci/scripts/pg_restore @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +export PGPASSWORD=$(kubectl get secret -n db-operator postgres-admin-secret -o yaml | yq .data.password | base64 -d) +export PGDATABASE=postgres +export PGUSER=$(kubectl get secret -n db-operator postgres-admin-secret -o yaml | yq .data.user | base64 -d) +export PGHOST=localhost +export DATABASE=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_DB | base64 -d) +export USERNAME=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_USER | base64 -d) + +pg_restore --no-owner --no-privileges -d $DATABASE --role $USERNAME -Fc ~/Downloads/working-namespace-postgres-latest.sql.gz --clean -vvv diff --git a/ci/scripts/postgres.sql b/ci/scripts/postgres.sql new file mode 100644 index 0000000..d71ccee --- /dev/null +++ b/ci/scripts/postgres.sql @@ -0,0 +1,123 @@ +-- ========================================================= +-- Random Test Data Generator for PostgreSQL Backup Testing +-- ========================================================= + +-- Optional: speed up bulk inserts +SET synchronous_commit = OFF; +SET maintenance_work_mem = '512MB'; + +-- ========================================================= +-- Drop existing tables (safe reset) +-- ========================================================= + +DROP TABLE IF EXISTS order_items; +DROP TABLE IF EXISTS orders; +DROP TABLE IF EXISTS products; +DROP TABLE IF EXISTS customers; + +-- ========================================================= +-- Create tables +-- ========================================================= + +CREATE TABLE customers ( + id BIGSERIAL PRIMARY KEY, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + email TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE TABLE products ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(10,2) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE TABLE orders ( + id BIGSERIAL PRIMARY KEY, + customer_id BIGINT NOT NULL REFERENCES customers(id), + order_date TIMESTAMP NOT NULL, + status TEXT NOT NULL +); + +CREATE TABLE order_items ( + id BIGSERIAL PRIMARY KEY, + order_id BIGINT NOT NULL REFERENCES orders(id), + product_id BIGINT NOT NULL REFERENCES products(id), + quantity INT NOT NULL, + unit_price NUMERIC(10,2) NOT NULL +); + +-- ========================================================= +-- Insert customers +-- Adjust the number in generate_series for scale +-- ========================================================= + +INSERT INTO customers (first_name, last_name, email, created_at) +SELECT + 'First' || gs, + 'Last' || gs, + 'user' || gs || '@example.com', + NOW() - (random() * interval '365 days') +FROM generate_series(1, 100000) AS gs; + +-- ========================================================= +-- Insert products +-- ========================================================= + +INSERT INTO products (name, price, created_at) +SELECT + 'Product ' || gs, + ROUND((random() * 500 + 5)::numeric, 2), + NOW() - (random() * interval '365 days') +FROM generate_series(1, 5000) AS gs; + +-- ========================================================= +-- Insert orders +-- ========================================================= + +INSERT INTO orders (customer_id, order_date, status) +SELECT + (random() * 99999 + 1)::BIGINT, + NOW() - (random() * interval '365 days'), + (ARRAY['pending','shipped','delivered','cancelled'])[floor(random()*4)+1] +FROM generate_series(1, 300000); + +-- ========================================================= +-- Insert order items +-- Each order gets 1–5 items +-- ========================================================= + +INSERT INTO order_items (order_id, product_id, quantity, unit_price) +SELECT + o.id, + (random() * 4999 + 1)::BIGINT, + (random() * 4 + 1)::INT, + ROUND((random() * 500 + 5)::numeric, 2) +FROM orders o +CROSS JOIN LATERAL generate_series(1, (random()*4 + 1)::INT); + +-- ========================================================= +-- Indexes (important for realistic backup size) +-- ========================================================= + +CREATE INDEX idx_orders_customer ON orders(customer_id); +CREATE INDEX idx_order_items_order ON order_items(order_id); +CREATE INDEX idx_order_items_product ON order_items(product_id); + +-- ========================================================= +-- Analyze for realistic planner stats +-- ========================================================= + +ANALYZE; + +-- ========================================================= +-- Summary +-- ========================================================= + +SELECT + (SELECT count(*) FROM customers) AS customers, + (SELECT count(*) FROM products) AS products, + (SELECT count(*) FROM orders) AS orders, + (SELECT count(*) FROM order_items) AS order_items; diff --git a/ci/scripts/psql b/ci/scripts/psql new file mode 100755 index 0000000..5ea6c73 --- /dev/null +++ b/ci/scripts/psql @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +export PGPASSWORD=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_PASSWORD | base64 -d) +export PGDATABASE=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_DB | base64 -d) +export PGUSER=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_USER | base64 -d) +export PGHOST=localhost + +psql "$@" From 8e6b5cee839a059e22390f4e7f2f91b1a6d8ba84 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 4 Mar 2026 17:14:17 +0100 Subject: [PATCH 3/9] Add some debugging scripts Signed-off-by: Nikolai Rodionov --- charts/db-operator/values.yaml | 2 +- ci/scripts/pg_restore | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100755 ci/scripts/pg_restore diff --git a/charts/db-operator/values.yaml b/charts/db-operator/values.yaml index 99cdb99..1ca20a8 100644 --- a/charts/db-operator/values.yaml +++ b/charts/db-operator/values.yaml @@ -32,7 +32,7 @@ controller: create: true config: backup: - activeDeadlineSeconds: 600 # 10m + activeDeadlineSeconds: 600 nodeSelector: {} postgres: image: "ghcr.io/db-operator/pgdump-rclone:0.1.0" diff --git a/ci/scripts/pg_restore b/ci/scripts/pg_restore deleted file mode 100755 index ad68a37..0000000 --- a/ci/scripts/pg_restore +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -export PGPASSWORD=$(kubectl get secret -n db-operator postgres-admin-secret -o yaml | yq .data.password | base64 -d) -export PGDATABASE=postgres -export PGUSER=$(kubectl get secret -n db-operator postgres-admin-secret -o yaml | yq .data.user | base64 -d) -export PGHOST=localhost -export DATABASE=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_DB | base64 -d) -export USERNAME=$(kubectl get secret postgres-creds -o yaml | yq .data.POSTGRES_USER | base64 -d) - -pg_restore --no-owner --no-privileges -d $DATABASE --role $USERNAME -Fc ~/Downloads/working-namespace-postgres-latest.sql.gz --clean -vvv From 385cc2e6de7b53ea1c1c18033cca484822f15ebc Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Fri, 10 Jul 2026 11:49:36 +0200 Subject: [PATCH 4/9] Update tag Signed-off-by: Nikolai Rodionov --- charts/db-operator/Chart.yaml | 2 +- charts/db-operator/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/db-operator/Chart.yaml b/charts/db-operator/Chart.yaml index 9ce86cc..101838b 100644 --- a/charts/db-operator/Chart.yaml +++ b/charts/db-operator/Chart.yaml @@ -7,7 +7,7 @@ description: This operator lets you manage databases in a Kubernetes native way, # All supported k8s versions are in the test: # https://github.com/db-operator/charts/blob/main/.github/workflows/integration-test.yaml kubeVersion: ">= 1.32-prerelease" -appVersion: "161b6438857d77df8f91fb4dd8c04cdc3654b420" +appVersion: 8b054a0094186274397130bb0888b066e3bdfb9b home: https://github.com/db-operator maintainers: - name: Nikolai Rodionov diff --git a/charts/db-operator/values.yaml b/charts/db-operator/values.yaml index 1ca20a8..955ca20 100644 --- a/charts/db-operator/values.yaml +++ b/charts/db-operator/values.yaml @@ -3,7 +3,7 @@ clusterDomain: cluster.local image: registry: ghcr.io - repository: db-operator/db-operator + repository: db-operator/db-operator-dev tag: "" pullPolicy: Always # imagePullSecrets: From 71b0874b14e61873c89a09010207d4c01de586d7 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Fri, 10 Jul 2026 11:49:46 +0200 Subject: [PATCH 5/9] Update tag Signed-off-by: Nikolai Rodionov --- charts/db-operator/README.md | 45 +++++-------------- .../crd/kinda.rocks_databases.yaml | 2 - 2 files changed, 10 insertions(+), 37 deletions(-) diff --git a/charts/db-operator/README.md b/charts/db-operator/README.md index 6365361..6b80a49 100644 --- a/charts/db-operator/README.md +++ b/charts/db-operator/README.md @@ -1,6 +1,6 @@ # db-operator -![Version: 3.10.0](https://img.shields.io/badge/Version-3.10.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.33.0](https://img.shields.io/badge/AppVersion-2.33.0-informational?style=flat-square) +![Version: 3.11.0](https://img.shields.io/badge/Version-3.11.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 8b054a0094186274397130bb0888b066e3bdfb9b](https://img.shields.io/badge/AppVersion-8b054a0094186274397130bb0888b066e3bdfb9b-informational?style=flat-square) This operator lets you manage databases in a Kubernetes native way, even if they are not deployed to Kubernetes @@ -27,7 +27,7 @@ Kubernetes: `>= 1.32-prerelease` | nameOverride | string | `""` | | | clusterDomain | string | `"cluster.local"` | | | image.registry | string | `"ghcr.io"` | | -| image.repository | string | `"db-operator/db-operator"` | | +| image.repository | string | `"db-operator/db-operator-dev"` | | | image.tag | string | `""` | | | image.pullPolicy | string | `"Always"` | | | metadata.labels | object | `{}` | | @@ -43,44 +43,17 @@ Kubernetes: `>= 1.32-prerelease` | controller.serviceAccount.name | string | `""` | | | controller.serviceAccount.create | bool | `true` | | | controller.rbac.create | bool | `true` | | -| controller.args.reconcileInterval | string | `"60"` | | -| controller.args.watchNamespace | string | `""` | | -| controller.args.checkForChanges | bool | `false` | | -| controller.serviceMonitor.enabled | bool | `false` | | -| controller.config.instance.google.proxy.nodeSelector | object | `{}` | | -| controller.config.instance.google.proxy.image | string | `"ghcr.io/db-operator/db-auth-gateway:v0.1.10"` | | -| controller.config.instance.google.proxy.metricsPort | int | `9090` | | -| controller.config.instance.generic | object | `{}` | | -| controller.config.instance.percona.proxy.image | string | `"severalnines/proxysql:2.0"` | | -| controller.config.instance.percona.proxy.metricsPort | int | `9090` | | | controller.config.backup.activeDeadlineSeconds | int | `600` | | | controller.config.backup.nodeSelector | object | `{}` | | -| controller.config.backup.postgres.image | string | `"kloeckneri/pgdump-gcs:latest"` | | -| controller.config.backup.mysql.image | string | `"kloeckneri/mydump-gcs:latest"` | | +| controller.config.backup.postgres.image | string | `"ghcr.io/db-operator/pgdump-rclone:0.1.0"` | | +| controller.config.backup.mysql.image | string | `"ghcr.io/db-operator/mydump-rclone:0.1.0"` | | | controller.config.backup.resources.requests.memory | string | `"64Mi"` | | | controller.config.backup.resources.requests.cpu | float | `0.2` | | | controller.config.monitoring.promPushGateway | string | `""` | | -| controller.config.monitoring.nodeSelector | object | `{}` | | -| controller.config.monitoring.postgres.image | string | `"wrouesnel/postgres_exporter:latest"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.query | string | `"SELECT userid, pgss.dbid, pgdb.datname, queryid, query, calls, total_time, mean_time, rows FROM pg_stat_statements pgss LEFT JOIN (select oid as dbid, datname from pg_database) as pgdb on pgdb.dbid = pgss.dbid WHERE not queryid isnull ORDER BY mean_time desc limit 20"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[0].userid.usage | string | `"LABEL"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[0].userid.description | string | `"User ID"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[1].dbid.usage | string | `"LABEL"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[1].dbid.description | string | `"database ID"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[2].datname.usage | string | `"LABEL"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[2].datname.description | string | `"database NAME"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[3].queryid.usage | string | `"LABEL"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[3].queryid.description | string | `"Query unique Hash Code"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[4].query.usage | string | `"LABEL"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[4].query.description | string | `"Query class"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[5].calls.usage | string | `"COUNTER"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[5].calls.description | string | `"Number of times executed"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[6].total_time.usage | string | `"COUNTER"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[6].total_time.description | string | `"Total time spent in the statement, in milliseconds"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[7].mean_time.usage | string | `"GAUGE"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[7].mean_time.description | string | `"Mean time spent in the statement, in milliseconds"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[8].rows.usage | string | `"COUNTER"` | | -| controller.config.monitoring.postgres.queries.pg_stat_statements.metrics[8].rows.description | string | `"Total number of rows retrieved or affected by the statement"` | | +| controller.args.reconcileInterval | string | `"60"` | | +| controller.args.watchNamespace | string | `""` | | +| controller.args.checkForChanges | bool | `false` | | +| controller.serviceMonitor.enabled | bool | `false` | | | webhook.enabled | bool | `true` | | | webhook.logLevel | string | `"info"` | | | webhook.extraArgs | list | `[]` | | @@ -106,4 +79,6 @@ Kubernetes: `>= 1.32-prerelease` | podLabels | object | `{}` | | | affinity | object | `{}` | | | tolerations | list | `[]` | | +| secrets.gsql | object | `{}` | | +| serviceMonitor.enabled | bool | `false` | | diff --git a/charts/db-operator/crd/kinda.rocks_databases.yaml b/charts/db-operator/crd/kinda.rocks_databases.yaml index 79aa271..518d6ae 100644 --- a/charts/db-operator/crd/kinda.rocks_databases.yaml +++ b/charts/db-operator/crd/kinda.rocks_databases.yaml @@ -372,8 +372,6 @@ spec: description: DatabaseBackup defines the desired state of backup and schedule properties: - bucket: - type: string cron: type: string enable: From 07210ebfff8d9c9467c34aaed80d50e32cb17ca7 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Mon, 13 Jul 2026 15:36:15 +0200 Subject: [PATCH 6/9] Update chart Signed-off-by: Nikolai Rodionov --- charts/db-operator/Chart.yaml | 2 +- charts/db-operator/README.md | 6 +++--- charts/db-operator/values.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/db-operator/Chart.yaml b/charts/db-operator/Chart.yaml index 101838b..24ff5b3 100644 --- a/charts/db-operator/Chart.yaml +++ b/charts/db-operator/Chart.yaml @@ -7,7 +7,7 @@ description: This operator lets you manage databases in a Kubernetes native way, # All supported k8s versions are in the test: # https://github.com/db-operator/charts/blob/main/.github/workflows/integration-test.yaml kubeVersion: ">= 1.32-prerelease" -appVersion: 8b054a0094186274397130bb0888b066e3bdfb9b +appVersion: 2.35.0 home: https://github.com/db-operator maintainers: - name: Nikolai Rodionov diff --git a/charts/db-operator/README.md b/charts/db-operator/README.md index 6b80a49..33a69c6 100644 --- a/charts/db-operator/README.md +++ b/charts/db-operator/README.md @@ -1,6 +1,6 @@ # db-operator -![Version: 3.11.0](https://img.shields.io/badge/Version-3.11.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 8b054a0094186274397130bb0888b066e3bdfb9b](https://img.shields.io/badge/AppVersion-8b054a0094186274397130bb0888b066e3bdfb9b-informational?style=flat-square) +![Version: 3.11.0](https://img.shields.io/badge/Version-3.11.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.35.0](https://img.shields.io/badge/AppVersion-2.35.0-informational?style=flat-square) This operator lets you manage databases in a Kubernetes native way, even if they are not deployed to Kubernetes @@ -45,8 +45,8 @@ Kubernetes: `>= 1.32-prerelease` | controller.rbac.create | bool | `true` | | | controller.config.backup.activeDeadlineSeconds | int | `600` | | | controller.config.backup.nodeSelector | object | `{}` | | -| controller.config.backup.postgres.image | string | `"ghcr.io/db-operator/pgdump-rclone:0.1.0"` | | -| controller.config.backup.mysql.image | string | `"ghcr.io/db-operator/mydump-rclone:0.1.0"` | | +| controller.config.backup.postgres.image | string | `"ghcr.io/db-operator/pgdump-rclone:0.1.1"` | | +| controller.config.backup.mysql.image | string | `"ghcr.io/db-operator/mydump-rclone:0.1.1"` | | | controller.config.backup.resources.requests.memory | string | `"64Mi"` | | | controller.config.backup.resources.requests.cpu | float | `0.2` | | | controller.config.monitoring.promPushGateway | string | `""` | | diff --git a/charts/db-operator/values.yaml b/charts/db-operator/values.yaml index 955ca20..40c34c5 100644 --- a/charts/db-operator/values.yaml +++ b/charts/db-operator/values.yaml @@ -35,9 +35,9 @@ controller: activeDeadlineSeconds: 600 nodeSelector: {} postgres: - image: "ghcr.io/db-operator/pgdump-rclone:0.1.0" + image: "ghcr.io/db-operator/pgdump-rclone:0.1.1" mysql: - image: "ghcr.io/db-operator/mydump-rclone:0.1.0" + image: "ghcr.io/db-operator/mydump-rclone:0.1.1" resources: requests: memory: 64Mi From 2ce8b26fd79cb29909d020a02cc50c352170389d Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Mon, 13 Jul 2026 15:38:55 +0200 Subject: [PATCH 7/9] Update chart Signed-off-by: Nikolai Rodionov --- charts/db-operator/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/db-operator/Chart.yaml b/charts/db-operator/Chart.yaml index 1ab3bcc..4d233d8 100644 --- a/charts/db-operator/Chart.yaml +++ b/charts/db-operator/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 type: application name: db-operator -version: 3.11.0 +version: 3.12.0 description: This operator lets you manage databases in a Kubernetes native way, even if they are not deployed to Kubernetes # All supported k8s versions are in the test: # https://github.com/db-operator/charts/blob/main/.github/workflows/integration-test.yaml From 66b46b6a5b7956d496c73be699b29c74e6c34414 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Mon, 13 Jul 2026 16:10:59 +0200 Subject: [PATCH 8/9] Use the main image Signed-off-by: Nikolai Rodionov --- charts/db-operator/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/db-operator/values.yaml b/charts/db-operator/values.yaml index 40c34c5..1b54f13 100644 --- a/charts/db-operator/values.yaml +++ b/charts/db-operator/values.yaml @@ -3,7 +3,7 @@ clusterDomain: cluster.local image: registry: ghcr.io - repository: db-operator/db-operator-dev + repository: db-operator/db-operator tag: "" pullPolicy: Always # imagePullSecrets: From afaa305320ebb6510463b91c6b78755105013fda Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Mon, 13 Jul 2026 18:45:08 +0200 Subject: [PATCH 9/9] Update docs Signed-off-by: Nikolai Rodionov --- charts/db-operator/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/db-operator/README.md b/charts/db-operator/README.md index 31bc63b..6c1d2ea 100644 --- a/charts/db-operator/README.md +++ b/charts/db-operator/README.md @@ -1,6 +1,6 @@ # db-operator -![Version: 3.11.0](https://img.shields.io/badge/Version-3.11.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.35.0](https://img.shields.io/badge/AppVersion-2.35.0-informational?style=flat-square) +![Version: 3.12.0](https://img.shields.io/badge/Version-3.12.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.35.0](https://img.shields.io/badge/AppVersion-2.35.0-informational?style=flat-square) This operator lets you manage databases in a Kubernetes native way, even if they are not deployed to Kubernetes @@ -27,7 +27,7 @@ Kubernetes: `>= 1.32-prerelease` | nameOverride | string | `""` | | | clusterDomain | string | `"cluster.local"` | | | image.registry | string | `"ghcr.io"` | | -| image.repository | string | `"db-operator/db-operator-dev"` | | +| image.repository | string | `"db-operator/db-operator"` | | | image.tag | string | `""` | | | image.pullPolicy | string | `"Always"` | | | metadata.labels | object | `{}` | |