Data Center Cost Optimization

Optimize costs for on-premises and colocation infrastructure. Covers power usage effectiveness, hardware lifecycle management, capacity planning, hybrid cloud economics, and the total cost of ownership model that compares cloud vs data center.

Data center cost optimization is the disciplined practice of reducing the total cost of owning and operating physical server infrastructure, including power, cooling, space, hardware, and operations. It matters when capital expenditure (CapEx) and operational expenditure (OpEx) for on-premises infrastructure dominate the technology budget, and when workloads are stable or predictable enough to justify long-term investment in hardware and facilities.

Right-Sizing Hardware and Workload Placement

Optimizing data center costs begins with matching hardware capacity to actual workload demands. The goal is to avoid over-provisioning — buying servers that sit underutilized — and under-provisioning — causing performance bottlenecks during peak load.

Use lshw to enumerate hardware configurations across all servers in a cluster:

lshw -json -class system -class memory -class cpu -class disk

This produces a JSON output with product, vendor, serial, memory, clock, and bus info fields. Collect this data across all servers and compare across clusters. A server with memory.size of 128 GiB and cpu.clock of 2.4 GHz might be overkill for a web application that averages 40% CPU and 35% memory utilization.

For workload placement, use cgroups to isolate and monitor resource consumption per service. On a systemd-managed server, define a cgroup for each service:


[Unit]
Description=My Application
After=network.target

[Service]
ExecStart=/opt/myapp/bin/run.sh
Restart=always
CPUAccounting=yes
MemoryAccounting=yes
TasksAccounting=yes
Slice=app.slice

[Install]
WantedBy=multi-user.target

Then, monitor resource usage with:

systemctl status myapp.service
systemd-cgtop

The CPUUsage and MemoryCurrent values in systemctl status are critical for identifying over-provisioned services. A service showing CPUUsage=1.2s over 10 minutes but MemoryCurrent=2.1GiB with MemoryLimit=4GiB indicates that 50% of memory is wasted.

When a service consistently uses less than 50% of its allocated resources, consider migrating it to a smaller server or consolidating multiple services into a single machine. Use docker inspect to collect resource metrics:

docker inspect --format='{{.Config.Image}}: {{.HostConfig.Memory}}B, {{.HostConfig.NanoCPUs}} CPU' myapp-container

The key insight: a service with 150% CPU utilization and 75% memory utilization is a candidate for vertical scaling (increasing CPU and memory), while one with 80% CPU and 30% memory suggests room for horizontal scaling (adding more instances).

Power and Cooling Efficiency

Data centers consume significant energy not just for computing, but for cooling and power delivery. Optimize by measuring and acting on power usage effectiveness (PUE), defined as total facility power divided by IT equipment power.

Use ipmitool to extract power and thermal telemetry from servers:

ipmitool sdr type "Temperature"
ipmitool sdr type "Power Supply"
ipmitool sdr type "Fan"

Each sensor returns Name, Value, State, and Unit. A sensor with Name="Ambient Temp", Value=22.1, Unit="Celsius" indicates that the ambient temperature is stable. If Value varies by more than 5°C over 24 hours, consider adjusting cooling systems.

For power monitoring, collect data from the ipmi interface and store it in a time-series database using influxdb:

# Collect sensor data every 10 minutes
while true; do
  ipmitool sdr type "Temperature" | \
    grep -E "(^| )Value:" | \
    awk '{print "sensors,host="$1" value="$3" " strftime("%s")}' | \
    curl -X POST -H "Content-Type: text/plain" --data-binary @- http://influxdb:8086/write?db=dc_metrics
  sleep 600
done

This writes to the sensors measurement with host tag and value field. Use flux queries to detect patterns:

from(bucket: "dc_metrics")
  |> range(start: -1w)
  |> filter(fn: (r) => r._measurement == "sensors" and r._field == "value")
  |> filter(fn: (r) => r.host == "web-01")
  |> aggregateWindow(every: 1h, fn: mean, createEmpty: true)
  |> yield(name: "hourly_avg")

When average temperature exceeds 25°C for three consecutive days, initiate a cooling adjustment. Use pkill to restart a cooling daemon:

pkill -SIGHUP cooling-daemon

The SIGHUP signal triggers a reload of configuration and a recalibration of fan speeds based on current temperature profiles.

Storage Tiering and Data Lifecycle Management

Data storage costs are often underestimated. A common failure is placing all data on high-performance SSDs, even when access patterns suggest a multi-tier strategy.

Use zpool list and zfs list -t all to analyze storage usage and I/O patterns. A typical ZFS pool might show:

$ zpool list -o name,alloc,free,capacity
NAME      ALLOC  FREE  CAPACITY
data      8.4T   2.1T  80%
backup    1.2T   3.8T  24%

Analyze access patterns with zfs iostat:

zfs iostat -c 10 5

This shows read/write bandwidth per dataset. A dataset with read 1.2 MiB/s, write 0.3 MiB/s, and avg latency 1.8 ms is ideal for SSDs. One with read 600 KiB/s, write 100 KiB/s, and avg latency 4.2 ms is suitable for SATA drives.

Use zfs send and zfs receive to implement a tiered backup strategy:

# Create a snapshot daily
zfs snapshot tank/data@$(date +%Y-%m-%d)
# Send incremental updates to backup pool
zfs send -i tank/data@2023-10-01 tank/data@2023-10-02 | \
  zfs receive -F tank/backup

The -F flag forces overwrite, ensuring that the backup is up-to-date and consistent.

For cold data, define a retention policy using zfs send -I to generate incremental backups from base snapshots. Then, use rsync to stage data to archival storage:

rsync -av --delete /mnt/archive/ user@cold-storage:/data/archive/

The --delete flag removes files no longer present in the source, preventing bloated archives.

The subtle failure mode: forgetting to zfs promote a dataset after a zfs receive. Without promotion, the dataset remains a clone and does not benefit from local caching, resulting in high latency for cold data.

Server Lifecycle and Capital Optimization

Servers are the largest fixed cost in a data center. Optimize through planned replacement cycles and efficient procurement.

Use dmidecode to extract hardware lifecycle data from servers:

dmidecode -t system
dmidecode -t baseboard
dmidecode -t bios

Extract Manufacturer, Product Name, Serial Number, UUID, Firmware Version, and Release Date. Compare Release Date with Current Date to calculate age. A server with Release Date=2019-05-15 and Current Date=2024-03-10 is 4 years and 10 months old.

For procurement, define a lifecycle policy based on hardware age and performance. A server older than 4 years is a candidate for replacement if its CPU utilization is consistently above 70% and memory utilization exceeds 85%.

Use ansible to orchestrate a server replacement campaign:

# deploy.yml
- hosts: all
  become: yes
  vars:
    server_age: 4
    cpu_threshold: 70
    memory_threshold: 85
  tasks:
    - name: Check server age
      command: dmidecode -t system | grep "Release Date"
      register: dmi_output

    - name: Parse release date
      set_fact:
        release_date: "{{ dmi_output.stdout | regex_search('Release Date: (.+)', capture=True) | first }}"

    - name: Convert to ISO date
      set_fact:
        iso_date: "{{ release_date | regex_replace('-', '/') | regex_replace('^(.+)/(\\d{1,2})/(\\d{4})$', '\\3-\\2-\\1') }}"

    - name: Calculate age in years
      set_fact:
        age_in_years: "{{ (ansible_date_time.iso8601 | date('Y')) - (iso_date | date('Y')) }}"

    - name: Mark servers for replacement
      copy:
        content: "{{ item }} is due for replacement.\n"
        dest: /var/log/replacement-candidates.txt
      loop: "{{ (age_in_years | int) > server_age | list }}"
      when: age_in_years > server_age

This playbook identifies servers older than four years and logs them for replacement. A server with age_in_years=5 and cpu_utilization=74% is a high-priority candidate.

When replacing a server, ensure data is fully migrated using rsync with --inplace and --partial:

rsync -av --inplace --partial --delete-before \
  --progress /data/ user@new-server:/data/

The --inplace flag updates files directly in place, reducing downtime. --partial preserves partially transferred files, allowing resumption after interruptions.

Automation and Monitoring

Automate cost optimization tasks with systemd timers and cron.

Define a daily task to analyze hardware utilization:

# /etc/systemd/system/hw-analysis.service
[Unit]
Description=Analyze server hardware utilization
After=network.target

[Service]
Type=oneshot
ExecStart=/opt/scripts/hw-analysis.sh
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Attach a timer:

# /etc/systemd/system/hw-analysis.timer
[Unit]
Description=Run hardware analysis daily
Requires=hw-analysis.service

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Enable with:

systemctl enable hw-analysis.timer
systemctl start hw-analysis.timer

The Persistent=true flag ensures that if the system is down during the scheduled time, the task runs as soon as it comes up.

Use journalctl to monitor the output:

journalctl -u hw-analysis.service -f

A common failure: missing StandardOutput=journal and StandardError=journal, which causes the service output to be lost. The ExecStart command must write to stdout and stderr explicitly.

For long-term tracking, use psql to maintain a cost optimization log:

CREATE TABLE data_center_optimization (
  id SERIAL PRIMARY KEY,
  task TEXT NOT NULL,
  server TEXT,
  start_time TIMESTAMP WITH TIME ZONE,
  end_time TIMESTAMP WITH TIME ZONE,
  status TEXT,
  notes TEXT
);

INSERT INTO data_center_optimization (task, server, start_time, end_time, status, notes)
VALUES (
  'Replace server web-01',
  'web-01',
  '2024-03-10 08:00:00+00',
  '2024-03-10 14:30:00+00',
  'completed',
  'Replaced 16-core, 64GB server with new 24-core, 128GB model.'
);

This table becomes the source of truth for all optimization efforts.

Common Failure Modes

These failures are subtle but common: they go unnoticed until a cost optimization initiative fails to deliver expected savings.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.