Database cost optimization CLI. Scan your warehouse, find waste, fix it.
Created by Arthi Arumugam
English | 中文 | 日本語 | हिन्दी | 한국어 | Español | Français | Deutsch | Italiano | Português | Nederlands | Polski | Русский | Türkçe | Svenska | العربية | বাংলা | اردو | தமிழ் | فارسی | עברית | Bahasa Indonesia | Tiếng Việt | ไทย | Filipino | Bahasa Melayu | Українська | Ελληνικά | Čeština | Română | Magyar | Dansk | Norsk | Suomi | Kiswahili
costctl scans your database infrastructure, identifies cost optimization opportunities, and generates actionable fixes — across BigQuery, Snowflake, PostgreSQL, Databricks, Redshift, and MySQL.
pip install "costctl[bigquery]" # Google BigQuery
pip install "costctl[snowflake]" # Snowflake
pip install "costctl[postgres]" # PostgreSQL
pip install "costctl[databricks]" # Databricks (Unity Catalog)
pip install "costctl[redshift]" # Amazon Redshift
pip install "costctl[mysql]" # MySQL
pip install "costctl[all]" # all database backends
pip install "costctl[web]" # web dashboard (Flask)# Scan using a named connection from .costctl.yaml
costctl scan --connection prod-bq
# Scan with inline flags
costctl scan --type bigquery --project my-gcp-project --dataset production
# Save results for later comparison
costctl scan --type snowflake --account xy12345 --warehouse WH --save baseline.jsoncostctl runs 6 analyzers against your database metadata and query history:
| Analyzer | What it detects | Example fix |
|---|---|---|
| Partitioning | Tables missing date/range partitions that would reduce scan bytes | PARTITION BY DATE(event_date) |
| Clustering | Columns queried in WHERE/JOIN that aren't clustered | CLUSTER BY (customer_id, status) |
| Query Patterns | Repeated full-table scans, SELECT *, expensive recurring queries | Column-specific SELECT lists |
| Idle Tables | Tables not read or written in N days (configurable) | DROP TABLE or archive |
| Storage | Bloated tables, vacuum gaps, excessive retention, redundant snapshots | VACUUM, retention policies |
| Resources | Oversized warehouses, always-on compute, unutilized reserved slots | Downsize or auto-suspend |
Each finding includes an estimated monthly savings in USD and a ready-to-run DDL fix where applicable.
costctl scan Scan for cost issues and estimate savings
costctl plan Generate a JSON remediation plan
costctl apply Execute fixes interactively (with confirmation prompts)
costctl report Generate a report (terminal, JSON, or CSV)
costctl diff Compare two scans to track progress over time
costctl forecast Predict cost trends from scan history
costctl dashboard Launch a web UI with charts, filters, and sorting
$ costctl scan --connection prod-bq
costctl v0.1.0 | connection: prod-bq
Scanning 142 tables across datasets: production, analytics...
PARTITIONING
x production.events Add daily partition on event_date -> save ~$420/mo
x analytics.pageviews Add daily partition on ts -> save ~$180/mo
CLUSTERING
x production.orders Cluster on (customer_id, status) -> save ~$95/mo
IDLE TABLES (>30 days unused)
x analytics.temp_export_2024 last read: 94 days ago (12 GB)
x production.legacy_sessions last read: 61 days ago (3.4 GB)
QUERY PATTERNS
x 3 queries use SELECT * on tables >10 GB
x 1 recurring query scans 2.1 TB/day -- add partition filter
Total estimated savings: $695-$740 / month
Issues found: 8 | Run 'costctl plan' to generate fixes
Track cost changes over time by saving scans and comparing them:
# Compare two saved scans — see what's resolved, new, or remaining
costctl diff before.json after.json
# Compare a baseline against a live scan
costctl diff --baseline baseline.json --type bigquery --project my-proj
# Forecast cost trends from 2+ snapshots
costctl forecast scan-jan.json scan-feb.json scan-mar.json --jsonLaunch an interactive dashboard from any saved scan file:
# Single scan
costctl dashboard scan-results.json
# Multiple scans — adds a cost trend chart
costctl dashboard scan-jan.json scan-feb.json scan-mar.json --port 8080Dashboard features:
- Savings by analyzer (bar chart) and severity breakdown (doughnut chart)
- Cost trend line when comparing multiple scans
- Filter by severity, analyzer, or database type
- Click any column header to sort
- JSON API at
/api/findings
Run costctl in CI to catch cost regressions automatically:
- name: Scan for cost issues
uses: arthi-arumugam-git/costctl@main
with:
type: bigquery
connection-args: "--project my-proj --dataset production"
format: json
fail-on-critical: "true"The action outputs findings-count, critical-count, and total-savings for use in downstream steps. See action.yml for all inputs and outputs.
costctl report --type bigquery --project my-proj # terminal (default)
costctl report --type bigquery --project my-proj --json # JSON
costctl report --type bigquery --project my-proj --csv # CSV
costctl scan --type snowflake --account X --save results.json # save to fileCreate .costctl.yaml in your project root or ~/.costctl.yaml for global config:
connections:
prod-bq:
type: bigquery
project: my-gcp-project
datasets: [production, analytics]
prod-snowflake:
type: snowflake
account: xy12345.us-east-1
warehouse: ANALYTICS_WH
prod-pg:
type: postgres
host: prod-db.example.com
database: myapp
prod-databricks:
type: databricks
host: adb-123.azuredatabricks.net
http_path: /sql/1.0/warehouses/abc
token: dapi...
catalog: main
prod-redshift:
type: redshift
host: cluster.abc.us-east-1.redshift.amazonaws.com
database: analytics
user: admin
password: ${REDSHIFT_PASSWORD}
prod-mysql:
type: mysql
host: db.example.com
database: myapp
user: readonly
password: ${MYSQL_PASSWORD}
thresholds:
stale_days: 30 # days before a table is considered idle
large_table_gb: 100 # tables above this get extra scrutiny
expensive_query_tb: 1 # queries scanning more than this trigger a finding
databricks_dbu_rate: 0.22 # cost per DBU for Databricks savings estimatesgit clone https://github.com/arthi-arumugam-git/costctl.git
cd costctl
pip install -e ".[dev]"
pytest301 tests across all connectors, analyzers, reporters, and CLI commands. Contributions, bug reports, and feature requests are welcome. Please open an issue before submitting a large pull request.
MIT — see LICENSE.


