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.
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).
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.
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.
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.
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.
zfs send command fails silently when the destination dataset is not mounted. Use zfs mount before zfs receive.ipmitool returns Error: Invalid command when the BMC is unreachable. Check ipmitool lan print for IP configuration.ansible tasks run but the when condition is ignored due to missing vars or incorrect loop formatting.systemd timers miss their schedule if SystemClock is not synchronized with systemd-timesyncd.rsync does not preserve file attributes when --archive is omitted. Use --archive or --a for consistency.dmidecode output includes multiple Release Date lines. Use grep -A1 "Release Date" to extract the correct value.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.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy