28.5. Configuración del WAL #

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).