A la lliçó anterior vas trobar un problema de rendiment la causa del qual era un valor de configuració mal ajustat: max_connexions=200 contra un max_connections=100. El vas trobar mesurant, i el vas corregir comprovant-ne l'efecte. Aquesta lliçó fa el mateix un nivell més avall: els valors que ajustaràs són els del nucli.

I comença amb un advertiment, perquè l'ajust del nucli és l'àrea de l'administració de sistemes amb més folklore per metre quadrat. Internet és ple de llistes de paràmetres «per optimitzar Linux» que es copien sense entendre, que contradiuen els valors per defecte sense motiu, i que en el millor dels casos no fan res. La regla que separa la feina del ritual és una de sola:

No s'ajusta el que no s'ha mesurat.

I el procediment que l'aplica, que és el mateix que ja has fet servir dues vegades —a l'incident del web lent de 05-07 i al de la latència de 07-02—:

  1. Mesurar l'estat actual i anotar-lo. Sense línia base no hi ha «després».
  2. Formular una hipòtesi concreta: quin paràmetre, per què creus que importa, i què esperes que canviï.
  3. Canviar una sola cosa. Dos canvis simultanis fan impossible atribuir el resultat.
  4. Mesurar de nou amb el mateix mètode.
  5. Documentar el canvi amb el seu motiu, o revertir-lo. Un paràmetre sense justificació escrita és un paràmetre que ningú no podrà treure d'aquí a un any.

Els valors per defecte d'Ubuntu 24.04 són raonables per a la majoria de les càrregues. Canviar-los sense una mesura que ho avali és, amb força probabilitat, empitjorar el sistema.

Contingut

  1. sysctl: la interfície de paràmetres del nucli
  2. Memòria virtual
  3. Xarxa i pila TCP
  4. Límits de fitxers i processos
  5. Planificador d'E/S
  6. Pàgines enormes transparents
  7. Límits per procés i qui guanya
  8. Governadors de freqüència i tuned
  9. Mòduls del nucli
  10. Compilar el nucli: quan té sentit i quan no
  11. Cas Tramontana: ajustar la pila de xarxa amb mesura

sysctl: la interfície de paràmetres del nucli

El nucli exposa els seus paràmetres ajustables com a fitxers sota /proc/sys/. És el «tot és un fitxer» del Mòdul 1 aplicat a la configuració del nucli: llegir el paràmetre és llegir un fitxer, canviar-lo és escriure-hi.

$ cat /proc/sys/vm/swappiness
60
$ sudo sh -c 'echo 10 > /proc/sys/vm/swappiness'
$ cat /proc/sys/vm/swappiness
10

sysctl és l'eina que fa el mateix amb una sintaxi de punts, on cada punt és una barra de la ruta:

$ sysctl vm.swappiness           # equival a /proc/sys/vm/swappiness
vm.swappiness = 10

$ sysctl -a 2>/dev/null | wc -l
1247

$ sysctl -a 2>/dev/null | grep -c '^net\.'
684

Gairebé mil tres-cents paràmetres, dels quals dos terços són de xarxa. La immensa majoria no es toquen mai.

Els tres modes d'operació, i quin fer servir quan:

Operació Ordre Persisteix en reiniciar
Consultar sysctl <parametre> —
Canviar ara, per provar sudo sysctl -w <parametre>=<valor> No
Canviar permanentment Fitxer a /etc/sysctl.d/ + sysctl --system Sí

Aquell -w efímer és l'eina del pas 3 del procediment: permet provar un canvi, mesurar, i si empitjora les coses n'hi ha prou de reiniciar —o tornar a escriure el valor anterior— per desfer-ho. Mai es posa un paràmetre directament a /etc/sysctl.d/ sense haver-lo provat abans en calent.

La persistència i la seva precedència:

$ ls /etc/sysctl.d/
10-console-messages.conf   10-network-security.conf  60-enfortiment.conf
10-ipv6-privacy.conf       10-ptrace.conf            99-sysctl.conf

$ sudo tee /etc/sysctl.d/70-rendiment.conf >/dev/null <<'EOF'
# Ajustos de rendiment. Cada linia amb el seu motiu i la seva mesura.
vm.swappiness = 10
EOF

$ sudo sysctl --system 2>&1 | grep -A1 70-rendiment
* Applying /etc/sysctl.d/70-rendiment.conf ...
vm.swappiness = 10

Els fitxers s'apliquen en ordre alfabètic, i l'últim a escriure un paràmetre guanya. D'aquí la convenció de numerar-los: 10- per al que ve de la distribució, 60- a 90- per al propi, i 99- per al que s'ha d'imposar a tot. I una nota important: /etc/sysctl.conf continua funcionant, però està en desús; fes servir /etc/sysctl.d/ amb fitxers separats per propòsit.

Fixa't que ja tens 60-enfortiment.conf, de 06-06, amb els paràmetres de seguretat. Els de rendiment van en un fitxer a part, i aquella separació no és estètica: quan algú hagi de revisar la postura de seguretat, o quan calgui revertir un ajust de rendiment, es toca un sol fitxer i no es barreja el que es pot treure amb el que no.

Memòria virtual

El subsistema de memòria virtual és on els ajustos tenen més impacte i on hi ha més mitologia. Els paràmetres que importen:

vm.swappiness

El més mal entès de tots. No és «quant intercanvi fer servir», ni un percentatge de memòria. És la preferència relativa del nucli entre dues maneres d'alliberar memòria quan li'n cal: descartar pàgines de memòria cau de fitxer, o moure pàgines anònimes (memòria de procés) a l'intercanvi.

$ sysctl vm.swappiness
vm.swappiness = 60
Valor Comportament Quan
0 Només fa servir l'intercanvi per evitar l'OOM killer Gairebé mai; pot provocar OOM abans d'hora
1 El mínim possible sense desactivar-lo Bases de dades amb tota la RAM assignada
10 Prefereix descartar memòria cau Servidors amb RAM suficient
60 Per defecte Escriptori i ús general
100 Tracta memòria cau i anònimes igual Càrregues on la memòria cau és més valuosa

I la dada que cal interioritzar abans de tocar-lo: si el sistema no fa servir intercanvi, swappiness no fa res. És un paràmetre que només es manifesta sota pressió de memòria. Abans de canviar-lo, mesura si hi ha pressió:

# La mesura previa: hi ha activitat d intercanvi? (columnes si/so)
$ vmstat 5 4
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 1284412 104882 1841204   0    0     0    12  412  882  3  2 95  0  0
 0  0      0 1284188 104882 1841204   0    0     0     8  388  841  2  2 96  0  0

# I l acumulat des de l arrencada
$ grep -E 'pswpin|pswpout' /proc/vmstat
pswpin 0
pswpout 0

Zero entrades i zero sortides d'intercanvi des de l'arrencada. A srv-tramontana, amb 3,8 GB i 2,4 GB disponibles, swappiness és irrellevant avui. Abaixar-lo no milloraria res.

Val la pena canviar-lo, doncs? Sí, però per un motiu diferent del rendiment mitjà: com a assegurança contra la degradació sota pressió. Si un dia l'aplicació creix i comença a haver-hi pressió, amb swappiness=10 el nucli preferirà descartar memòria cau abans que enviar a l'intercanvi les pàgines actives del procés, cosa que evita l'escenari de latències de centenars de mil·lisegons. És una decisió defensable, i així cal documentar-la: no «això accelera el servidor», sinó «això limita el dany si algun dia falta memòria».

vm.dirty_ratio i vm.dirty_background_ratio

Aquests sí que tenen efecte mesurable, i són la causa d'un patró de latència molt característic.

Quan un procés escriu en un fitxer, les dades van primer a la memòria cau de pàgina i es marquen com a brutes (dirty); el nucli les aboca al disc més tard. Aquests dos paràmetres controlen quan:

$ sysctl vm.dirty_background_ratio vm.dirty_ratio vm.dirty_expire_centisecs
vm.dirty_background_ratio = 10
vm.dirty_ratio = 20
vm.dirty_expire_centisecs = 3000
Paràmetre Què passa en assolir-lo
dirty_background_ratio El nucli comença a abocar en segon pla, sense bloquejar ningú
dirty_ratio El procés que escriu es bloqueja fins que s'aboca prou
dirty_expire_centisecs Antiguitat màxima d'una pàgina bruta abans d'abocar-la (30 s)

El patró que produeixen: escriptures ràpides mentre hi ha marge, i de cop una pausa llarga quan s'assoleix dirty_ratio i el procés queda bloquejat. Amb 3,8 GB de RAM, el 20 % són uns 760 MB de dades brutes que poden haver d'anar-se'n al disc de cop.

Això és exactament el perfil de l'incident de 05-07: la còpia de seguretat escrivint a ràfegues, amb w_await de 22,85 ms i l'aplicació bloquejada. ionice va mitigar el símptoma; aquests paràmetres ataquen el mecanisme.

# Mesura previa: quantes dades brutes hi ha en un moment donat
$ grep -E '^Dirty|^Writeback' /proc/meminfo
Dirty:              1024 kB
Writeback:             0 kB

# I durant la copia, amb el volum xifrat com a desti
$ while true; do grep '^Dirty:' /proc/meminfo; sleep 2; done
Dirty:            412844 kB
Dirty:            688204 kB     # s acosta al 20% de 3,8 GB
Dirty:             12408 kB     # abocat de cop: aqui esta la pausa

Els valors més petits produeixen abocaments més freqüents i petits, amb latència més uniforme:

$ sudo sysctl -w vm.dirty_background_ratio=5
$ sudo sysctl -w vm.dirty_ratio=10

I l'advertiment honest: això no fa el sistema «més ràpid» en cabal total —de vegades el fa una mica més lent— sinó més predictible. Per a un servei interactiu, la latència uniforme val més que el cabal màxim. Per a una còpia de seguretat aïllada, el contrari. És un compromís, no una millora.

La resta de la memòria virtual

Paràmetre Per defecte Què fa Quan tocar-lo
vm.vfs_cache_pressure 100 Agressivitat en recuperar memòria cau d'inodes i dentries Abaixar a 50 si hi ha molts fitxers petits i es rellegeix molt
vm.overcommit_memory 0 0 heurístic, 1 permetre-ho tot, 2 estricte 1 per a Redis; 2 només amb overcommit_ratio ben calculat
vm.min_free_kbytes ~45000 Reserva mínima lliure Pujar si hi ha fallades d'assignació en ràfegues de xarxa
vm.max_map_count 65530 Regions de memòria per procés Pujar per a Elasticsearch i bases de dades grans

vm.overcommit_memory=2 mereix un avís: sembla prudent («no prometis memòria que no tens») i a la pràctica fa que aplicacions perfectament normals fallin en reservar, perquè moltes en reserven molta més de la que fan servir. No es toca sense una raó molt concreta.

Xarxa i pila TCP

Aquí és on srv-tramontana té marge real, i on l'incident de 07-02 va deixar una pista sense explorar.

Cues de connexions entrants

Quan arriba una connexió TCP, passa per dues cues abans que l'aplicació l'accepti:

$ sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 1024
Paràmetre Quina cua controla
tcp_max_syn_backlog Connexions a mig establir: va arribar el SYN, falta l'ACK final
somaxconn Connexions establertes esperant que l'aplicació les accepti

I el detall crucial que fa que aquest paràmetre s'ajusti malament la meitat de les vegades: somaxconn és un sostre, no un valor efectiu. La cua real és el mínim entre somaxconn i el paràmetre backlog que l'aplicació passa a la crida listen(). Pujar somaxconn a 65535 no serveix de res si l'aplicació en demana 128.

La mesura que diu si la cua s'està omplint:

# La columna Recv-Q en un socol LISTEN es la cua d acceptacio pendent;
# Send-Q es el maxim efectiu (el minim entre somaxconn i el backlog de l app)
$ ss -ltn
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port
LISTEN  0       128     127.0.0.1:8080      0.0.0.0:*
LISTEN  0       4096    10.0.2.15:5432      0.0.0.0:*
LISTEN  0       128     0.0.0.0:22          0.0.0.0:*

# El comptador de connexions descartades per cua plena: LA metrica que importa
$ nstat -az | grep -iE 'ListenOverflows|ListenDrops'
TcpExtListenOverflows           0                  0.0
TcpExtListenDrops               0                  0.0

Aquell Send-Q 128 del port 8080 és la informació valuosa: l'aplicació demana un backlog de 128, així que el somaxconn de 4096 no l'afecta en absolut. I ListenOverflows a zero significa que cap connexió no s'ha descartat per cua plena. Conclusió: pujar somaxconn en aquesta màquina no faria res, i qui el posés al seu sysctl.d estaria afegint folklore.

Això és el procediment funcionant: la hipòtesi raonable («les cues de connexió són el coll») queda refutada per la mesura abans de tocar res. Refutar hipòtesis és tan valuós com confirmar-les, i força més barat.

Ports efímers i connexions en espera

Aquí sí que hi ha una cosa que l'incident de 07-02 va deixar a la vista. Quan l'aplicació obria 300 connexions cada deu segons, cadascuna consumia un port efímer que quedava en estat TIME_WAIT durant 60 segons:

$ sysctl net.ipv4.ip_local_port_range net.ipv4.tcp_fin_timeout net.ipv4.tcp_tw_reuse
net.ipv4.ip_local_port_range = 32768	60999
net.ipv4.tcp_fin_timeout = 60
net.ipv4.tcp_tw_reuse = 2

$ ss -s
Total: 284
TCP:   112 (estab 24, closed 74, orphaned 0, timewait 71)

Setanta-un sòcols en TIME_WAIT. Amb 28.231 ports disponibles no és un problema, però durant l'incident, amb 30 connexions per segon, se'n van arribar a acumular uns 1.800 — i una aplicació que n'obrís deu vegades més els esgotaria.

TIME_WAIT no és un defecte: garanteix que paquets endarrerits d'una connexió tancada no es confonguin amb una de nova que reutilitzi la mateixa parella de ports. Els ajustos possibles:

Ajust Efecte Risc
Ampliar ip_local_port_range a 1024 65535 Més ports disponibles Pot xocar amb serveis que escoltin en ports alts
tcp_tw_reuse = 1 Reutilitzar sòcols en TIME_WAIT per a connexions sortints Cap de rellevant avui; requereix marques de temps TCP
Abaixar tcp_fin_timeout Menys temps en TIME_WAIT Reintrodueix el risc que TIME_WAIT evita
tcp_tw_recycle — Eliminat del nucli a la 4.12. No existeix.

Aquell últim és l'exemple perfecte de folklore: tcp_tw_recycle apareix en centenars de guies «per optimitzar TCP», trencava les connexions des de xarxes amb NAT de forma subtil i intermitent, i va ser eliminat del nucli fa anys. Si trobes una guia que el recomana, saps que el seu autor no l'ha provada en aquesta dècada.

A Ubuntu 24.04, tcp_tw_reuse ja ve en 2 (habilitat només per a adreces de bucle i enllaç local), que és un valor prudent. La correcció real del problema de 07-02 no era cap sysctl: era arreglar el grup de connexions, que és el que vas fer.

Memòries intermèdies de sòcol

$ sysctl net.core.rmem_max net.core.wmem_max net.ipv4.tcp_rmem net.ipv4.tcp_wmem
net.core.rmem_max = 212992
net.core.wmem_max = 212992
net.ipv4.tcp_rmem = 4096	131072	6291456
net.ipv4.tcp_wmem = 4096	16384	4194304

Els tres valors de tcp_rmem són mínim, per defecte i màxim, i el nucli els ajusta automàticament dins d'aquell rang. Ampliar el màxim importa quan el producte amplada de banda × latència és gran: un enllaç d'1 Gbit/s amb 100 ms d'anada i tornada necessita uns 12 MB de finestra per saturar-se, i amb 6 MB de màxim es queda a la meitat.

En una xarxa local amb 0,4 ms de latència, com la de Tramontana, això és completament irrellevant. És un ajust per a transferències de llarga distància, i posar-lo «per si de cas» només consumeix memòria.

BBR: l'ajust de xarxa que sí que val la pena

El control de congestió decideix a quina velocitat envia TCP quan detecta problemes a la xarxa. L'algorisme clàssic, CUBIC, interpreta la pèrdua de paquets com a senyal de congestió. BBR, desenvolupat per Google, modela en canvi l'amplada de banda i la latència reals del camí.

La diferència pràctica és gran en enllaços amb pèrdua o amb memòries intermèdies grans (el problema del bufferbloat), que és el cas de gairebé qualsevol connexió a través d'Internet:

$ sysctl net.ipv4.tcp_available_congestion_control net.ipv4.tcp_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic
net.ipv4.tcp_congestion_control = cubic

# Carregar el modul de BBR
$ sudo modprobe tcp_bbr
$ sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic bbr

# Mesurar ABANS, amb el client a l altre costat de la xarxa
$ iperf3 -c 198.51.100.20 -t 20 -R | tail -3
[  5]   0.00-20.00  sec   184 MBytes  77.2 Mbits/sec                  receiver

# Canviar (efimer) i mesurar DESPRES
$ sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
$ sudo sysctl -w net.core.default_qdisc=fq
$ iperf3 -c 198.51.100.20 -t 20 -R | tail -3
[  5]   0.00-20.00  sec   441 MBytes  185 Mbits/sec                  receiver

De 77 a 185 Mbit/s en el mateix enllaç, i amb latència menor sota càrrega. default_qdisc=fq no és opcional: BBR necessita l'encuament fair queue per funcionar tal com està dissenyat, i sense ell el resultat és pitjor.

Amb la mesura feta, el canvi es fa persistent amb el seu motiu escrit:

$ sudo tee -a /etc/sysctl.d/70-rendiment.conf >/dev/null <<'EOF'

# Control de congestio BBR. Mesurat amb iperf3 contra un client remot:
# 77 -> 185 Mbit/s de baixada, i menor latencia sota carrega. Requereix fq.
# Mesura: 2026-08-18. Revertir amb cubic si apareixen anomalies.
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
$ echo 'tcp_bbr' | sudo tee /etc/modules-load.d/bbr.conf
$ sudo sysctl --system >/dev/null && sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = bbr

Fixa't en el comentari: què es va mesurar, amb què, el resultat, la data i com revertir-ho. Això és la diferència entre un ajust i una línia copiada.

Límits de fitxers i processos

$ sysctl fs.file-max fs.file-nr kernel.pid_max fs.inotify.max_user_watches
fs.file-max = 9223372036854775807
fs.file-nr = 2848	0	9223372036854775807
kernel.pid_max = 4194304
fs.inotify.max_user_watches = 65536

fs.file-max en nuclis moderns és pràcticament il·limitat, així que pujar-lo —un altre clàssic de les guies— no fa res. El límit real que s'assoleix a la pràctica és el per procés, que es veu a l'apartat següent.

fs.inotify.max_user_watches sí que s'esgota de debò, i amb un error desconcertant. Cada inotify watch consumeix una entrada, i eines de desenvolupament, sincronitzadors de fitxers i logrotate en fan un ús intensiu:

# El simptoma: "No space left on device" sense que falti espai al disc
$ df -h / | tail -1
/dev/mapper/vg-root  23G  7,1G   15G  33% /

# La causa real
$ find /proc/*/fd -lname anon_inode:inotify 2>/dev/null | wc -l
1284
$ sudo sysctl -w fs.inotify.max_user_watches=524288

Aquell ENOSPC que no és falta de disc és una de les confusions més freqüents de Linux, i mereix ser al manual d'operació.

Planificador d'E/S

El planificador decideix en quin ordre se serveixen les peticions al disc. Ubuntu 24.04 fa servir la infraestructura de multicua (blk-mq):

$ cat /sys/block/sda/queue/scheduler
[none] mq-deadline kyber bfq

$ lsblk -d -o NAME,ROTA,SCHED
NAME ROTA SCHED
sda     1 none
Planificador Com funciona Per a quin dispositiu
none Sense reordenació; FIFO per cua NVMe i SSD ràpids
mq-deadline Terminis per petició, evita la inanició SATA SSD i discos mecànics de servidor
kyber Objectius de latència adaptatius Càrregues mixtes amb moltes cues
bfq Repartiment equitatiu per procés Escriptori; interactivitat per sobre del cabal

La regla de none per a NVMe té un perquè concret que cal entendre: un planificador reordena peticions per minimitzar el moviment del capçal d'un disc mecànic. Un NVMe no té capçal, atén desenes de milers d'operacions per segon i té les seves pròpies cues al maquinari. Reordenar només hi afegeix latència i consum de CPU. En un disc mecànic, en canvi, reordenar pot multiplicar el cabal.

# Canvi efimer, per provar
$ echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
$ cat /sys/block/sda/queue/scheduler
none [mq-deadline] kyber bfq

Com que /sys no és persistent, la manera correcta de fixar-ho és una regla d'udev que decideixi segons el tipus de dispositiu:

$ sudo tee /etc/udev/rules.d/60-planificador-io.rules >/dev/null <<'EOF'
# NVMe: sense planificador. El dispositiu te les seves propies cues.
ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*", ATTR{queue/scheduler}="none"
# Discos rotacionals (ROTA=1): mq-deadline reordena i aprofita la localitat.
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \
    ATTR{queue/scheduler}="mq-deadline"
# SSD SATA (ROTA=0): mq-deadline tambe, pels seus terminis.
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", \
    ATTR{queue/scheduler}="mq-deadline"
EOF
$ sudo udevadm control --reload && sudo udevadm trigger --subsystem-match=block
$ cat /sys/block/sda/queue/scheduler
none [mq-deadline] kyber bfq

I dos paràmetres més de la cua que de vegades importen:

$ cat /sys/block/sda/queue/read_ahead_kb /sys/block/sda/queue/nr_requests
128
64

read_ahead_kb és quant llegeix el nucli per endavant. Pujar-lo (a 512 o més) ajuda en lectures seqüencials grans —una còpia de seguretat, un abocament de base de dades—; abaixar-lo ajuda en accessos aleatoris petits, perquè evita llegir dades que no es faran servir. És exactament el compromís que cal mesurar abans de tocar.

Una observació sobre la pila de Tramontana: el volum de còpies és LUKS sobre LVM sobre sda, i cada capa presenta el seu propi dispositiu de bloc amb la seva pròpia cua. El planificador que importa és el del dispositiu físic (sda); els dispositius dm-* passen les peticions cap avall. És la mateixa pila que calia descompondre al primer exercici de 07-02.

Pàgines enormes transparents

El processador tradueix adreces virtuals a físiques en pàgines de 4 KB, fent servir una memòria cau anomenada TLB. Amb molta memòria, la TLB falla sovint i cada fallada costa accessos extra a memòria. Les pàgines enormes de 2 MB redueixen dràsticament el nombre d'entrades necessàries.

THP (Transparent Huge Pages) fa això automàticament. I és el paràmetre que totes les bases de dades demanen desactivar:

$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
$ cat /sys/kernel/mm/transparent_hugepage/defrag
always defer [defer+madvise] madvise never
Valor Comportament
always THP per a tot. Per defecte a Ubuntu
madvise Només per a processos que ho demanen amb madvise(MADV_HUGEPAGE)
never Desactivat

Per què les bases de dades ho rebutgen, que és la part interessant: per donar una pàgina de 2 MB, el nucli necessita 2 MB físicament contigus. Quan la memòria està fragmentada, l'ha de compactar, i aquella compactació passa de forma síncrona en el context del procés que demana memòria. El resultat són pauses impredictibles de desenes o centenars de mil·lisegons justament al procés que menys les tolera. PostgreSQL, MongoDB, Redis, Oracle i Elasticsearch documenten tots la recomanació de madvise o never.

madvise és el millor compromís: els processos que se'n beneficien ho demanen explícitament, i els altres no en paguen el cost.

$ echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled

Com que /sys no persisteix, es fixa amb una unitat de systemd —aplicant 05-05—, que és més net que un rc.local:

$ sudo tee /etc/systemd/system/thp-madvise.service >/dev/null <<'EOF'
[Unit]
Description=Posar transparent hugepages en madvise (recomanat per PostgreSQL)
Documentation=https://www.postgresql.org/docs/16/kernel-resources.html
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=postgresql.service tramontana.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled'

[Install]
WantedBy=basic.target
EOF
$ sudo systemctl daemon-reload && sudo systemctl enable --now thp-madvise.service
$ cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

El Before=postgresql.service importa: el canvi ha d'estar aplicat abans que la base de dades reservi la seva memòria compartida.

Límits per procés i qui guanya

Un límit del nucli és global; els que s'assoleixen a la pràctica són els per procés, i aquí hi ha tres mecanismes que es trepitgen entre ells. Saber quin guanya estalvia hores de desconcert.

$ ulimit -n          # limit tou de la sessio actual
1024
$ ulimit -Hn         # limit dur
1048576
Mecanisme On A qui s'aplica
ulimit Ordre del shell El procés actual i els seus fills
/etc/security/limits.conf PAM (pam_limits) Només sessions que passen per PAM (login, SSH, su)
LimitNOFILE= a la unitat systemd Els serveis de systemd
DefaultLimitNOFILE= /etc/systemd/system.conf Tots els serveis sense límit propi

I la resposta a «quin guanya», que és la raó d'aquest apartat: per a un servei de systemd, guanya la unitat, i limits.conf no s'aplica en absolut. systemd arrenca els serveis directament, sense passar per PAM. Posar svc-tramontana hard nofile 16384 a limits.conf i esperar que afecti tramontana.service és un error clàssic que no dona cap avís: simplement no funciona.

# Comprovar el limit REAL d un servei en marxa: la font de veritat
$ pid=$(systemctl show tramontana.service -p MainPID --value)
$ grep -E 'Max open files|Max processes' /proc/$pid/limits
Max open files            1024                 524288               files
Max processes             15370                15370               processes

Amb MemoryMax=512M i TasksMax=64 ja posats a 05-05, el límit de fitxers oberts s'ajusta igual, per drop-in:

$ sudo tee /etc/systemd/system/tramontana.service.d/limits.conf >/dev/null <<'EOF'
[Service]
# 80 connexions al grup de PostgreSQL + socols de client + registres + marge.
# Mesurat despres de l ajust de 07-02: pic de 214 descriptors.
LimitNOFILE=8192
EOF
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ pid=$(systemctl show tramontana.service -p MainPID --value)
$ grep 'Max open files' /proc/$pid/limits
Max open files            8192                 8192                 files

# I verificar l us real, per saber si 8192 es raonable o excessiu
$ ls /proc/$pid/fd | wc -l
214

214 descriptors en ús amb un límit de 8192: marge de sobres sense ser absurd. Aquell és el criteri — un límit es dimensiona a partir de l'ús mesurat, no d'un número rodó copiat.

Governadors de freqüència i tuned

$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null \
    || echo "sense cpufreq (habitual en una VM)"
sense cpufreq (habitual en una VM)

En una màquina virtual la gestió de freqüència la fa l'amfitrió, així que aquest apartat no s'aplica a srv-tramontana. En maquinari físic:

Governador Comportament Quan
powersave Freqüència mínima, puja segons demanda Per defecte; amb intel_pstate és raonable
performance Freqüència màxima sempre Servidors sensibles a la latència
schedutil Guiat pel planificador El modern; bon equilibri

El cas d'ús real de performance és la latència de cua: pujar de freqüència triga microsegons, i en un servei amb objectius de latència estrictes aquells microsegons apareixen al percentil 99. A canvi de força més consum elèctric.

tuned és la manera sensata d'aplicar conjunts coherents d'ajustos en lloc de paràmetres solts:

$ sudo apt install tuned
$ sudo tuned-adm list
Available profiles:
- balanced               - General non-specialized tuned profile
- latency-performance    - Optimize for deterministic performance
- network-latency        - Optimize for deterministic performance, low latency
- network-throughput     - Optimize for streaming network throughput
- throughput-performance - Broadly applicable tuning for throughput
- virtual-guest          - Optimize for running inside a virtual guest

$ sudo tuned-adm active
Current active profile: virtual-guest

# Veure EXACTAMENT que canvia un perfil abans d aplicar-lo
$ cat /usr/lib/tuned/throughput-performance/tuned.conf | grep -A12 '\[sysctl\]'

Aquella última ordre és el consell important: tuned és útil perquè aplica conjunts provats i consistents, però llegir què fa un perfil abans d'activar-lo és obligatori. Un perfil pot canviar vint paràmetres, i si un d'ells entra en conflicte amb el teu sysctl.d, el resultat depèn de l'ordre d'aplicació i és difícil de depurar.

Mòduls del nucli

Un mòdul és un tros de nucli que es carrega i es descarrega en calent: controladors, sistemes de fitxers, protocols.

$ lsmod | head -5
Module                  Size  Used by
tcp_bbr                24576  20
dm_crypt              65536  1
raid1                 49152  0
vboxguest             57344  2

$ modinfo tcp_bbr | head -5
filename:       /lib/modules/6.8.0-41-generic/kernel/net/ipv4/tcp_bbr.ko.zst
license:        Dual BSD/GPL
description:    TCP BBR (Bottleneck Bandwidth and RTT)
depends:
intree:         Y
Operació Ordre
Llistar els carregats lsmod
Informació modinfo <modul>
Carregar (amb dependències) sudo modprobe <modul>
Descarregar sudo modprobe -r <modul>
Veure els paràmetres actuals systool -v -m <modul>

La configuració persistent:

# Carregar un modul a l arrencada (ho vas fer servir per a BBR)
$ echo 'tcp_bbr' | sudo tee /etc/modules-load.d/bbr.conf

# Passar parametres a un modul
$ sudo tee /etc/modprobe.d/tramontana.conf >/dev/null <<'EOF'
# Redueix l us de memoria del modul de xifratge en una VM petita
options dm_crypt max_read_size=131072
EOF

# Impedir que un modul es carregui: reduccio de superficie d atac (06-06)
$ sudo tee /etc/modprobe.d/blacklist-tramontana.conf >/dev/null <<'EOF'
# Sistemes de fitxers i protocols que aquest servidor no fa servir mai.
# Cada linia redueix superficie d atac: son moduls amb CVE historics.
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install udf /bin/true
install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true
EOF
$ sudo update-initramfs -u -k all

Dos detalls importants. El primer: install <modul> /bin/true és més eficaç que blacklist <modul>, perquè blacklist només evita la càrrega automàtica i no impedeix que un altre mòdul el carregui com a dependència. El segon: després de tocar modprobe.d, cal regenerar l'initramfs, pel que vas aprendre a 07-01 — l'initramfs porta la seva pròpia còpia d'aquella configuració.

Aquesta llista de mòduls bloquejats és, de fet, una recomanació dels CIS Benchmarks que va quedar pendent a la llista de comprovació de 06-06. És un bon moment per tancar-la.

DKMS (Dynamic Kernel Module Support) resol el problema dels mòduls de tercers: un mòdul compilat per al nucli 6.8.0-41 no funciona al 6.8.0-45. DKMS el recompila automàticament a cada actualització del nucli:

$ dkms status
virtualbox-guest/7.0.16, 6.8.0-41-generic, x86_64: installed
virtualbox-guest/7.0.16, 6.8.0-39-generic, x86_64: installed

Amb Secure Boot actiu, un mòdul recompilat per DKMS necessita signatura, i aquí entra el mmx64.efi (MokManager) que vas veure a 07-01. És la causa habitual que un controlador de tercers deixi de funcionar després d'actualitzar el nucli en una màquina amb Secure Boot.

Compilar el nucli: quan té sentit i quan no

Compilar el nucli és un ritu de pas en l'aprenentatge de Linux, i convé ser honest sobre la seva utilitat en un servidor de producció: gairebé mai no en té.

Motiu al·legat Compensa?
«Un nucli més petit i ràpid» No. Els mòduls no carregats no consumeixen res. El guany és indetectable
«Necessito una opció no habilitada» De vegades. Primer comprova si existeix un paquet d'Ubuntu que ja la porta
«Necessito un pedaç que no és a cap versió» Sí. És el cas legítim
«Necessito una versió molt recent per a un controlador» No. Fes servir el nucli HWE o linux-generic-hwe-24.04
«Per aprendre» Sí, i al laboratori, no en producció

I el cost que se subestima: un nucli compilat a mà queda fora del sistema de paquets. No rep actualitzacions de seguretat automàtiques —adeu a l'unattended-upgrades de 05-03—, no està signat per a Secure Boot, i cada CVE del nucli exigeix recompilar a mà. En una màquina que tracta dades personals, això és un problema de compliment, no només de comoditat.

Les variants empaquetades cobreixen gairebé tot:

Paquet Per a què
linux-image-generic Ús general. És el que tens
linux-image-virtual Màquines virtuals: sense controladors de maquinari físic, més petit
linux-image-generic-hwe-24.04 Nucli més recent sobre la LTS, per a maquinari nou
linux-image-lowlatency Menor latència de planificació, menor cabal

El procediment, per al laboratori:

$ sudo apt install build-essential libncurses-dev bison flex libssl-dev \
      libelf-dev dwarves zstd
$ apt source linux-image-unsigned-$(uname -r)
$ cd linux-6.8.0

# Partir de la configuracio ACTUAL, mai de zero
$ cp /boot/config-$(uname -r) .config
$ make olddefconfig          # ajusta la config antiga a les opcions noves
$ make menuconfig            # nomes el canvi que necessites

$ make -j"$(nproc)" 2>&1 | tail -3
$ sudo make modules_install
$ sudo make install          # copia vmlinuz i executa update-initramfs
$ sudo update-grub

# I la xarxa de seguretat de 07-01: el nucli anterior continua al menu
$ awk -F"'" '/menuentry .Ubuntu, with Linux/ {print $2}' /boot/grub/grub.cfg

make olddefconfig partint de /boot/config-$(uname -r) és el que evita l'error del principiant: make defconfig genera una configuració genèrica que probablement no inclogui el controlador del teu controlador de disc, i el resultat és l'indicador (initramfs) de 07-01.

Cas Tramontana: ajustar la pila de xarxa amb mesura

L'exercici complet, aplicant el procediment de cinc passos. Punt de partida: la línia base de 05-07, l'incident dels db_timeout amb connexions_actives=200, i l'ajust del grup de connexions de 07-02.

Pas 1: mesurar l'estat actual

$ cat ~/scripts/linia_base_nucli.sh
#!/usr/bin/env bash
# linia_base_nucli.sh - Registra els parametres i comptadors del nucli
#   rellevants per al rendiment, per poder comparar abans i despres.
# Us: linia_base_nucli.sh [etiqueta]
set -euo pipefail

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/comuns.sh
source "${SCRIPT_DIR}/lib/comuns.sh"

readonly ETIQUETA_LOG="linia-base-nucli"
readonly DESTI="${TRAMONTANA_BASE_DIR:-/home/operador/dades/linies-base}"

main() {
    local etiqueta="${1:-manual}"
    local data; data="$(date +%Y%m%d-%H%M%S)"
    local fitxer="${DESTI}/nucli-${etiqueta}-${data}.txt"

    install -d -m 750 "$DESTI"

    {
        printf '# Linia base de nucli — %s — %s\n\n' "$etiqueta" "$(date -Is)"
        printf '## Parametres\n'
        sysctl vm.swappiness vm.dirty_ratio vm.dirty_background_ratio \
               net.core.somaxconn net.ipv4.tcp_max_syn_backlog \
               net.ipv4.tcp_congestion_control net.core.default_qdisc \
               net.ipv4.ip_local_port_range 2>/dev/null
        printf '\n## Planificador d E/S\n'
        for d in /sys/block/sd*/queue/scheduler; do
            printf '%s: %s\n' "$d" "$(cat "$d")"
        done
        printf '\n## Comptadors de xarxa (els que importen)\n'
        nstat -az 2>/dev/null | grep -iE 'ListenOverflows|ListenDrops|TCPSynRetrans|RetransSegs'
        printf '\n## Socols\n'
        ss -s
        printf '\n## Cues d escolta\n'
        ss -ltn
        printf '\n## Intercanvi\n'
        grep -E 'pswpin|pswpout' /proc/vmstat
        printf '\n## Latencia de l aplicacio (5 mostres)\n'
        for _ in {1..5}; do
            curl -s -o /dev/null -w '%{time_total}\n' \
                "http://127.0.0.1:${TRAMONTANA_PORT:-8080}/cases" || true
        done
    } >"$fitxer"

    chmod 640 "$fitxer"
    log "linia base escrita a $fitxer"
    printf '%s\n' "$fitxer"
}

main "$@"
$ chmod +x ~/scripts/linia_base_nucli.sh
$ shellcheck ~/scripts/linia_base_nucli.sh && ~/scripts/linia_base_nucli.sh abans
[2026-08-18 17:12:04] linia-base-nucli: linia base escrita a /home/operador/dades/linies-base/nucli-abans-20260818-171204.txt

Pas 2: hipòtesis, i la que es refuta

Tres hipòtesis raonables a partir de l'historial, i què diu la mesura de cadascuna:

Hipòtesi Mesura Veredicte
Les cues de connexió es desborden ListenOverflows = 0, Send-Q = 128 (backlog de l'app) Refutada. somaxconn no hi intervé
Hi ha pressió de memòria i intercanvi pswpin = 0, pswpout = 0, 2,4 GB disponibles Refutada avui; s'ajusta com a assegurança
El control de congestió limita el trànsit remot cubic, i 77 Mbit/s mesurats amb iperf3 Confirmada. BBR dona 185 Mbit/s

Dues de tres refutades per la mesura, i aquest és el resultat normal. Si haguessis copiat una guia d'«optimització de xarxa per a Linux», hauries posat somaxconn = 65535 —que aquí no fa res— i probablement tcp_tw_recycle, que ja no existeix.

Passos 3 i 4: un canvi, mesurar

# L unic canvi confirmat per la mesura, primer en efimer
$ sudo modprobe tcp_bbr
$ sudo sysctl -w net.core.default_qdisc=fq
$ sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
$ iperf3 -c 198.51.100.20 -t 20 -R | tail -2
[  5]   0.00-20.00  sec   441 MBytes  185 Mbits/sec                  receiver

# I verificar que no ha empitjorat res mes
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0
$ ~/scripts/linia_base_nucli.sh despres
$ diff -u ~/dades/linies-base/nucli-abans-*.txt \
          ~/dades/linies-base/nucli-despres-*.txt | head -20

Pas 5: documentar el fitxer final

$ sudo tee /etc/sysctl.d/70-rendiment.conf >/dev/null <<'EOF'
# Ajustos de rendiment de srv-tramontana.
# REGLA: cada parametre porta el seu motiu, la seva mesura i la seva data.
# Els parametres de SEGURETAT son a 60-enfortiment.conf, a part i a
# proposit: aquests es poden revertir, aquells no.

# --- Xarxa -------------------------------------------------------------
# BBR modela amplada de banda i latencia en lloc de reaccionar a la perdua.
# Mesurat amb iperf3 contra 198.51.100.20 el 2026-08-18:
#   cubic: 77 Mbit/s   ->   bbr: 185 Mbit/s, i menor latencia sota carrega.
# Requereix l encuament fq: sense ell, BBR rendeix PITJOR que cubic.
# Revertir: net.ipv4.tcp_congestion_control = cubic
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# --- Memoria virtual ---------------------------------------------------
# NO es una millora de rendiment mitja: avui no hi ha activitat d intercanvi
# (pswpin=0, pswpout=0). Es una ASSEGURANCA: si algun dia hi ha pressio de
# memoria, el nucli preferira descartar memoria cau abans que enviar a
# l intercanvi les pagines actives del proces, evitant latencies de
# centenars de ms.
vm.swappiness = 10

# Llindars de pagines brutes mes baixos que el 10/20 per defecte.
# Motiu: amb 3,8 GB, el 20% son ~760 MB que poden abocar-se de cop i
# bloquejar el proces que escriu. Es el mecanisme de l incident del
# 2026-08-18 (copia nocturna saturant E/S, w_await 22,85 ms).
# Efecte: menys cabal maxim, latencia mes uniforme. Compromis acceptat.
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10

# --- Fitxers -----------------------------------------------------------
# S esgota de debo i l error es enganyos ("No space left on device"
# sense que falti disc). Mesurat: 1284 watches en us amb 65536 de limit.
fs.inotify.max_user_watches = 524288

# NO TOQUEM, i consta per que:
#   net.core.somaxconn: l app demana backlog=128, aixi que somaxconn no
#     hi intervé (ss -ltn: Send-Q=128). ListenOverflows=0. Sense efecte.
#   net.core.rmem_max/wmem_max: xarxa local amb 0,4 ms de RTT. El producte
#     amplada de banda x latencia no justifica memories intermedies majors.
#   net.ipv4.tcp_tw_recycle: NO EXISTEIX. Eliminat del nucli a la 4.12.
EOF

$ sudo sysctl --system >/dev/null
$ sysctl net.ipv4.tcp_congestion_control vm.swappiness
net.ipv4.tcp_congestion_control = bbr
vm.swappiness = 10

La secció final —«NO TOQUEM, i consta per què»— és la part del fitxer que més valor té a llarg termini. Documenta les hipòtesis refutades, i això impedeix que d'aquí a sis mesos algú —tu inclòs— afegeixi somaxconn = 65535 perquè ho ha vist en una guia. Un ajust descartat amb el seu motiu escrit és coneixement; un ajust absent és només una omissió.

Errors Comuns i Consells

  • Copiar llistes de sysctl d'Internet. La majoria són de fa deu anys, moltes contradiuen valors per defecte que ja són correctes, i algunes recomanen paràmetres que ja no existeixen (tcp_tw_recycle és el senyal inequívoc d'una guia sense provar).
  • Canviar diverses coses alhora. Si millora, no saps quina; si empitjora, tampoc. Un canvi, una mesura.
  • Posar un paràmetre a /etc/sysctl.d/ sense provar-lo amb -w. El canvi efímer és reversible amb un reinici; el persistent et pot deixar un sistema que arrenca malament.
  • Creure que swappiness redueix l'ús de memòria. Només actua quan hi ha pressió i el sistema recorre a l'intercanvi. Sense intercanvi en ús, no fa res.
  • Pujar somaxconn sense mirar el backlog de l'aplicació. El límit efectiu és el mínim dels dos. ss -ltn mostra el real a Send-Q.
  • Confiar en limits.conf per a un servei de systemd. No s'aplica: systemd no passa per PAM. Es fa servir LimitNOFILE= a la unitat, i es verifica a /proc/<pid>/limits.
  • Deixar THP en always amb una base de dades. Provoca pauses síncrones de compactació impredictibles. madvise és el compromís correcte, i cal aplicar-lo abans que la base de dades arrenqui.
  • Posar mq-deadline en un NVMe. Reordenar peticions no té sentit sense capçal: només hi afegeix latència. none per a NVMe.
  • Oblidar update-initramfs després de tocar /etc/modprobe.d/. L'initramfs porta la seva pròpia còpia. És la lliçó de 07-01.
  • Compilar el nucli en producció. Queda fora del sistema de paquets: sense actualitzacions de seguretat automàtiques, sense signatura per a Secure Boot, i amb cada CVE a mà. Prova primer linux-image-virtual o el nucli HWE.
  • Ajustar sense línia base. Sense un «abans» registrat, el «després» no significa res, i la sensació de millora és notòriament poc fiable.
  • Consell de mètode. El comentari de cada paràmetre ha de respondre a quatre preguntes: què vas mesurar, amb quina ordre, quin resultat, i com revertir-ho. I documenta també el que vas decidir no tocar: és el que evita que el folklore torni a entrar.

Exercicis

Exercici 1

El Luis t'envia aquesta llista «per optimitzar el servidor», treta d'un blog:

net.core.somaxconn = 65535
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_max_syn_backlog = 65535
fs.file-max = 2097152
vm.swappiness = 0
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728

Avalua cada línia: què faria, si té sentit a srv-tramontana, i amb quina ordre ho comprovaries. Redacta la resposta que li enviaries.

Exercici 2

Després de l'ajust de 07-02, l'aplicació manté un grup de 80 connexions a PostgreSQL. Comprova si els límits de descriptors de fitxer són adequats als tres nivells implicats (el servei de l'aplicació, el de PostgreSQL, i el global), i ajusta el que calgui amb la seva mesura.

Exercici 3

/srv/tramontana/backups és LUKS sobre LVM sobre sda, i vols saber si el planificador d'E/S influeix en la durada de la còpia nocturna. Dissenya l'experiment: què mesuraries, com aïllaries la variable, i per què el resultat podria no ser concloent.

Solucions

Solució 1

Re: llista d'optimització del nucli

Gràcies, Luis. L'he revisada línia per línia contra l'estat real del servidor. Resum: una té sentit, una és contraproduent, una no existeix, i quatre no farien res. Et passo el detall amb les ordres, perquè ho puguis comprovar tu mateix.


1. net.core.somaxconn = 65535 — Sense efecte

somaxconn és un sostre. La cua real és el mínim entre aquest valor i el backlog que l'aplicació demana a listen().

$ ss -ltn | grep 8080
LISTEN  0  128  127.0.0.1:8080  0.0.0.0:*
$ nstat -az | grep ListenOverflows
TcpExtListenOverflows  0  0.0

Send-Q = 128: l'aplicació en demana 128, així que el valor actual (4096) ja és irrellevant, i 65535 ho seria igual. I ListenOverflows = 0 significa que mai no s'ha descartat una connexió per cua plena. Perquè aquest paràmetre servís caldria canviar el backlog al codi de l'aplicació, i no hi ha cap evidència que calgui.

2. net.ipv4.tcp_tw_recycle = 1 — No existeix

$ sysctl net.ipv4.tcp_tw_recycle
sysctl: cannot stat /proc/sys/net/ipv4/tcp_tw_recycle: No such file or directory

Es va eliminar del nucli a la versió 4.12 (2017) perquè trencava les connexions des de xarxes amb NAT de forma intermitent i molt difícil de diagnosticar. Que aparegui a la guia ens diu una cosa útil: l'autor no l'ha provada en aquesta dècada, així que convé desconfiar de la resta. Si l'objectiu era el reciclatge de TIME_WAIT, el paràmetre vigent és tcp_tw_reuse, i ja és en 2, que és el valor prudent per defecte a Ubuntu 24.04.

3. net.ipv4.tcp_max_syn_backlog = 65535 — Sense efecte mesurable

$ nstat -az | grep -iE 'TCPReqQFullDrop|ListenDrops'
TcpExtListenDrops  0  0.0

És la cua de connexions a mig establir, i la seva utilitat real és resistir una inundació de SYN. Zero descarts amb el valor actual de 1024. A més, contra aquell atac ja tenim tcp_syncookies actiu des de 06-03, que és la defensa correcta. Pujar-lo reservaria memòria del nucli sense benefici.

4. fs.file-max = 2097152 — Sense efecte (i seria una baixada)

$ sysctl fs.file-max
fs.file-max = 9223372036854775807
$ sysctl fs.file-nr
fs.file-nr  2848  0  9223372036854775807

En nuclis moderns ja és pràcticament il·limitat: fixar 2.097.152 seria reduir-lo. I en fem servir 2.848. El límit que sí que s'assoleix a la pràctica és el de per procés, que en un servei de systemd es posa amb LimitNOFILE= a la unitat — limits.conf no s'aplica als serveis, que és un detall que confon molta gent.

5. vm.swappiness = 0 — Contraproduent

Aquest és el que em preocupa. swappiness = 0 no significa «no facis servir intercanvi», significa «fes servir intercanvi només per evitar l'OOM killer». L'efecte real és que el nucli esgota la memòria cau per complet abans de considerar l'intercanvi, i en un pic de memòria pot invocar l'OOM killer abans d'hora — matant el procés de l'aplicació o de PostgreSQL.

$ grep -E 'pswpin|pswpout' /proc/vmstat
pswpin 0
pswpout 0
$ free -h | awk 'NR==2 {print "disponible:", $7}'
disponible: 2,4Gi

Avui tant se val perquè no hi ha activitat d'intercanvi, però el dia que n'hi hagi, 0 és pitjor que el valor per defecte. He posat 10, que prefereix descartar memòria cau sense renunciar a l'intercanvi com a xarxa de seguretat, i ho he documentat com a assegurança contra la degradació, no com a millora de rendiment.

6 i 7. rmem_max / wmem_max = 128 MB — Sense efecte, i consumeix memòria

Les memòries intermèdies grans importen quan el producte amplada de banda × latència és gran.

$ ping -c3 10.0.2.15 | tail -1
rtt min/avg/max/mdev = 0.312/0.398/0.482/0.071 ms

Amb 0,4 ms d'anada i tornada en xarxa local, la finestra necessària per saturar 1 Gbit/s són uns 50 KB. El màxim actual (208 KB de rmem_max, i fins a 6 MB a tcp_rmem) sobra amb escreix. Reservar 128 MB per sòcol en una màquina amb 3,8 GB de RAM és un risc real si s'obren moltes connexions.


El que sí que he fet, i per què

Seguint el mateix criteri —mesurar primer—, l'únic canvi de xarxa que ha resultat justificat és el control de congestió BBR, que no era a la teva llista:

# cubic (per defecte)
$ iperf3 -c 198.51.100.20 -t 20 -R | tail -2
[  5]   0.00-20.00  sec   184 MBytes  77.2 Mbits/sec  receiver
# bbr + fq
$ iperf3 -c 198.51.100.20 -t 20 -R | tail -2
[  5]   0.00-20.00  sec   441 MBytes  185 Mbits/sec  receiver

De 77 a 185 Mbit/s cap a clients remots. Aquell sí que és un ajust amb efecte mesurat, i és a /etc/sysctl.d/70-rendiment.conf amb la mesura, la data i la manera de revertir-lo.

El que proposo per a la propera vegada. Abans d'aplicar un paràmetre, tres preguntes: quin comptador em diu que aquest recurs és el coll? l'he provat amb sysctl -w i mesurat abans i després? està escrit el motiu? Si alguna resposta és no, el paràmetre no entra. He afegit al fitxer una secció de «NO TOQUEM, i consta per què» amb aquestes set línies i la seva raó, precisament perquè no ho tornem a discutir d'aquí a sis mesos.

Solució 2

Els tres nivells, mesurats de dins cap enfora.

# --- Nivell 1: el servei de l aplicacio ---
$ pid_app=$(systemctl show tramontana.service -p MainPID --value)
$ grep 'Max open files' /proc/$pid_app/limits
Max open files            8192                 8192                 files
$ ls /proc/$pid_app/fd | wc -l
214

Ús real 214 sobre un límit de 8192: folgat i correcte. El desglossament confirma que el número té sentit:

$ ls -l /proc/$pid_app/fd | awk '{print $NF}' | sed 's/[0-9]*$//' \
    | sort | uniq -c | sort -rn | head -5
     80 socket:[
     94 /opt/tramontana/releases/3.2.1/plantilles/
     14 /var/log/tramontana/
      3 pipe:[
      2 /dev/null

80 sòcols = el grup de connexions a PostgreSQL, exactament el valor de max_connexions=80 que es va ajustar a 07-02. Els descriptors són on han de ser.

# --- Nivell 2: PostgreSQL. Aqui esta el problema. ---
$ pid_pg=$(systemctl show [email protected] -p MainPID --value)
$ grep 'Max open files' /proc/$pid_pg/limits
Max open files            1024                 524288               files
$ sudo -u postgres psql -tAc "SHOW max_connections;"
100
$ sudo -u postgres psql -tAc "SHOW max_files_per_process;"
1000

El límit tou és 1024, i max_files_per_process és 1000. I aquí està el càlcul que revela el risc: cada procés de rerefons de PostgreSQL obre descriptors per als fitxers de taula i índex que toca. Amb 100 connexions possibles i fins a 1000 fitxers per procés, el límit de 1024 del procés principal és estret.

# La comprovacio directa: us real agregat
$ sudo ls /proc/$pid_pg/fd | wc -l
88
$ for p in $(pgrep -P $pid_pg); do sudo ls /proc/$p/fd 2>/dev/null | wc -l; done \
    | awk '{s+=$1} END {print "descriptors als processos de rerefons:", s}'
descriptors als processos de rerefons: 412

Avui no s'està assolint, però el marge és escàs i la fallada, quan arriba, es manifesta com a errors de connexió intermitents sota càrrega — difícils de diagnosticar.

# Corregir per drop-in, mai editant la unitat del paquet
$ sudo mkdir -p /etc/systemd/system/[email protected]
$ sudo tee /etc/systemd/system/[email protected]/limits.conf >/dev/null <<'EOF'
[Service]
# max_connections=100 x max_files_per_process=1000 en el pitjor cas.
# 65536 dona marge ampli sense ser absurd. Mesurat el 2026-08-18:
# proces principal 88 fd, processos de rerefons 412 fd agregats.
LimitNOFILE=65536
EOF
$ sudo systemctl daemon-reload && sudo systemctl restart [email protected]
$ pid_pg=$(systemctl show [email protected] -p MainPID --value)
$ grep 'Max open files' /proc/$pid_pg/limits
Max open files            65536                65536                files
# --- Nivell 3: el limit global ---
$ sysctl fs.file-nr fs.file-max
fs.file-nr  2848  0  9223372036854775807
fs.file-max = 9223372036854775807

2.848 descriptors a tot el sistema contra un límit pràcticament infinit: no hi ha res a ajustar, i això confirma el que se li va respondre al Luis a l'exercici anterior sobre fs.file-max.

# I el valor per defecte per a serveis que no declarin limit propi
$ grep -i defaultlimitnofile /etc/systemd/system.conf
#DefaultLimitNOFILE=1024:524288

Aquí hi ha una decisió de criteri: es podria pujar el valor per defecte global, però és preferible no fer-ho. Un límit per defecte alt amaga les fuites de descriptors: un servei que no tanca el que obre falla aviat i de forma visible amb un límit raonable, i consumeix memòria del nucli silenciosament durant mesos amb un d'alt. Millor límits explícits i dimensionats per servei.

Verificació final:

$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0
$ sudo -u postgres psql -tAc "SELECT count(*) FROM pg_stat_activity;"
82
$ sudo timeout 10 strace -f -c -p $pid_app 2>&1 | grep -cE 'EMFILE|ENFILE'
0

Zero errors EMFILE/ENFILE, que són els que apareixerien si s'esgotessin els descriptors. I la lliçó de mètode: el problema era a PostgreSQL, no a l'aplicació, encara que el símptoma anterior es manifestés a l'aplicació. Igual que a 07-02, la causa era a l'altre costat de la relació client-servidor. Convé afegir al manual d'operació la regla: en ajustar un límit d'un costat, comprovar el de l'altre.

Solució 3

Què mesuraria, i per què aquelles mètriques. La durada total de la còpia és la variable d'interès, però és massa gruixuda per atribuir-la al planificador. Cal mesurar a tres nivells:

# 1. Durada total (el que li importa a la Marta)
$ systemd-analyze --no-pager verify tramontana-copia.service
$ sudo systemctl show tramontana-copia.service \
      -p ExecMainStartTimestamp -p ExecMainExitTimestamp

# 2. Latencia per DISPOSITIU. -D separa les capes de la pila, que es
#    la rao de fer servir biolatency en lloc de la mitjana d iostat.
$ sudo biolatency-bpfcc -D 300 1 > /tmp/lat-planificador.txt

# 3. On es bloqueja el proces: xifratge (CPU) o E/S (espera)
$ sudo timeout 60 offcputime-bpfcc -p $(pgrep -f 'restic backup') -f \
    | sort -k2 -rn | head -5
$ sudo perf stat -p $(pgrep -f 'restic backup') -- sleep 30 2>&1 \
    | grep -E 'CPUs utilized'

Com aïllaria la variable. Aquest és el nucli de l'exercici, i hi ha quatre fonts de contaminació que cal neutralitzar:

# a) Dades identiques a totes dues execucions. Una copia incremental de
#    restic depen del que hagi canviat: dues nits diferents NO son
#    comparables. Solucio: partir de la mateixa instantania a les dues proves.
$ sudo restic -r "$REPO" snapshots --latest 1
$ sudo lvcreate -L 4G -s -n prova-io /dev/vg-dades/lv-backups

# b) Estat de la memoria cau de pagina identic. Una segona execucio es mes
#    rapida nomes perque les dades ja son a la memoria cau.
$ sync && echo 3 | sudo tee /proc/sys/vm/drop_caches

# c) Sense carrega concurrent. La copia real corre a les 02:30 amb ionice;
#    per a l experiment cal treure tant la carrega com l ionice,
#    perque ionice interactua amb el planificador i confondria el resultat.
$ sudo systemctl stop tramontana.service   # nomes a la VM de proves
$ sudo ss -tn state established | wc -l

# d) Diverses repeticions, no una. La variabilitat d E/S en una VM es alta.
$ for planificador in none mq-deadline bfq; do
      for repeticio in 1 2 3; do
          echo "$planificador" | sudo tee /sys/block/sda/queue/scheduler >/dev/null
          sync && echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
          inici=$(date +%s)
          sudo restic -r /srv/tramontana/backups/restic backup \
              --quiet /opt/tramontana/releases/3.2.1
          printf '%s\t%s\t%s\n' "$planificador" "$repeticio" "$(( $(date +%s) - inici ))"
      done
  done | tee /tmp/experiment-planificador.tsv

I una decisió de disseny important: l'experiment es fa a srv-tramontana-proves, no en producció. És la VM que crearàs a la lliçó següent, i aquest és exactament el cas d'ús que la justifica: canviar el planificador d'E/S en calent al servidor real, amb drop_caches i sense ionice, és demanar una degradació del servei.

Per què el resultat podria no ser concloent. Cinc motius, i són els que fan que aquest sigui un bon exercici:

  1. El planificador que importa és el del dispositiu físic, no el de la pila. /srv/tramontana/backups és dm-1 (LUKS) sobre lv-backups (LVM) sobre sda. Els dispositius dm-* no tenen planificador propi: passen les peticions cap avall. Es comprova:

    $ cat /sys/block/dm-1/queue/scheduler
    none
    

    Un none fix, sense alternatives entre claudàtors, indica que aquell dispositiu no planifica. Així que l'experiment només té sentit sobre sda.

  2. Som en una màquina virtual. El planificador de l'hoste opera sobre un disc que en realitat és un fitxer a l'amfitrió, que té el seu propi planificador i la seva pròpia memòria cau. Les decisions de l'hoste poden quedar completament anul·lades per l'amfitrió. Això és el que probablement farà que el resultat no sigui concloent, i és la raó per la qual virtual-guest és el perfil de tuned actiu.

  3. El coll podria ser el xifratge, no l'E/S. Si perf stat dona CPUs utilized a prop d'1,00 i offcputime assenyala funcions d'AES, el procés està limitat per CPU i el planificador d'E/S és irrellevant per definició. Aquest control cal fer-lo abans de l'experiment: si el coll és el xifratge, l'experiment no escau.

  4. La còpia és majoritàriament escriptura seqüencial, i allà les diferències entre planificadors són petites. On se separen de debò és en la barreja de lectura aleatòria i escriptura amb diversos processos competint.

  5. restic deduplica i comprimeix, així que la relació entre dades llegides i blocs escrits no és fixa. Encara que les dades d'origen siguin idèntiques, la càrrega d'E/S pot variar entre execucions.

Conclusió del disseny. L'experiment s'ha de fer, però amb l'expectativa correcta: la hipòtesi més probable és que el planificador no influeixi de forma mesurable en aquesta màquina, i el pas 3 —mesurar si el coll és CPU o E/S— probablement ho demostrarà abans de començar. Això no és un fracàs: és refutar una hipòtesi per 30 minuts de feina, i evita afegir una regla d'udev que no fa res.

I si el pas 3 mostra que el coll és el xifratge, la línia d'investigació correcta és una altra: comprovar si la CPU exposa AES-NI a l'hoste (grep -m1 aes /proc/cpuinfo) i, si no, activar host-passthrough a la definició de la VM. Això pot multiplicar per cinc o deu la velocitat del xifratge, i és un canvi de virtualització, no de nucli. Just el terreny de la lliçó següent.

Conclusió

Saps ajustar el nucli, i —més important— saps quan no ajustar-lo. Tens el mapa dels paràmetres que de debò importen: la memòria virtual amb swappiness i els llindars de pàgines brutes, la pila de xarxa amb les seves dues cues de connexió i el control de congestió, els límits de fitxers amb el parany de limits.conf que no s'aplica als serveis de systemd, el planificador d'E/S amb la regla de none per a NVMe i el seu perquè, i les pàgines enormes transparents que tota base de dades demana en madvise. Has vist els mòduls, modprobe.d amb install ... /bin/true —tancant de passada una fila pendent de la llista de comprovació de 06-06—, DKMS, i una resposta honesta a compilar el nucli: gairebé mai en producció, perquè treu la màquina del sistema de paquets i amb ell de les actualitzacions de seguretat.

Però el que t'endus d'aquesta lliçó no és una llista de paràmetres. És el procediment: mesurar, formular una hipòtesi, canviar una cosa amb sysctl -w, mesurar un altre cop, i documentar amb la mesura i la data o revertir. Aplicant-lo, de tres hipòtesis raonables sobre la xarxa de srv-tramontana dues van quedar refutades —somaxconn no hi intervé perquè l'aplicació demana backlog=128, i swappiness no fa res sense activitat d'intercanvi— i només una, BBR, va resultar tenir efecte mesurable: de 77 a 185 Mbit/s. Aquella proporció és el normal, i la secció «NO TOQUEM, i consta per què» del teu 70-rendiment.conf és el que impedirà que el folklore torni a entrar d'aquí a sis mesos.

Fixa't on t'ha deixat l'últim exercici. Per provar el planificador d'E/S sense degradar el servei calia una màquina on buidar la memòria cau, treure l'ionice i aturar l'aplicació sense que ningú se n'assabentés. Per mesurar si el xifratge de LUKS va per programari calia canviar la configuració de CPU de la VM. I el desplegament 3.3.0 que no va arrencar i va provocar una reversió al Mòdul 4 no es va arribar a diagnosticar mai, perquè no hi havia on reproduir-lo. Tot apunta al mateix: falta un entorn de proves, i el curs fa quatre mòduls que el troba a faltar.

A la lliçó 07-04: Virtualització amb Linux el construeixes. Entendràs què és virtualitzar de debò —els tres models, el paper exacte de KVM com a mòdul que converteix Linux en hipervisor, i la seva relació amb QEMU i libvirt—, manejaràs màquines amb virsh i virt-install, aprovisionaràs sense intervenció amb cloud-init, triaràs entre raw i qcow2 sabent per què, faràs instantànies recordant que una instantània no és una còpia de seguretat, i connectaràs la xarxa per NAT o per pont segons convingui —inclosa l'explicació d'aquell virbr0 sense enllaç que costava 35 segons d'arrencada a 07-01—. Al final tindràs srv-tramontana-proves, clonat del servidor real: el lloc on provar els canvis de nucli d'aquesta lliçó, reproduir el desplegament que va fallar, i assajar la recuperació de l'arrencada de 07-01 sense arriscar res. I de passada veuràs la pila de virtualització des de dins, que és el que fa que la lliçó següent —contenidors— s'entengui per contrast i no per analogia.

Curs de Linux: De Principiant a Administrador de Sistemes

Mòdul 1: Introducció a Linux

Mòdul 2: Comandes Bàsiques de Linux

Mòdul 3: Habilitats Avançades en la Línia de Comandes

Mòdul 4: Scripting en Shell

Mòdul 5: Administració del Sistema

Mòdul 6: Xarxes i Seguretat

Mòdul 7: Temes Avançats

Mòdul 8: Projectes Pràctics

© Copyright 2026. Tots els drets reservats