Hay varios parámetros de configuración relacionados con el WAL que afectan el rendimiento de la base de datos. Esta sección explica su uso. Consulta Chapter 19 para obtener información general sobre cómo establecer los parámetros de configuración del servidor.
Los Puntos de control (checkpoints) son puntos en la secuencia de transacciones en los que se garantiza que los archivos de datos de heap y de índice se han actualizado con toda la información escrita antes de ese punto de control. En el momento del punto de control, todas las páginas de datos sucias se vacían al disco y se escribe un registro de punto de control especial en el archivo WAL. (Los registros de cambios se vaciaron previamente a los archivos WAL). En caso de un fallo, el procedimiento de recuperación de fallos busca el último registro de punto de control para determinar el punto en el WAL (conocido como el registro de redo) desde el cual debe comenzar la operación REDO. Se garantiza que cualquier cambio realizado en los archivos de datos antes de ese punto ya está en el disco. Por lo tanto, después de un punto de control, los segmentos de WAL que preceden al que contiene el registro de redo ya no son necesarios y se pueden reciclar o eliminar. (Cuando se realiza el archivado de WAL, los segmentos de WAL deben archivarse antes de ser reciclados o eliminados).
El requisito del punto de control de vaciar todas las páginas de datos sucias al disco puede causar una carga de E/S significativa. Por esta razón, la actividad del punto de control se regula para que la E/S comience al inicio del punto de control y se complete antes de que deba comenzar el siguiente punto de control; esto minimiza la degradación del rendimiento durante los puntos de control.
El proceso checkpointer del servidor realiza automáticamente un punto de control de vez en cuando. Se inicia un
punto de control cada checkpoint_timeout segundos, o si max_wal_size
está a punto de excederse, lo que ocurra primero. Los valores por defecto son 5 minutos y 1 GB, respectivamente.
Si no se ha escrito ningún WAL desde el punto de control anterior, se omitirán los nuevos puntos de control
incluso si ha transcurrido checkpoint_timeout. (Si se está utilizando el archivado de WAL y
deseas establecer un límite inferior sobre qué tan a menudo se archivan los archivos para acotar la pérdida
potencial de datos, debes ajustar el parámetro archive_timeout en lugar de los parámetros
del punto de control). También es posible forzar un punto de control utilizando el comando SQL CHECKPOINT.
Reducir checkpoint_timeout y/o max_wal_size hace que los puntos de control
ocurran con más frecuencia. Esto permite una recuperación posterior a un fallo más rápida, ya que se necesitará
rehacer menos trabajo. Sin embargo, se debe equilibrar esto con el costo incrementado de vaciar las páginas de
datos sucias con más frecuencia. Si full_page_writes está activado (como es el valor por defecto),
hay otro factor a considerar. Para garantizar la coherencia de la página de datos, la primera modificación de una
página de datos después de cada punto de control da como resultado el registro de todo el contenido de la página.
En ese caso, un intervalo de punto de control más pequeño aumenta el volumen de salida al WAL, negando parcialmente
el objetivo de utilizar un intervalo más pequeño y, en cualquier caso, provocando más E/S de disco.
Los puntos de control son bastante costosos, en primer lugar porque requieren la escritura de todos los buffers
actualmente sucios y, en segundo lugar, porque provocan un tráfico de WAL subsiguiente adicional como se explicó
anteriormente. Por lo tanto, es aconsejable configurar los parámetros del punto de control lo suficientemente
altos como para que los puntos de control no ocurran con demasiada frecuencia. Como una simple comprobación de
cordura de los parámetros de tus puntos de control, puedes establecer el parámetro checkpoint_warning.
Si los puntos de control ocurren más juntos que checkpoint_warning segundos, se emitirá un mensaje
en el registro del servidor recomendando aumentar max_wal_size. La aparición ocasional de dicho
mensaje no es motivo de alarma, pero si aparece con frecuencia, se deben aumentar los parámetros de control del
punto de control. Las operaciones masivas, como las transferencias grandes de COPY, pueden provocar
la aparición de varias de estas advertencias si no has configurado max_wal_size lo suficientemente
alto.
Para evitar saturar el sistema de E/S con una ráfaga de escrituras de páginas, la escritura de buffers sucios
durante un punto de control se distribuye a lo largo de un período de tiempo. Ese período está controlado por
checkpoint_completion_target, que se expresa como una fracción del intervalo del punto de
control (configurado mediante el uso de checkpoint_timeout). La tasa de E/S se ajusta de modo
que el punto de control finalice cuando haya transcurrido la fracción dada de checkpoint_timeout
segundos, o antes de que se exceda max_wal_size, lo que ocurra primero. Con el valor por defecto de
0.9, se puede esperar que PostgreSQL complete cada punto de control un poco antes del
siguiente punto de control programado (alrededor del 90% de la duración del último punto de control). Esto distribuye
la E/S tanto como sea posible para que la carga de E/S del punto de control sea constante a lo largo del intervalo del
punto de control. La desventaja de esto es que prolongar los puntos de control afecta al tiempo de recuperación,
porque se deberán conservar más segmentos de WAL para su posible uso en la recuperación. Un usuario preocupado por la
cantidad de tiempo necesaria para recuperarse podría desear reducir checkpoint_timeout para que los
puntos de control ocurran con más frecuencia pero aún así distribuyan la E/S a lo largo del intervalo del punto de
control. Alternativamente, se podría reducir checkpoint_completion_target, pero esto daría lugar
a momentos de E/S más intensa (durante el punto de control) y momentos de menor E/S (después de que se complete el
punto de control pero antes del siguiente punto de control programado) y por lo tanto no se recomienda. Aunque
checkpoint_completion_target se podría configurar tan alto como 1.0, normalmente se recomienda no
configurarlo a más de 0.9 (el valor por defecto) ya que los puntos de control incluyen algunas otras actividades
además de escribir buffers sucios. Es muy probable que una configuración de 1.0 dé como resultado que los puntos de
control no se completen a tiempo, lo que provocaría una pérdida de rendimiento debido a la variación inesperada en
la cantidad de segmentos de WAL necesarios.
En plataformas Linux y POSIX, checkpoint_flush_after te permite forzar que las páginas del
sistema operativo escritas por el punto de control se vacíen en el disco después de una cantidad configurable de
bytes. De lo contrario, estas páginas pueden mantenerse en el caché de páginas del sistema operativo, provocando una
pausa cuando se emita fsync al final de un punto de control. Esta configuración a menudo ayudará a
reduccir la latencia de las transacciones, pero también puede tener un efecto adverso en el rendimiento; particularmente
para cargas de trabajo que son mayores que shared_buffers, pero menores que el caché de páginas
del sistema operativo.
La cantidad de archivos de segmento WAL en el directorio pg_wal depende de min_wal_size,
max_wal_size y la cantidad de WAL generada en los ciclos de puntos de control anteriores.
Cuando los archivos de segmento WAL antiguos ya no son necesarios, se eliminan o se reciclan (es decir, se renombran
para convertirse en futuros segmentos en la secuencia numerada). Si, debido a un pico a corto plazo en la tasa de
salida de WAL, se supera max_wal_size, los archivos de segmento innecesarios se eliminarán hasta
que el sistema vuelva a estar por debajo de este límite. Por debajo de ese límite, el sistema recicla suficientes
archivos WAL para cubrir la necesidad estimada hasta el siguiente punto de control y elimina el resto. La estimación
se basa en un promedio móvil de la cantidad de archivos WAL utilizados en ciclos de puntos de control anteriores.
El promedio móvil se incrementa inmediatamente si el uso real supera la estimación, por lo que se adapta al uso pico
en lugar del uso promedio hasta cierto punto. min_wal_size establece un mínimo en la cantidad de
archivos WAL reciclados para uso futuro; esa cantidad de WAL siempre se recicla para uso futuro, incluso si el
sistema está inactivo y la estimación de uso de WAL sugiere que se necesita poco WAL.
Independientemente de max_wal_size, los wal_keep_size megabytes más
recientes de archivos WAL más un archivo WAL adicional se conservan en todo momento. Además, si se utiliza el
archivado de WAL, los segmentos antiguos no se pueden eliminar ni reciclar hasta que se archiven. Si el archivado
de WAL no puede mantener el ritmo al que se genera el WAL, o si archive_command o
archive_library falla repetidamente, los archivos WAL antiguos se acumularán en pg_wal
hasta que se resuelva la situación. Un servidor en espera (standby) lento o fallido que utiliza una ranura de
replicación tendrá el mismo efecto (consulta Section 26.2.6). Del mismo modo, si la
resumulación de WAL (WAL summarization) está habilitada,
los segmentos antiguos se conservan hasta que se resumen.
En la recuperación de archivos o en el modo en espera (standby), el servidor realiza periódicamente puntos
de reinicio (restartpoints),
que son similares a los puntos de control en el funcionamiento normal: el servidor fuerza todo su estado al disco,
actualiza el archivo pg_control para indicar que los datos WAL ya procesados no necesitan ser
escaneados nuevamente y luego recicla los archivos de segmento WAL antiguos en el directorio pg_wal.
Los puntos de reinicio no se pueden realizar con más frecuencia que los puntos de control en el primario porque los
puntos de reinicio sólo se pueden realizar en los registros de puntos de control. Un punto de reinicio puede ser
exigido por un programa o por una solicitud externa. El contador restartpoints_timed en la
vista pg_stat_checkpointer cuenta
los primeros, mientras que el contador restartpoints_req cuenta los segundos. Un punto de
reinicio se activa por programa cuando se alcanza un registro de punto de control si han transcurrido al menos
checkpoint_timeout segundos desde el último punto de reinicio realizado o cuando el intento
anterior de realizar el punto de reinicio ha fallado. En este último caso, el siguiente punto de reinicio se programará
en 15 segundos. Un punto de reinicio se activa por solicitud debido a razones similares a las del punto de control,
pero sobre todo si el tamaño del WAL está a punto de superar max_wal_size. Sin embargo, debido a
las limitaciones sobre cuándo se puede realizar un punto de reinicio, a menudo se supera max_wal_size
durante la recuperación, hasta en un ciclo de punto de control de WAL. (De todos modos, max_wal_size
nunca es un límite estricto, por lo que siempre debes dejar mucho espacio libre para evitar quedarte sin espacio en
el disco). El contador restartpoints_done en la vista
pg_stat_checkpointer cuenta los
puntos de reinicio que realmente se han realizado.
En algunos casos, cuando el tamaño del WAL en el primario aumenta rápidamente, por ejemplo durante un
INSERT masivo, el contador restartpoints_req en el standby puede
mostrar un crecimiento pico. Esto ocurre porque las solicitudes para crear un nuevo punto de reinicio debido al aumento
del consumo de WAL no se pueden realizar porque el registro de punto de control seguro desde el último punto de
reinicio aún no se ha reproducido en el standby. Este comportamiento es normal y no conduce a un aumento en el consumo
de recursos del sistema. Sólo el contador restartpoints_done de los relacionados con los
puntos de reinicio indica que se han gastado recursos notables del sistema.
Hay dos funciones internas de WAL de uso común: XLogInsertRecord y XLogFlush.
XLogInsertRecord se utiliza para colocar un nuevo registro en los buffers de WAL en la memoria
compartida. Si no hay espacio para el nuevo registro, XLogInsertRecord tendrá que escribir
(mover a la caché del kernel) unos pocos buffers de WAL llenos. Esto no es deseable porque XLogInsertRecord
se utiliza en cada modificación de bajo nivel de la base de datos (por ejemplo, inserción de fila) en un momento en
que se mantiene un bloqueo exclusivo en las páginas de datos afectadas, por lo que la operación debe ser lo más rápida
posible. Lo que es peor, la escritura de los buffers de WAL también podría forzar la creación de un nuevo segmento de
WAL, lo que lleva aún más tiempo. Normalmente, los buffers de WAL deben ser escritos y vaciados mediante una solicitud
XLogFlush, que se realiza, en su mayor parte, en el momento de la confirmación de la transacción
para garantizar que los registros de la transacción se vacíen en el almacenamiento permanente. En sistemas con una
alta salida de WAL, las solicitudes de XLogFlush podrían no ocurrir con la suficiente frecuencia
como para evitar que XLogInsertRecord tenga que realizar escrituras. En tales sistemas, se debe
aumentar la cantidad de buffers de WAL modificando el parámetro wal_buffers. Cuando
full_page_writes está activado y el sistema está muy ocupado, configurar wal_buffers
más alto ayudará a suavizar los tiempos de respuesta durante el período inmediatamente posterior a cada punto de control.
El parámetro commit_delay define por cuántos microsegundos dormirá un proceso líder de
confirmación de grupo después de adquirir un bloqueo dentro de XLogFlush, mientras que los
seguidores de confirmación de grupo se ponen en cola detrás del líder. Este retraso permite que otros procesos del
servidor agreguen sus registros de confirmación a los buffers de WAL para que todos ellos sean vaciados por la eventual
operación de sincronización del líder. No ocurrirá ningún sueño si fsync no está habilitado,
o si menos de commit_siblings otras sesiones se encuentran actualmente en transacciones
activas; esto evita dormir cuando es poco probable que alguna otra sesión confirme pronto. Ten en cuenta que en algunas
plataformas, la resolución de una solicitud de sueño es de diez milisegundos, por lo que cualquier configuración de
commit_delay distinta de cero entre 1 y 10000 microsegundos tendría el mismo efecto. Ten en cuenta
también que en algunas plataformas, las operaciones de sueño pueden tomar un poco más de tiempo de lo solicitado
por el parámetro.
Dado que el propósito de commit_delay es permitir que el costo de cada operación de vaciado se
amortice a lo largo de las transacciones que se confirman simultáneamente (potencialmente a expensas de la latencia de
la transacción), es necesario cuantificar ese costo antes de poder elegir la configuración de manera inteligente.
Cuanto mayor sea ese costo, más efectivo se espera que sea commit_delay para incrementar el
rendimiento de las transacciones, hasta cierto punto. El programa pg_test_fsync se puede utilizar para
medir el tiempo promedio en microsegundos que toma una sola operación de vaciado de WAL. Un valor de la mitad del
tiempo promedio que el programa informa que toma vaciar después de una sola operación de escritura de 8 kB suele ser la
configuración más efectiva para commit_delay, por lo que este valor se recomienda como punto de
partida para usar al optimizar para una carga de trabajo particular. Si bien ajustar commit_delay
es particularmente útil cuando el WAL se almacena en discos giratorios de alta latencia, los beneficios pueden ser
significativos incluso en medios de almacenamiento con tiempos de sincronización muy rápidos, como unidades de estado
sólido o matrices RAID con caché de escritura con batería de respaldo; pero esto definitivamente debe probarse frente a
una carga de trabajo representativa. En tales casos, se deben usar valores más altos de commit_siblings,
mientras que valores más pequeños de commit_siblings suelen ser útiles en medios de mayor latencia.
Ten en cuenta que es muy posible que una configuración de commit_delay demasiado alta pueda
aumentar tanto la latencia de la transacción que el rendimiento total de la transacción se vea afectado.
Cuando commit_delay se establece en cero (el valor por defecto), todavía es posible que ocurra una
forma de confirmación de grupo, pero cada grupo constará únicamente de sesiones que alcancen el punto en el que necesiten
vaciar sus registros de confirmación durante la ventana en la que se está produciendo la operación de vaciado anterior
(si la hay). Con un mayor número de clientes, tiende a producirse un “efecto pasarela” (gangway effect),
de modo que los efectos de la confirmación de grupo se vuelven significativos incluso cuando commit_delay
es cero, y por lo tanto, configurar explícitamente commit_delay tiende a ayudar menos. Configurar
commit_delay sólo puede ayudar cuando (1) hay algunas transacciones que se confirman simultáneamente
y (2) el rendimiento está limitado en cierta medida por la tasa de confirmación; pero con una alta latencia de rotación,
esta configuración puede ser eficaz para aumentar el rendimiento de las transacciones con tan sólo dos clientes (es
decir, un único cliente que confirma con una transacción hermana).
El parámetro wal_sync_method determina cómo PostgreSQL le pedirá
al kernel que fuerce las actualizaciones de WAL al disco. Todas las opciones deberían ser iguales
en términos de confiabilidad, con la excepción de fsync_writethrough, que a veces puede forzar un
vaciado de la caché del disco incluso cuando otras opciones no lo hacen. Sin embargo, es bastante específico de la
plataforma cuál de ellas será la más rápida. Puedes probar las velocidades de las diferentes opciones utilizando el
programa pg_test_fsync. Ten en cuenta que este parámetro es irrelevante si fsync
se ha desactivado.
Habilitar el parámetro de configuración wal_debug (siempre que
PostgreSQL haya sido compilado con soporte para ello) dará como resultado que cada llamada
WAL de XLogInsertRecord y XLogFlush se registre en el registro del servidor.
Esta opción podría ser reemplazada por un mecanismo más general en el futuro.
Hay dos funciones internas para escribir datos WAL en el disco: XLogWrite e
issue_xlog_fsync. Cuando track_wal_io_timing está habilitado, los
tiempos totales que XLogWrite escribe e issue_xlog_fsync sincroniza los
datos WAL en el disco se cuentan como write_time y fsync_time en
pg_stat_io para el object wal, respectivamente.
XLogWrite normalmente es llamada por XLogInsertRecord (cuando no hay
espacio para el nuevo registro en los buffers de WAL), XLogFlush y el WAL writer, para escribir
los buffers de WAL en el disco y llamar a issue_xlog_fsync. issue_xlog_fsync
es llamada normalmente por XLogWrite para sincronizar los archivos WAL con el disco. Si
wal_sync_method es open_datasync o bien open_sync, una
operación de escritura en XLogWrite garantiza la sincronización de los datos WAL escritos en el
disco e issue_xlog_fsync no hace nada. Si wal_sync_method es
fdatasync, fsync o fsync_writethrough, la operación de
escritura mueve los buffers de WAL a la caché del kernel e issue_xlog_fsync los sincroniza
con el disco. Independientemente de la configuración de track_wal_io_timing, la cantidad de veces
que XLogWrite escribe e issue_xlog_fsync sincroniza datos WAL en el disco
también se cuentan como writes y fsyncs en pg_stat_io
para el object wal, respectivamente.
El parámetro recovery_prefetch se puede utilizar para reducir los tiempos de espera de
E/S durante la recuperación indicando al kernel que inicie lecturas de bloques de disco que pronto se necesitarán
pero que actualmente no están en el buffer pool de PostgreSQL. Las configuraciones
maintenance_io_concurrency y wal_decode_buffer_size limitan la
concurrencia y la distancia de la prelectura (prefetching), respectivamente. Por defecto, está establecido en
try, lo que habilita la característica en sistemas que admiten la emisión de avisos de prelectura
(read-ahead).