System Resource & I/O Optimization Questions
Tuning how a system uses CPU, memory, disk, and network at the OS and I/O layer. Covers I/O throughput and blocking, buffering and batching, filesystem and kernel-level performance settings, and resource contention between processes. Includes OS-level performance tuning and diagnosing resource saturation on the host.
HardSystem Design
49 practiced
Hard: You need to implement a safety net in systemd for services that may leak memory over time: an automatic, staged restart strategy that restarts a service before it reaches critical memory usage while preserving in-flight requests where possible. Describe unit settings, helper scripts, and how you'd detect approaching thresholds safely.
Sample Answer
Requirements clarification:- Soft restart before critical memory use to avoid OOMs- Preserve in-flight requests where possible (graceful drain)- Automated, staged escalation with safety/hysteresis- Integrates with systemd unit lifecycle and service discovery/load‑balancerHigh-level design:- systemd unit has resource controls (MemoryHigh/MemoryMax), graceful stop timeouts and Restart=.- A helper watchdog service polls cgroup memory metrics and triggers a staged orchestration script when thresholds are approached.- Orchestration: mark instance draining in LB/SD, wait for inflight to drop (or wait timeout), send graceful SIGTERM, wait for StopTimeout, force kill if needed, restart unit, clear drain state. Use hysteresis and sustained-sample checks.Example systemd unit additions (snippet to include in service .service):[Service]Restart=on-failureRestartSec=10TimeoutStopSec=60 # allow graceful shutdownKillMode=mixedMemoryHigh=800M # kernel throttles/notifications start hereMemoryMax=1G # hard cap (optional, avoid abrupt kills unless intended)OOMScoreAdjust=-100 # deprioritize OOM killer if needed# Watchdog service unit (runs helper script)[Unit]Requires=yourservice.serviceAfter=yourservice.service[Service]Type=simpleExecStart=/usr/local/bin/memory_watchdog.sh yourserviceRestart=alwaysRestartSec=30Helper script (pseudocode, robust safety features):Detection safety and robustness:- Use cgroup v2 metrics (memory.current, memory.high) when available — more accurate than /proc per‑PID for multi-process services.- Use sustained-threshold (N consecutive checks) and moving averages to avoid flapping.- Add hysteresis: only re-enable normal operation when memory drops well below threshold (e.g., 50%) for M checks.- Rate-limit restarts (e.g., systemd StartLimitBurst / StartLimitIntervalSec) to avoid crash loops.- Use health checks: after restart, validate service via HTTP/GRPC probe before marking ready.- Prefer graceful draining via LB/SD APIs rather than dropping traffic; if no LB, comply with the service’s own graceful shutdown semantics.Failure modes & mitigations:- Restart fails repeatedly: systemd StartLimit to escalate alerting instead of endless restarts.- Rapid leak bypasses detection: set MemoryMax to guarantee cgroup-enforced bounds (if acceptable).- Watchdog crash: run under systemd with Restart=always and alert on watchdog service failures.Operational notes:- Expose tuning via unit drop-in or templated systemd unit per service.- Log all watchdog actions centrally and emit Prometheus metrics: current memory, threshold crossings, restarts, drain durations.- Test in staging with production-like traffic; simulate leaks and verify in-flight preservation.This approach gives a safe, automated, observable, and tunable safety net that prefers graceful draining and restart while preventing abrupt OOM kill and preserving availability.
bash
#!/bin/bash
svc=$1
threshold_pct=75
sustain_checks=3
check_interval=10
drain_timeout=60
# get cgroup path (systemd-run style)
while true; do
pid=$(systemctl show -p MainPID --value $svc)
[ "$pid" -gt 0 ] || { sleep $check_interval; continue; }
mem_bytes=$(cat /sys/fs/cgroup/system.slice/${svc}.service/memory.current 2>/dev/null || \
awk '/VmRSS/ {print $2*1024}' /proc/$pid/status)
mem_max=$(cat /sys/fs/cgroup/system.slice/${svc}.service/memory.max 2>/dev/null || echo 0)
if [ "$mem_max" -gt 0 ]; then
pct=$((100 * mem_bytes / mem_max))
else
# derive sensible limit or use physical memory
pct=$((100 * mem_bytes / (1024*1024*1024) )) # assume 1GB baseline or fetch from config
fi
if [ "$pct" -ge "$threshold_pct" ]; then
hits=$((hits+1)); else hits=0; fi
if [ "$hits" -ge "$sustain_checks" ]; then
# Stage 1: mark draining (LB / Consul / pacemaker API)
curl -s -X POST http://lb-api/drain/$(hostname) || true
# wait for inflight to drop or timeout
start=$(date +%s)
while in_flight_requests_above_zero && [ $(( $(date +%s)-start )) -lt $drain_timeout ]; do sleep 2; done
# Stage 2: graceful restart
systemctl kill --signal=SIGTERM $svc
# Wait for stop; systemd will restart due to Restart=on-failure
# If service doesn't stop, escalate after TimeoutStopSec
# Stage 3: clear drain after service active and healthy
sleep 5
curl -s -X POST http://lb-api/ready/$(hostname) || true
hits=0
fi
sleep $check_interval
doneMediumTechnical
28 practiced
Medium: A service under systemd sometimes fails to bind to a TCP port on startup with 'Address already in use' only when restarted quickly. How would you change the unit file and application to reduce the likelihood of this race? Explain TIME_WAIT implications and a system-level mitigation.
Sample Answer
Clarify the problem: the service sometimes fails to bind with "Address already in use" only when restarted quickly — this is a classic race between the old socket/connection cleanup and the new process trying to bind.Short-term/systemd changes- Use socket activation: move the TCP listen socket into a .socket unit (Accept=no) and have the service be a .service triggered by that socket. systemd will keep the socket open across service restarts so the kernel owned listening FD isn’t closed between stop/start, eliminating the bind race.- If socket activation isn't possible, add a small restart delay in the unit to reduce race likelihood: - Restart=on-failure (or always as appropriate) - RestartSec=1 (or a tuned value) - TimeoutStopSec and KillMode=control-group to allow graceful shutdown before restartApplication changes- Use proper socket options: - Set SO_REUSEADDR before bind. This allows binding to an address in TIME_WAIT on many platforms. - Consider SO_REUSEPORT if you want multiple processes to bind the same port for load distribution (and the kernel supports it).- Implement graceful shutdown: - Stop accepting new connections, call shutdown() on the listening socket, let existing connections drain, then close. This reduces active closes that create TIME_WAIT state.- Ensure correct close semantics: prefer the server to close the client side when appropriate so TIME_WAITs are on the client side where possible.TIME_WAIT implications (why this happens)- TIME_WAIT is a TCP state that persists after a socket is closed to ensure late packets are discarded and to allow proper retransmission handling. Sockets in TIME_WAIT can prevent reconnection semantics in some OSes if bind semantics require exclusive address ownership.- Typically, TIME_WAIT consumes ephemeral port table entries and can block re-binding in certain server teardown/start patterns; it is not a bug but a TCP safety mechanism.System-level mitigation (use with caution)- Avoid tcp_tw_recycle (deprecated/incompatible with NAT). tcp_tw_reuse can be enabled (net.ipv4.tcp_tw_reuse=1) to allow reusing TIME_WAIT sockets for new outgoing connections; it doesn’t directly help server bind issues and carries trade-offs.- Increase ephemeral port range or reduce TIME_WAIT timeout via net.ipv4.tcp_fin_timeout only as a last resort — these change global TCP behavior and can hide problems.- Prefer socket activation and application-level fixes over kernel tuning.Trade-offs & summary- Best practice: use systemd socket activation to remove bind races entirely. Combine with SO_REUSEADDR and proper graceful shutdown in the app. Kernel tunables are risky and only for last-resort, cluster-wide tuning. This approach minimizes downtime and preserves correct TCP semantics.
EasyTechnical
37 practiced
Explain what the page cache is and how the Linux kernel uses it. Describe how you would tell if a workload is benefiting from page cache and one way to reduce memory pressure caused by page cache.
Sample Answer
The page cache is kernel-managed RAM that caches file data (pages) read from block devices so future reads hit memory instead of disk. The kernel stores file-backed pages in the page cache and writes dirty pages back asynchronously; this improves throughput and reduces latency for I/O-heavy workloads.How Linux uses it:- On read, the kernel checks page cache -> if present (cache hit) it serves from RAM; on miss it reads from disk and populates the cache.- Writes go to page cache as dirty pages, flushed by pdflush/flush workers or writeback.- The kernel reclaims page-cache pages under memory pressure before killing processes.How to tell if a workload benefits:- Observe high Cached value and low swap/major faults while the workload runs. Commands: - cat /proc/meminfo | egrep 'Cached|MemFree|Buffers' - cat /proc/vmstat | egrep 'pgfault|pgmajfault|pgpgin|pgpgout' - vmstat 1 — watch low si/so and increasing cache hits; iostat -x to see decreased device read throughput.- Evidence of benefits: workload read latency decreases, fewer disk reads (lower reads/sec) while Cached rises; low page faults and low pgmajfaults.One way to reduce memory pressure from page cache:- Tune vfs_cache_pressure to prefer keeping inode/dentry vs. reclaiming page cache: echo 50 > /proc/sys/vm/vfs_cache_pressure (lower keeps cache longer). For per-workload control, use O_DIRECT or direct I/O (or fsync patterns) to bypass the page cache, or adjust application readahead. Note: dropping caches (echo 3 > /proc/sys/vm/drop_caches) is disruptive—use only for testing.
HardTechnical
26 practiced
Hard: You find a process consuming large amounts of file descriptors and causing EMFILE errors globally. Propose a plan to debug where descriptors are coming from, short-term mitigations to restore service, and long-term design changes to prevent descriptor leaks. Include commands to gather evidence.
Sample Answer
Situation: Production hosts are hitting EMFILE (too many open files) and services failing. My goals: identify which process(es) hold many FDs, stop global impact quickly, then eliminate root cause and prevent recurrence.Debug plan (gather evidence)- Find top FD consumers across system: lsof -nP | awk '{print $2}' | sort | uniq -c | sort -nr | head find /proc -maxdepth 2 -name fd -type d -exec bash -c 'echo {}; ls -1 {} | wc -l' \; # quick per-process fd counts- Inspect specific candidate PID: ls -l /proc/<PID>/fd | wc -l lsof -p <PID> -nP # shows filenames, sockets, pipes lsof -p <PID> -a -i -nP # network fds ss -tanp | grep <PID> # TCP sockets ss -xap | grep <PID> # unix sockets- Map FDs to files/sockets to see leak pattern (many unique files, repeated same file, many ephemeral sockets).- If uncertain, attach temporarily: strace -p <PID> -e trace=file,desc -f -s 200 # watch opens/creat/close / or use eBPF (bcc) tools: opensnoop, execsnoop to trace opens over time.- Record ulimits and system-wide counts: ulimit -n cat /proc/sys/fs/file-nr ss -s # socket summary- Capture timeline: timestamped snapshots of ls -l /proc/*/fd to show growth.Short-term mitigations (restore service fast)- Throttle/stop offending process gracefully: - Try to trigger graceful restart that closes FDs (service reload/restart). systemctl restart <service> # prefer graceful first- If restart not feasible, limit process FD usage temporarily: prlimit --pid <PID> --nofile=<newsoft>:<hard> (use cautiously)- Reclaim FDs by restarting worker processes or scaling horizontally behind load-balancer.- If global exhaustion persists, raise kernel limits temporarily: sysctl -w fs.file-max=<higher> and increase per-process ulimit in emergency, but only as stopgap.- If sockets are idle, identify and close idle connections at LB or kill zombie client processes.- Put circuit breakers / maintenance mode to prevent client storms.Long-term fixes (prevent recurrence)- Add FD monitoring and alerts: - Export per-process FD metric (e.g., node_exporter process_open_fds or custom exporter); alert on > threshold or rate of increase.- Harden service config: - Set appropriate systemd LimitNOFILE and Restart=on-failure; enforce sane ulimits via PAM/systemd.- Fix code leaks: - Audit code paths to ensure close() on files, sockets, DB connections; add tests for resource cleanup. - Use connection pools with bounded sizes and timeouts; ensure proper finally/with patterns in languages.- Add stress/soak tests in CI that track FD usage over time and fail on leak patterns.- Improve architecture: - Break large monolith into bounded services or worker pools so leaks impact fewer processes. - Use backpressure and rate-limiting to prevent client floods from causing runaway FDs.- Observability & postmortem: - Record root cause, timeline, and implement runbook: how to gather /proc/*/fd, how to restart safely, who to page.Why these steps:- Immediate evidence collection pinpoints whether leaks are files, sockets, or pipes and whether leak is linear (opens without closes) vs burst (client flood). Short-term mitigations restore service quickly without masking root cause. Long-term controls combine monitoring, code fixes, and architectural limits to prevent recurrence.
MediumTechnical
37 practiced
Write a short Python script that parses the output of peticularly formatted 'vmstat 1 5' (5 samples) from stdin and prints the maximum 'si' and 'so' columns observed and the timestamp of that sample. Explain how you would run it safely on a production server without interfering with performance.
Sample Answer
Approach: read stdin, find the header row that contains column names (look for "si" and "so"), record their column indices, then parse subsequent sample lines (skip repeating headers), extract timestamp (if present in header as "time" or first column), track the maximum si/so and the timestamp when each maximum occurred. Print both maxima and their timestamps.Key points:- Dynamically find column indices from header so script tolerates column order.- Handles optional timestamp column (e.g., produced by vmstat -t or custom wrapper).- Time complexity O(n), space O(1).Edge cases:- Missing header: script ignores until header found.- Non-numeric fields: skipped.- Ties: first occurrence preserved.Running safely on production:- Prefer running on a monitoring/maintenance host, or use existing agents (Prometheus node_exporter, Telegraf) instead of ad-hoc vmstat runs.- If you must run it on a prod box: run vmstat with low priority (nice +10, ionice -c2), limit frequency and count (vmstat 1 5), run in screen/at/cron during low-load windows, redirect output to a file and parse offline. Avoid scripts that spawn frequently; integrate into monitoring pipeline and alerting rather than manual polling.
python
#!/usr/bin/env python3
import sys
def parse_vmstat(stream):
headers = None
si_idx = so_idx = time_idx = None
max_si = {'val': None, 'time': None}
max_so = {'val': None, 'time': None}
for line in stream:
line = line.strip()
if not line:
continue
parts = line.split()
# detect header line (contains 'si' and 'so')
if 'si' in parts and 'so' in parts:
headers = parts
si_idx = parts.index('si')
so_idx = parts.index('so')
# optional timestamp header name (common: 'time' or 'timestamp' or first column not numeric)
if parts[0].lower() in ('time', 'timestamp'):
time_idx = 0
continue
# skip kernel repeated header lines or summary lines
if headers is None:
continue
# if there's an extra timestamp column before standard columns
# detect if first token looks like hh:mm:ss
if time_idx is None:
if ':' in parts[0]:
time_idx = 0
# shift indices right by 1 if header didn't include time
# assume header had no time column -> si_idx/so_idx correspond to parts without time
# so adjust using length difference
# if parts have same length as headers+1, shift
if len(parts) == len(headers) + 1:
si_idx += 1
so_idx += 1
try:
si = int(parts[si_idx])
so = int(parts[so_idx])
except (IndexError, ValueError):
continue
ts = parts[time_idx] if time_idx is not None else None
if max_si['val'] is None or si > max_si['val']:
max_si.update(val=si, time=ts)
if max_so['val'] is None or so > max_so['val']:
max_so.update(val=so, time=ts)
return max_si, max_so
if __name__ == '__main__':
max_si, max_so = parse_vmstat(sys.stdin)
print(f"max si: {max_si['val']} at {max_si['time']}")
print(f"max so: {max_so['val']} at {max_so['time']}")Unlock Full Question Bank
Get access to hundreds of System Resource & I/O Optimization interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.