Capacity Planning
Cost Optimization
Read aloud is unavailable in this browser.
The Economics of Database Infrastructure
Database infrastructure represents one of the largest line items in technology budgets. Between compute, storage, licensing, backup, and operational costs, databases can consume 30-50% of total infrastructure spend. Yet many organizations significantly overspend—paying for capacity they don't use, maintaining inefficient configurations, or failing to leverage cost-effective alternatives.
Cost optimization isn't about cutting corners or accepting degraded performance. It's about achieving required performance and reliability at the lowest sustainable cost. Strategic cost management frees budget for innovation while maintaining the service levels users expect.
This page explores systematic approaches to database cost optimization—from infrastructure rightsizing to architectural decisions that reduce long-term expense.
By the end of this page, you will understand cost components of database infrastructure, techniques for rightsizing resources, cloud cost optimization strategies, licensing considerations, and operational efficiency improvements. You'll learn to build a culture of cost awareness without sacrificing reliability.
Understanding Database Cost Components
Before optimizing, you must understand where money is spent. Database costs span multiple categories, each with different optimization levers and trade-offs.
| Cost Category | Typical % of Total | Drivers | Optimization Potential |
|---|---|---|---|
| Compute (CPU/Memory) | 25-40% | Instance size, utilization, count | High - often oversized |
| Storage | 15-30% | Data volume, tier, IOPS | Medium-High - tiering opportunities |
| Licensing | 10-40% | Edition, cores, features | High - often over-licensed |
| Network/Data Transfer | 5-15% | Egress, cross-region traffic | Medium - architecture dependent |
| Backup/DR | 5-15% | Retention, frequency, storage | Medium - policy optimization |
| Operations/Labor | 15-25% | Automation level, team size | High - automation investment |
| Monitoring/Tools | 2-5% | Observability platforms | Low - necessary investment |
The hidden costs:
Beyond direct infrastructure spend, database systems incur indirect costs that are often overlooked:
- •Downtime Costs — Production outages cost far more than infrastructure. Poor reliability optimization creates massive hidden costs.
- •Performance-Related Revenue Impact — Slow queries reduce conversions and user engagement. The cost isn't in infrastructure—it's in lost business.
- •Developer Productivity — Slow development databases, difficult migrations, and operational complexity burn engineering time.
- •Opportunity Cost — Budget locked in over-provisioned infrastructure can't fund new initiatives.
- •Technical Debt Interest — Quick cost cuts that create maintenance burden cost more in the long run.
Always consider total cost of ownership, not just monthly infrastructure bills. A cheaper database option that requires 2x operational overhead may cost more overall. Include labor, downtime risk, and opportunity cost in optimization decisions.
Infrastructure Rightsizing
Rightsizing means matching provisioned resources to actual requirements—neither over-provisioned (wasting money) nor under-provisioned (degrading performance). Most organizations are over-provisioned because it's safer to have excess capacity than risk outages.
The rightsizing process:
- •Measure Current Utilization — Collect resource utilization data across all dimensions (CPU, memory, storage, IOPS) over extended periods (weeks, not hours).
- •Identify Peak Requirements — Determine actual peak utilization, accounting for daily, weekly, and seasonal patterns.
- •Calculate Target Sizing — Size for peak utilization at 70% capacity target, providing headroom without waste.
- •Validate Before Changing — Test downsized configurations in staging before production changes.
- •Implement Gradually — Make changes during low-traffic periods with rollback capability.
- •Monitor Post-Change — Verify performance meets requirements after rightsizing.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
-- Comprehensive rightsizing analysis queries -- CPU Rightsizing AssessmentWITH hourly_cpu AS ( SELECT DATE_TRUNC('hour', collection_timestamp) AS hour_bucket, AVG(cpu_utilization_pct) AS avg_cpu, MAX(cpu_utilization_pct) AS max_cpu, PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY cpu_utilization_pct) AS p95_cpu FROM system_metrics WHERE collection_timestamp >= NOW() - INTERVAL '30 days' GROUP BY DATE_TRUNC('hour', collection_timestamp)),cpu_summary AS ( SELECT MAX(p95_cpu) AS peak_p95_cpu, AVG(avg_cpu) AS overall_avg_cpu, PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY max_cpu) AS p99_peak_cpu FROM hourly_cpu)SELECT 16 AS current_cores, ROUND(overall_avg_cpu, 1) AS avg_utilization_pct, ROUND(peak_p95_cpu, 1) AS peak_p95_pct, ROUND(p99_peak_cpu, 1) AS p99_peak_pct, -- Rightsizing calculation: target 70% at peak CEILING(16.0 * p99_peak_cpu / 70) AS recommended_cores, CASE WHEN p99_peak_cpu < 35 THEN 'OVERSIZED - Consider 50% reduction' WHEN p99_peak_cpu < 50 THEN 'SOMEWHAT OVERSIZED - Consider 25% reduction' WHEN p99_peak_cpu < 70 THEN 'ACCEPTABLE - Minor optimization possible' WHEN p99_peak_cpu < 85 THEN 'OPTIMAL - Well-sized' ELSE 'UNDERSIZED - Consider upgrade' END AS cpu_assessmentFROM cpu_summary; -- Memory Rightsizing Assessment WITH memory_metrics AS ( SELECT DATE_TRUNC('hour', collection_timestamp) AS hour_bucket, AVG(memory_used_pct) AS avg_memory, MAX(memory_used_pct) AS max_memory, AVG(buffer_cache_hit_ratio) AS avg_cache_hit FROM system_metrics WHERE collection_timestamp >= NOW() - INTERVAL '30 days' GROUP BY DATE_TRUNC('hour', collection_timestamp))SELECT 128 AS current_memory_gb, ROUND(AVG(avg_memory), 1) AS avg_memory_pct, ROUND(MAX(max_memory), 1) AS peak_memory_pct, ROUND(AVG(avg_cache_hit) * 100, 2) AS avg_cache_hit_pct, CASE WHEN MAX(max_memory) < 50 AND AVG(avg_cache_hit) > 0.98 THEN 'OVERSIZED - Can likely reduce 30-50%' WHEN MAX(max_memory) < 70 AND AVG(avg_cache_hit) > 0.95 THEN 'SOMEWHAT OVERSIZED - Can reduce 10-20%' WHEN AVG(avg_cache_hit) > 0.97 THEN 'OPTIMAL - Good balance' WHEN AVG(avg_cache_hit) < 0.90 THEN 'UNDERSIZED - Cache pressure affecting performance' ELSE 'ACCEPTABLE' END AS memory_assessmentFROM memory_metrics; -- Storage Rightsizing AssessmentWITH storage_usage AS ( SELECT database_name, ROUND(total_size_gb, 1) AS total_size_gb, ROUND(used_size_gb, 1) AS used_size_gb, ROUND(100.0 * used_size_gb / NULLIF(total_size_gb, 0), 1) AS utilization_pct, monthly_growth_rate_pct FROM storage_allocation)SELECT database_name, total_size_gb || ' GB' AS allocated, used_size_gb || ' GB' AS used, utilization_pct || '%' AS utilization, -- Months until full at current growth CASE WHEN monthly_growth_rate_pct > 0 THEN ROUND(LN((100.0 - utilization_pct) / 100.0 + 1) / LN(1 + monthly_growth_rate_pct / 100.0), 1) ELSE NULL END AS months_to_full, CASE WHEN utilization_pct < 30 THEN 'HIGHLY OVERSIZED - Reduce allocation 50%+' WHEN utilization_pct < 50 THEN 'OVERSIZED - Reduce allocation 20-30%' WHEN utilization_pct < 70 THEN 'ACCEPTABLE' WHEN utilization_pct < 85 THEN 'OPTIMAL' ELSE 'NEEDS EXPANSION' END AS recommendationFROM storage_usageORDER BY utilization_pct; -- Combined savings estimateWITH potential_savings AS ( SELECT -- Assume current monthly cost of $8000 8000 AS current_monthly_cost, -- Component-specific potential savings (based on analysis above) 0.20 AS cpu_savings_potential, -- 20% from CPU rightsizing 0.15 AS memory_savings_potential, -- 15% from memory rightsizing 0.25 AS storage_savings_potential -- 25% from storage rightsizing)SELECT current_monthly_cost AS current_cost, ROUND(current_monthly_cost * 0.35 * cpu_savings_potential, 0) AS cpu_savings, ROUND(current_monthly_cost * 0.25 * memory_savings_potential, 0) AS memory_savings, ROUND(current_monthly_cost * 0.20 * storage_savings_potential, 0) AS storage_savings, ROUND(current_monthly_cost * (0.35 * cpu_savings_potential + 0.25 * memory_savings_potential + 0.20 * storage_savings_potential), 0) AS total_monthly_savings, ROUND(current_monthly_cost * (0.35 * cpu_savings_potential + 0.25 * memory_savings_potential + 0.20 * storage_savings_potential) * 12, 0) AS annual_savingsFROM potential_savings;Always rightsize conservatively. A 10% utilization increase from unexpected traffic is easily absorbed at 70% target but causes problems at 90%. Validate changes in staging. Have quick rollback procedures. Monitor closely after changes.
Cloud Database Cost Optimization
Cloud databases offer flexibility but can become expensive without active cost management. Cloud providers offer various pricing models and optimization features—understanding and leveraging these is essential for cost efficiency.
Cloud pricing models:
| Model | Discount | Commitment | Best For |
|---|---|---|---|
| On-Demand | 0% | None | Unpredictable workloads, testing, development |
| Reserved Instances (1yr) | 30-40% | 1 year capacity | Stable production workloads |
| Reserved Instances (3yr) | 50-60% | 3 years capacity | Long-term, stable workloads |
| Savings Plans | 30-40% | Commitment to spend level | Flexible but predictable spend |
| Spot/Preemptible | 60-90% | Can be interrupted | Fault-tolerant batch, dev/test |
| Serverless | Variable | Per-request pricing | Intermittent, variable workloads |
Cloud-specific optimization strategies:
- •Reserved Instance Coverage — Commit to RIs for baseline capacity, use on-demand for peak overflow. Target 70-80% RI coverage for production databases.
- •Instance Family Selection — Choose appropriate instance types. Memory-optimized for cache-heavy workloads; compute-optimized for CPU-bound; burstable (T-series) for variable load.
- •Storage Tier Optimization — Use Provisioned IOPS only when needed. Standard SSD often sufficient. Archive cold data to cheaper storage classes.
- •Auto-Scaling — Enable read replica auto-scaling for variable read load. Scale out during peaks, scale in during quiet periods.
- •Cross-Region Strategy — Minimize data transfer costs. Keep compute and data in same region. Use CDN for read-heavy global workloads.
- •Idle Resource Elimination — Stop development/test databases outside business hours. Delete unused snapshots. Clean up abandoned resources.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
"""Cloud Database Cost Optimization Analysis Analyzes cloud database resources for cost optimization opportunities.Supports AWS RDS, Azure SQL, and GCP Cloud SQL patterns.""" from dataclasses import dataclassfrom typing import List, Dict, Optionalfrom datetime import datetime, timedelta @dataclassclass DatabaseInstance: """Represents a cloud database instance""" instance_id: str instance_class: str engine: str region: str multi_az: bool storage_type: str storage_gb: int iops_provisioned: Optional[int] monthly_cost: float avg_cpu_utilization: float avg_memory_utilization: float avg_iops_utilized: Optional[int] avg_connections: int environment: str # production, staging, development class CloudCostOptimizer: """Analyzes and recommends cloud database cost optimizations""" def __init__(self, instances: List[DatabaseInstance]): self.instances = instances def analyze_all(self) -> Dict: """Run all optimization analyses""" return { 'rightsizing': self.analyze_rightsizing(), 'reserved_instances': self.analyze_ri_opportunities(), 'storage_optimization': self.analyze_storage(), 'idle_resources': self.find_idle_resources(), 'multi_az_review': self.review_multi_az(), 'total_current_cost': sum(i.monthly_cost for i in self.instances), 'estimated_savings': self._calculate_total_savings() } def analyze_rightsizing(self) -> List[Dict]: """Identify instances that can be downsized""" recommendations = [] for instance in self.instances: if instance.avg_cpu_utilization < 20 and instance.avg_memory_utilization < 40: # Significantly underutilized recommendations.append({ 'instance_id': instance.instance_id, 'current_class': instance.instance_class, 'recommendation': 'Downsize by 50%', 'reason': f'CPU: {instance.avg_cpu_utilization}%, Memory: {instance.avg_memory_utilization}%', 'estimated_savings_pct': 45, 'risk': 'Low' if instance.environment != 'production' else 'Medium' }) elif instance.avg_cpu_utilization < 40 and instance.avg_memory_utilization < 60: # Moderately underutilized recommendations.append({ 'instance_id': instance.instance_id, 'current_class': instance.instance_class, 'recommendation': 'Downsize by 25%', 'reason': f'CPU: {instance.avg_cpu_utilization}%, Memory: {instance.avg_memory_utilization}%', 'estimated_savings_pct': 20, 'risk': 'Low' if instance.environment != 'production' else 'Medium' }) return recommendations def analyze_ri_opportunities(self) -> Dict: """Identify reserved instance purchase opportunities""" production_instances = [i for i in self.instances if i.environment == 'production'] on_demand_cost = sum(i.monthly_cost for i in production_instances) ri_1yr_cost = on_demand_cost * 0.65 # ~35% savings ri_3yr_cost = on_demand_cost * 0.45 # ~55% savings return { 'production_instance_count': len(production_instances), 'current_monthly_cost': on_demand_cost, 'ri_1yr_monthly_cost': ri_1yr_cost, 'ri_1yr_annual_savings': (on_demand_cost - ri_1yr_cost) * 12, 'ri_3yr_monthly_cost': ri_3yr_cost, 'ri_3yr_annual_savings': (on_demand_cost - ri_3yr_cost) * 12, 'recommendation': f'Consider 1-year RIs for {len(production_instances)} stable production instances' } def analyze_storage(self) -> List[Dict]: """Identify storage optimization opportunities""" recommendations = [] for instance in self.instances: # Check for over-provisioned IOPS if instance.iops_provisioned and instance.avg_iops_utilized: iops_utilization = instance.avg_iops_utilized / instance.iops_provisioned if iops_utilization < 0.3: recommendations.append({ 'instance_id': instance.instance_id, 'type': 'IOPS', 'recommendation': 'Reduce provisioned IOPS or switch to gp3', 'detail': f'Using only {iops_utilization*100:.0f}% of provisioned IOPS', 'estimated_savings': instance.monthly_cost * 0.10 }) # Check for storage type optimization if instance.storage_type == 'io1' and instance.avg_iops_utilized and instance.avg_iops_utilized < 3000: recommendations.append({ 'instance_id': instance.instance_id, 'type': 'Storage Type', 'recommendation': 'Switch from io1 to gp3', 'detail': 'gp3 provides 3000 IOPS baseline at lower cost', 'estimated_savings': instance.monthly_cost * 0.15 }) return recommendations def find_idle_resources(self) -> List[Dict]: """Find databases with minimal or no activity""" idle_instances = [] for instance in self.instances: if instance.avg_connections < 5 and instance.avg_cpu_utilization < 5: idle_instances.append({ 'instance_id': instance.instance_id, 'environment': instance.environment, 'monthly_cost': instance.monthly_cost, 'recommendation': 'Review necessity - appears unused', 'metrics': f'Avg connections: {instance.avg_connections}, CPU: {instance.avg_cpu_utilization}%' }) return idle_instances def review_multi_az(self) -> List[Dict]: """Review Multi-AZ configurations for appropriateness""" recommendations = [] for instance in self.instances: if instance.multi_az and instance.environment in ['development', 'staging']: recommendations.append({ 'instance_id': instance.instance_id, 'environment': instance.environment, 'recommendation': 'Disable Multi-AZ for non-production', 'estimated_savings': instance.monthly_cost * 0.45 # Multi-AZ roughly doubles cost }) return recommendations def _calculate_total_savings(self) -> Dict: """Calculate total potential savings across all categories""" rightsizing = sum( i.monthly_cost * (r.get('estimated_savings_pct', 0) / 100) for i in self.instances for r in self.analyze_rightsizing() if r['instance_id'] == i.instance_id ) storage = sum(r.get('estimated_savings', 0) for r in self.analyze_storage()) multi_az = sum(r.get('estimated_savings', 0) for r in self.review_multi_az()) idle = sum(r['monthly_cost'] for r in self.find_idle_resources()) ri_savings = self.analyze_ri_opportunities()['ri_1yr_annual_savings'] / 12 return { 'rightsizing_monthly': rightsizing, 'storage_monthly': storage, 'multi_az_monthly': multi_az, 'idle_resources_monthly': idle, 'reserved_instances_monthly': ri_savings, 'total_monthly': rightsizing + storage + multi_az + idle + ri_savings, 'total_annual': (rightsizing + storage + multi_az + idle + ri_savings) * 12 } # Example usagedef demonstrate_cost_analysis(): instances = [ DatabaseInstance('prod-db-1', 'db.r5.2xlarge', 'postgres', 'us-east-1', True, 'io1', 500, 5000, 1850, 35, 45, 1200, 150, 'production'), DatabaseInstance('prod-db-2', 'db.r5.xlarge', 'postgres', 'us-east-1', True, 'gp3', 200, None, 850, 55, 60, None, 80, 'production'), DatabaseInstance('staging-db', 'db.r5.large', 'postgres', 'us-east-1', True, 'gp2', 100, None, 325, 15, 25, None, 10, 'staging'), DatabaseInstance('dev-db-1', 'db.r5.large', 'postgres', 'us-east-1', False, 'gp2', 50, None, 165, 5, 10, None, 2, 'development'), DatabaseInstance('unused-db', 'db.t3.medium', 'mysql', 'us-east-1', False, 'gp2', 20, None, 85, 1, 5, None, 0, 'development'), ] optimizer = CloudCostOptimizer(instances) analysis = optimizer.analyze_all() print("Cloud Database Cost Optimization Analysis") print("=" * 50) print(f"Total Current Monthly Cost: ${analysis['total_current_cost']:, .0f }") print(f"Estimated Monthly Savings: ${analysis['estimated_savings']['total_monthly']:,.0f}") print(f"Estimated Annual Savings: ${analysis['estimated_savings']['total_annual']:,.0f}") print(f"Savings Percentage: {analysis['estimated_savings']['total_monthly']/analysis['total_current_cost']*100:.1f}%")All major clouds provide cost analysis tools: AWS Cost Explorer with RDS-specific insights, Azure Cost Management, GCP Cost Management. Use these alongside custom analysis. Set up budget alerts to catch cost spikes early.
Licensing Cost Optimization
For commercial databases (Oracle, SQL Server, DB2), licensing often exceeds infrastructure costs. License optimization requires understanding licensing models, auditing current usage, and strategically managing license allocation.
Common licensing models:
| Model | Description | Cost Drivers | Optimization Levers |
|---|---|---|---|
| Per-Core | License per CPU core | Core count, core factor | Reduce cores, use efficient processors |
| Per-User (Named) | License per specific user | User count | Audit actual usage, reassign unused |
| Per-User (Concurrent) | License per simultaneous user | Peak concurrent users | Connection pooling, off-peak processing |
| Per-Instance | License per database instance | Instance count | Consolidation on fewer instances |
| Capacity/Data Volume | License by stored data | Data volume | Archival, compression, partitioning |
| Subscription (SaaS) | Monthly/annual per-use | Usage metrics | Match tier to actual usage |
- •Edition Downgrade — Standard edition often suffices where Enterprise is deployed. Review feature usage before renewing. Partitioning, encryption, and advanced HA may not be used.
- •Core Reduction — For per-core licensing, fewer faster cores may be cheaper than more slower cores. Virtualization settings affect license requirements.
- •Consolidation — Combine multiple small databases onto shared infrastructure. Reduces per-instance license counts.
- •License-Included Cloud — Cloud managed services often include licenses in pricing. May be cheaper than BYOL for some workloads.
- •Open Source Migration — For suitable workloads, migrating from commercial to PostgreSQL/MySQL eliminates license costs entirely.
- •Hybrid Licensing — Use commercial databases only where required (legacy applications, specific features). New development on open source.
- •Contract Negotiation — Large deployments have negotiation leverage. Multi-year commitments, consolidation across business units, and competitive pressure drive discounts.
Under-licensing creates audit risk with significant financial and legal exposure. Maintain accurate license inventories, understand virtualization multipliers, and consider license compliance tools. The cheapest license is one that exists but isn't needed—audit before optimizing.
Storage Cost Optimization
Storage costs grow with data volume, making storage optimization increasingly important as databases mature. Strategies range from technical compression to policy-based data lifecycle management.
- •Compression — Table and page compression reduce storage 50-80% with modest CPU overhead
- •Data Type Optimization — Use appropriate types (INT vs BIGINT, VARCHAR(100) vs TEXT)
- •Index Optimization — Remove unused/duplicate indexes; covering indexes may be smaller than tables
- •Backup Compression — Compressed backups reduce storage 60-80%
- •Log Management — Aggressive log shipping with shorter retention
- •Data Archival — Move old data to cheaper storage or offline archives
- •Retention Policies — Define and enforce data retention limits
- •Partitioning — Enable efficient archival of old partitions
- •Tiered Storage — Hot data on fast storage, warm/cold on cheaper tiers
- •Data Masking — Non-prod may not need full data copies
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
-- Storage optimization opportunity analysis --Identify tables by size and growth for archival candidatesWITH table_sizes AS( SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS total_size, pg_total_relation_size(schemaname || '.' || tablename) AS size_bytes, n_live_tup AS row_count, COALESCE(last_vacuum, last_autovacuum) AS last_maintenance FROM pg_stat_user_tables), table_analysis AS( SELECT schemaname, tablename, total_size, size_bytes, row_count, ROUND(size_bytes:: numeric / NULLIF(row_count, 0), 0) AS bytes_per_row, 100.0 * size_bytes / SUM(size_bytes) OVER() AS pct_total_storage FROM table_sizes ) SELECT schemaname || '.' || tablename AS table_name, total_size, row_count, bytes_per_row || ' bytes/row' AS row_efficiency, ROUND(pct_total_storage, 1) || '%' AS pct_storage, CASE WHEN row_count > 10000000 THEN 'Consider partitioning/archival' WHEN bytes_per_row > 2000 THEN 'Review large columns' WHEN pct_total_storage > 20 THEN 'Major storage consumer - optimize first' ELSE 'Normal' END AS recommendationFROM table_analysisORDER BY size_bytes DESCLIMIT 20; --Compression opportunity analysis(PostgreSQL with pg_column_compression) SELECT schemaname || '.' || tablename AS table_name, pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS current_size, --Estimate compressed size based on typical compression ratios pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename) * 0.4) AS estimated_compressed, pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename) * 0.6) AS estimated_savingsFROM pg_stat_user_tablesWHERE pg_total_relation_size(schemaname || '.' || tablename) > 1073741824 -- > 1GBORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC; --Unused index detection(candidates for removal) SELECT schemaname || '.' || indexrelname AS index_name, schemaname || '.' || relname AS table_name, pg_size_pretty(pg_relation_size(indexrelid)) AS index_size, idx_scan AS times_used, idx_tup_read AS tuples_read, CASE WHEN idx_scan = 0 THEN 'UNUSED - Safe to drop' WHEN idx_scan < 100 THEN 'RARELY USED - Review necessity' ELSE 'ACTIVE' END AS recommendationFROM pg_stat_user_indexesWHERE NOT indexrelname LIKE '%_pkey' -- Exclude primary keysORDER BY pg_relation_size(indexrelid) DESCLIMIT 20; --Duplicte index detection WITH index_columns AS( SELECT i.indexrelid, i.indrelid, i.indexrelid:: regclass AS index_name, i.indrelid:: regclass AS table_name, array_agg(a.attname ORDER BY ord) AS columns, pg_relation_size(i.indexrelid) AS index_size FROM pg_index i JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS c(attnum, ord) ON true JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = c.attnum GROUP BY i.indexrelid, i.indrelid ) SELECT a.table_name, a.index_name AS index_to_drop, pg_size_pretty(a.index_size) AS index_size, b.index_name AS superseding_index, a.columns AS dropped_columns, b.columns AS kept_columnsFROM index_columns aJOIN index_columns b ON a.indrelid = b.indrelid AND a.indexrelid != b.indexrelid AND a.columns < @b.columns-- a's columns are subset of b's AND a.index_size <= b.index_sizeORDER BY a.index_size DESCLIMIT 10; The top 5-10 largest tables typically account for 60-80% of storage. Focus optimization efforts there first. A 50% reduction on a 500GB table saves more than 90% reduction on a 10GB table.
Operational Efficiency
Labor costs—salaries, training, opportunity cost—often exceed infrastructure costs. Improving operational efficiency through automation, standardization, and self-service reduces the human cost of database operations.
- •Infrastructure as Code — Terraform, Ansible, CloudFormation for reproducible, auditable provisioning. Eliminates manual setup time and configuration drift.
- •Automated Maintenance — Scheduled jobs for vacuuming, reindexing, statistics updates. Reduces reactive work from neglected maintenance.
- •Self-Service Provisioning — Developer portals for database creation/cloning within guardrails. Reduces DBA-as-bottleneck toil.
- •Standardization — Consistent configurations, naming conventions, and patterns. Reduces decision fatigue and speeds troubleshooting.
- •Runbooks and Documentation — Documented procedures for common operations. Enables junior team members and reduces tribal knowledge dependency.
- •Proactive Monitoring — Alerting on leading indicators prevents reactive firefighting. Fix problems before they cause outages.
- •Database-as-a-Service — Managed services shift operational burden to provider. Trade control for reduced labor requirement.
Automation ROI calculation:
| Task | Manual Time/Week | Automation Effort | Ongoing Time | Annual Time Saved | ROI Payback |
|---|---|---|---|---|---|
| Database provisioning | 4 hours | 40 hours | 0.5 hours | 180 hours | 2 months |
| Backup verification | 2 hours | 20 hours | 0.1 hours | 98 hours | 2 months |
| Performance monitoring | 5 hours | 60 hours | 0.5 hours | 232 hours | 3 months |
| Schema migrations | 3 hours | 50 hours | 0.5 hours | 130 hours | 4 months |
| User management | 2 hours | 30 hours | 0.2 hours | 93 hours | 3 months |
| Capacity reporting | 3 hours | 25 hours | 0.3 hours | 140 hours | 2 months |
Focus automation on repetitive, well-defined tasks (toil) rather than complex judgement-requiring work. Provisioning, backups, and routine maintenance are automation sweet spots. Complex debugging and architecture decisions remain human work.
Building a Cost Governance Framework
Sustainable cost optimization requires ongoing governance, not one-time efforts. A cost governance framework institutionalizes cost awareness and creates accountability for efficient resource use.
- •Cost Visibility — Teams see their actual database costs. Cost allocation by team, project, and environment. Monthly cost reports with trend analysis.
- •Budget Allocation — Define database budget per team/project. Approval process for exceeding budget. Incentives for staying under budget.
- •Resource Tagging — Mandatory tags for owner, project, environment, cost center. Enables accurate allocation and identifies orphaned resources.
- •Provisioning Guardrails — Default resource limits by environment. Non-prod capped at appropriate sizes. Approval required for expensive configurations.
- •Regular Reviews — Monthly cost review meetings. Quarterly optimization initiatives. Annual capacity and budget planning.
- •Cost Anomaly Alerting — Automated alerts for spending spikes. Early detection prevents surprise bills.
- •Optimization Incentives — Teams keep portion of savings for other priorities. Recognize and reward efficient resource use.
Cost allocation tagging strategy:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
# Database Cost Allocation Tagging Strategy# Tags applied to all database resources for accurate cost attribution required_tags: # Ownership and accountability - name: owner description: "Team or individual responsible for this resource" valid_values: ["platform-team", "payments-team", "search-team", "analytics-team"] - name: cost_center description: "Finance cost center for chargeback" pattern: "^CC-[0-9]{6}$" - name: project description: "Project or product this supports" examples: ["checkout-v2", "search-reindex", "core-platform"] - name: environment description: "Deployment environment" valid_values: ["production", "staging", "development", "testing"] # Environment - based defaults and limits environment_policies: production: multi_az: required backup_retention_days: 35 max_auto_approval_cost: null # All production requires approval staging: multi_az: optional backup_retention_days: 7 max_auto_approval_cost: 500 # Per month development: multi_az: disabled backup_retention_days: 3 max_auto_approval_cost: 200 # Per month auto_shutdown: enabled: true schedule: "weekdays 8am-8pm" # Only run during business hours testing: multi_az: disabled backup_retention_days: 1 max_auto_approval_cost: 100 # Per month lifetime_limit_hours: 72 # Auto - delete after 72 hours # Cost reporting configuration cost_reports: daily: recipients: ["dba-team@company.com"] format: summary threshold_alert: 10 % # Alert if daily spend 10 % + over average weekly: recipients: ["engineering-leads@company.com"] format: detailed_by_team include_optimization_recommendations: true monthly: recipients: ["engineering-all@company.com", "finance@company.com"] format: executive_summary include_trends: true include_forecast: true # Anomaly detection anomaly_alerts: - condition: "daily_cost > 2x 30day_average" severity: high notify: ["dba-oncall", "owner"] - condition: "new_resource_cost > $500/month" severity: medium notify: ["owner", "finance-approvers"] - condition: "resource_idle > 7_days" severity: low notify: ["owner"] action: auto_stop_if_non_productionOne of the most effective cost governance tools is simply making costs visible. When teams see their actual database spend, they naturally optimize. Publish dashboards showing cost by team/project updated daily.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.