Prioridades de proceso: nice, renice e ionice

El nice value es un entero que el kernel usa como pista para el scheduler CFS (Completely Fair Scheduler) a la hora de repartir tiempo de CPU entre procesos. El rango va de -20 (prioridad máxima) a +19 (prioridad mínima), con 0 como valor por defecto. La terminología es contraintuitiva a propósito: un proceso “nice” (amable) con el resto del sistema tiene un valor alto y recibe menos CPU; uno egoísta tiene valor negativo. Solo root puede asignar valores negativos, porque elevar la prioridad de un proceso puede degradar la respuesta de todo el sistema.

El CFS no trabaja con prioridades absolutas sino con pesos relativos. Internamente convierte el nice value a un peso mediante una tabla exponencial: cada paso de 1 en el nice value cambia el peso aproximadamente un 25%. La diferencia entre nice 0 y nice 10 no es “recibe menos CPU cuando hay contención” sino “recibe menos CPU en proporción al peso de todos los demás procesos compitiendo por ese core en ese instante”. En un sistema ocioso, un proceso con nice +19 puede usar el 100% de la CPU sin problema.

¿Cuándo tiene sentido ajustar esto manualmente? Principalmente en máquinas de un solo core o con cores completamente saturados, cuando lanzas una tarea pesada —compilación, backup, transcodificación— que compite con procesos interactivos. En ese contexto, reducir el nice value del proceso pesado garantiza que las peticiones interactivas se despachen antes. En sistemas modernos con muchos cores y systemd gestionando los grupos de control, el impacto práctico es más limitado, pero sigue siendo relevante en workstations de desarrollo o servidores con carga de CPU sostenida.

Si te equivocas aquí, los efectos son sutiles pero reales: lanzar un proceso de compilación con nice 0 en una máquina saturada puede hacer que tu sesión SSH o tu servidor web noten latencia. El error inverso —asignar nice -10 a un proceso de usuario sin necesidad— puede hacer que ese proceso acapare CPU e impacte a servicios críticos. Con ionice el riesgo es mayor: poner un proceso en clase real-time de I/O en un disco giratorio puede literalmente paralizar otros accesos durante segundos.

ionice opera sobre un sistema completamente separado: el scheduler de I/O del kernel (CFQ históricamente, aunque en kernels modernos se usa BFQ o mq-deadline dependiendo del tipo de disco). Las tres clases son: idle (clase 3, solo usa I/O si nadie más lo necesita), best-effort (clase 2, la predeterminada, con niveles 0-7 dentro de la clase), y real-time (clase 1, acceso garantizado al disco, peligrosa en manos equivocadas). Un proceso con ionice -c 3 puede tardar minutos en completar una lectura si el sistema está bajo carga de I/O constante; eso es exactamente lo que quieres para un backup nocturno.

#!/bin/bash
# Script de backup que no debe interferir con el sistema en producción.
# Combina nice (CPU) e ionice (I/O) para ejecutarse completamente en segundo plano.

set -euo pipefail

ORIGEN="/var/lib/postgresql"
DESTINO="/mnt/backup/postgres-$(date +%Y%m%d-%H%M%S).tar.zst"
LOG="/var/log/backup-postgres.log"

echo "[$(date -Iseconds)] Iniciando backup de $ORIGEN" >> "$LOG"

# nice -n 15: reduce prioridad de CPU (peso ~1/6 del default)
# ionice -c 2 -n 7: best-effort, nivel más bajo (7 = mínima prioridad dentro de la clase)
# La combinación asegura que tanto CPU como I/O cedan ante cualquier otro proceso activo.
nice -n 15 ionice -c 2 -n 7 \
    tar --create --file=- "$ORIGEN" \
  | zstd -T2 -3 \
  > "$DESTINO"

echo "[$(date -Iseconds)] Backup completado: $DESTINO" >> "$LOG"

# Verificación: mostrar las prioridades actuales del proceso de backup
# (útil para confirmar en sistemas donde el proceso dura mucho)
SELF_PID=$$
ps -o pid,ni,cls,pri,comm -p "$SELF_PID"

# Si necesitas cambiar la prioridad de un proceso ya en marcha:
# renice +19 -p <PID>        → lleva a mínima prioridad CPU
# ionice -c 3 -p <PID>       → cambia a idle I/O sin reiniciar el proceso

# Para servicios gestionados por systemd, es mejor usar la unidad directamente:
# systemctl set-property postgresql.service CPUWeight=20 IOWeight=20
# Esto persiste y opera a nivel de cgroup, más efectivo que nice en sistemas multicore.

El comando central combina nice -n 15 e ionice -c 2 -n 7 como wrappers anidados antes de tar. nice afecta al proceso y todos sus hijos —incluyendo zstd en la tubería—, porque el nice value se hereda al hacer fork(). ionice con -c 2 -n 7 elige best-effort en lugar de idle (-c 3) deliberadamente: la clase idle en sistemas con BFQ puede provocar que el proceso quede completamente bloqueado durante períodos prolongados si hay cualquier otro proceso haciendo I/O. El nivel 7 dentro de best-effort ya ofrece la prioridad más baja posible con comportamiento predecible.

zstd -T2 limita la compresión a dos threads; sin eso, zstd lanzaría tantos threads como cores físicos y el nice sobre el proceso padre no se propagaría correctamente a todos los threads del pool interno de zstd, porque esos threads los gestiona la librería, no el shell.

La línea con ps -o pid,ni,cls,pri muestra cuatro columnas que conviene distinguir: ni es el nice value ajustado por el usuario, pri es la prioridad final calculada por el kernel (un número diferente, donde valores más altos significan menor prioridad en la representación del kernel), y cls indica la política de scheduling (TS para tiempo compartido normal, FF para FIFO de tiempo real).

renice opera sobre procesos ya en marcha sin reiniciarlos. La restricción importante: un usuario normal puede aumentar el nice value de sus propios procesos (reducir prioridad), pero nunca bajarlo una vez subido, salvo root. Si haces renice +15 -p 1234 como usuario sin privilegios y luego intentas renice 0 -p 1234, el kernel lo rechaza con EPERM.

La nota sobre systemctl set-property no es un detalle menor. En Debian Bookworm con systemd, los servicios viven dentro de cgroups, y el reparto de CPU entre cgroups ocurre antes de que el CFS aplique los nice values individuales. Un proceso con nice -5 dentro de un cgroup con CPUWeight=10 va a recibir menos CPU que un proceso con nice +5 en un cgroup con CPUWeight=1000. El tuning manual con nice sigue siendo válido para procesos lanzados desde el shell fuera del control de systemd, pero para servicios de larga duración, CPUWeight= e IOWeight= en la unit file dan un control más preciso y reproducible.

64

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio