diff --git a/charts/db-operator/Chart.yaml b/charts/db-operator/Chart.yaml index e618d44..4d233d8 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.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 kubeVersion: ">= 1.32-prerelease" -appVersion: "2.34.0" +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 62bb5f6..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.34.0](https://img.shields.io/badge/AppVersion-2.34.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 @@ -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.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 | `""` | | -| 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 0927361..518d6ae 100644 --- a/charts/db-operator/crd/kinda.rocks_databases.yaml +++ b/charts/db-operator/crd/kinda.rocks_databases.yaml @@ -376,6 +376,8 @@ spec: 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..1b54f13 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 nodeSelector: {} postgres: - image: "kloeckneri/pgdump-gcs:latest" + image: "ghcr.io/db-operator/pgdump-rclone:0.1.1" mysql: - image: "kloeckneri/mydump-gcs:latest" + image: "ghcr.io/db-operator/mydump-rclone:0.1.1" 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 07d497c..eb164fb 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..79480df 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: '*/40 * * * *' + 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..9e4b972 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: '*/40 * * * *' + 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 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/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 "$@"