domingo, 21 de agosto de 2016

Write Ahead Log + comprender postgresql.conf: checkpoint_segments, checkpoint_timeout, checkpoint_warning - WRITE AHEAD LOG + UNDERSTANDING POSTGRESQL.CONF: CHECKPOINT_SEGMENTS, CHECKPOINT_TIMEOUT, CHECKPOINT_WARNING

Esta entrada es una traducción de https://www.depesz.com/2011/07/14/write-ahead-log-understanding-postgresql-conf-checkpoint_segments-checkpoint_timeout-checkpoint_warning/
La traducción puede contener errores.
This entry is a translation of https://www.depesz.com/2011/07/14/write-ahead-log-understanding-postgresql-conf-checkpoint_segments-checkpoint_timeout-checkpoint_warning/
The translation may contain errors.

Aunque hay algunos documentos sobre ello, yo decidí escribir sobre ello, en tal vez un lenguaje más accesible - no como un desarrollador, sino como un usuario de Postgresql.

Algunas partes (partes bastante grandes) fueron descritas en uno (N. del T.: traducción al español) de mis anteriores posts, pero yo intentaré concentrarme en WAL en sí mismo, y mostraré un poco más aquí.

Antes de que yo vaya sobre cómo es escrito, y cómo comprobar varias cosas, primero vamos a intentar pensar una pregunta simple: ¿por qué existe esta cosa? ¿Qué hace?

Imagina que tú tienes un fichero de 1GB, y que tú quieres cambiar 100KB de él, desde un desplazamiento (offset) definido.

Teóricamente es sencillo - abre el fichero para escribir (sin opción de sobreescribir), haz un fseek() a la posición apropiada, y escribe los nuevos 100KB. Después cierra el fichero, y abre una cerveza - trabajo hecho.

¿Pero lo está? ¿Qué ocurrirá si la energía se va mientras se está haciendo la escritura? ¿Y si tú no tienes UPS?

Esta es realmente una muy mala situación - asumamos que ocurrió en medio del proceso. Tu programa fue detenido (bien, la máquina fue apagada), y los datos en el disco contienen mitad de datos antiguos y mitad de datos nuevos.

Por su puesto tú podrías argumentar que esto es por lo que nosotros tenemos UPSes, pero realmente es un poco más complicado - varios discos o controladoras de almacenamiento tienen cache de escritura, y pueden mentir al sistema operativo (el cual entonces miente a la aplicación) que los datos han sido escritos, mientras que están en caché, y entonces el problema puede ocurrir de nuevo.

Así, la solución que tiene que ser encontrada asegurará que nosotros tendremos o los datos nuevos, o los datos viejos, pero no una mezcla de ambos.

La solución aquí es relativamente simple. Aparte de este fichero de datos de 1GB, almacena un fichero adicional, que nunca es sobreescrito, sólo es añadido. Y cambia tu proceso a:

abre el fichero de registro (log), en modo añadir
escribe al fichero de registro la información "escribiras estos datos (aquí van los datos) a este fichero (ruta) en el desplazamiento (desplazamiento)"
cierra el fichero de registro
asegúrate que el fichero de registro es realmente escrito a disco. Llama a fsync() y espera que los discos lo hagan correctamente
cambia el fichero de datos normalmente
marca la operación en el fichero de registro como hecha

La última parte puede ser simplemente hecha almacenando la localización de alguna parte del último cambio aplicado del fichero de log.

Ahora. Pensemos sobre el fallo de energía. ¿Qué ocurrirá si el fallo de energía ocurriera cuando se está escribiendo el fichero de registro? Nada. Los datos en el fichero real no están dañados, y tu programa tiene que ser bastante inteligente para ignorar las entradas de registro que no están completamente escritas. ¿Y qué ocurrirá si la caída de energía ocurriera durante el cambio de datos en tu fichero real? Es simple - en el siguiente arranque de tu programa, tú compruebas el registro para cambios que deben de ser aplicados, pero no lo están, y tú los aplicas - cuando le programa es arrancado el contenido del fichero de datos está roto, pero es arreglado rápido.

¿Y si la energía se fuera cuando se está marcando la operación como hecha? Sin problema - en el siguiente arranque la operación será simplemente rehecha.

Gracias a esto nosotros estamos (razonablemente) seguros de tales cosas. Esto además tiene otros beneficios, pero esto vendrá más adelante.

Así, ahora que nosotros sabemos cuál es el propósito del WAL (en caso de que no esté claro: protege tus datos de fallos de hardware/energía), pensemos sobre cómo.

Postgresql use las llamadas "páginas" (pages). Todas las cosas aparte, la página es simplemente 8KB de datos. Esto es por lo que los ficheros de tabla/índice tienen siempre tamaños divisibles por 8192 (8KB) - tú no puedes tener una tabla que sea 10.5 páginas en tamaño. Es de 11 páginas. No todas las páginas están llenas. No todas son incluso garantizadas para ser usadas (ellas pueden contener datos que fueron eliminados).

Todas las operaciones de IO usan páginas.  Es decir, para conseguir el valor INT4 de una tabla, postgresql lee 8KB de datos (al menos).

Así, ¿qué ocurre cuando tú INSERTAS (INSERT) una nueva fila? Primero postgres encuentra a qué página la pondrá. Puede ser una nueva página creada si todas las páginas de la tabla están completas, o puede ser alguna otra página si hay espacio libre en ellas.

Después de que la página ha sido elegidas Postgres la carga a memoria (shared_buffers), y hace los cambios allí. La información de todos los cambios es registrada a WAL, aunque esto es hecho mediante un simple "write()" (sin llamar a fsync, aún), así esto es muy rápido.

Ahora, cuando tú emites un COMMIT; (que podría ocurrir automáticamente si es una conexión con auto-commit), Postgres realmente hace fsync() al WAL. Y esto es todo. Los datos no están escritos a tu fichero de tabla.

En este momento, los cambios que tú quisiste (la nueva fila) están en 2 lugares:

copia modificada de la página de la tabla en shared_buffers
registro en WAL con la información sobre el cambio

Todas las escrituras al WAL son hechas no por backends (N. del T.: procesos en segundos plano) individuales, sin por procesos especializados - wal write, el cual tú puedes ver por ejemplo aquí:

=$ ps uxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
pgdba    25473  0.0  0.0 112552  2104 ?        S    01:16   0:00 sshd: pgdba@pts/8
pgdba    25474  0.0  0.0  28000  6704 pts/8    Ss   01:16   0:00  \_ -bash
pgdba    25524  0.0  0.0  20520  1148 pts/8    R+   01:16   0:00      \_ ps uxf
pgdba     7366  0.0  0.0  67716  7492 ?        S    Jul13   0:00 /home/pgdba/work/bin/postgres
pgdba     7368  0.0  0.0  25308   888 ?        Ss   Jul13   0:01  \_ postgres: logger process
pgdba     7370  0.5  0.2  67936 36176 ?        Ss   Jul13   0:55  \_ postgres: writer process
pgdba     7371  0.0  0.0  67716  2072 ?        Ss   Jul13   0:08  \_ postgres: wal writer process
pgdba     7372  0.0  0.0  68824  5588 ?        Ss   Jul13   0:00  \_ postgres: autovacuum launcher process
pgdba     7373  0.0  0.0  27404   900 ?        Ss   Jul13   0:00  \_ postgres: archiver process   last was 000000010000000800000057
pgdba     7374  0.0  0.0  27816  1340 ?        Ss   Jul13   0:01  \_ postgres: stats collector process

Gracias a esto no hay bloqueos en las escrituras al WAL, así de simple, añadiendo en plano datos.

Ahora, si nosotros continuáramos el proceso durante un tiempo largo, nosotros tendríamos un montón de páginas modificadas en memoria, y montones de registros en WAL.

Así, ¿cuándo están los datos escritos a las páginas de disco reales de las tablas?

Dos situaciones:
swap (intercambio) de páginas
checkpoint

El intercambio de páginas es un proceso muy simple - asumamos que nosotros teníamos shared_buffers configurado a 10, y todos estos buffers están tomados por 10 páginas diferentes, y todas están modificadas. Y ahora, debido a la actividad del usuario Postgres tiene que cargar otra página para obtener datos de ella. ¿Qué ocurrirá? Es simple - una de las páginas será desalojada de la memoria, y la nueva página será cargada. Si la página que fue eliminada estaba "sucia" ("dirty") (lo que significa que había cambios en ella que no habían sido guardados aún al fichero de tablas), será primero escrita al fichero de tabla.

El checkpoint es mucho más interesante. Antes de que vayamos dentro de lo que es, pensemos sobre el escenario teórico. Tú tienes una base de datos que es de 1GB de tamaño, y tu servidor tiene 10GB de RAM. Claramente tú puedes mantener todas las páginas de la base de datos en memoria, así que el intercambio de páginas nunca ocurre.

¿Qué ocurrirá si tú permitieras a la base de datos ejecutarse, con escrituras, cambios, inserciones, durante largo tiempo? Teóricamente todo estaría ok - todos los cambios serían registrados a WAL, y las páginas de memoria serían modificadas, todo bien. Ahora imagina, que después de 24 horas, el sistema es detenido - de nuevo - fallo de energía.

En el siguiente arranque Postgresql tendrá que leer, y aplicar, todos los cambios desde los segmentos WAL que ocurrieron en las últimas 24 horas. Eso es un montón de trabajo, y esto provocaría al arranque de Postgres tomar muuuuuuuucho tiempo.

Para resolver este problema, nosotros tenemos los checkpoints. Estos ocurren habitualmente automáticamente, pero tú puedes forzarlos para ocurrir a voluntad, lanzando el comando CHECKPOINT.

Así, ¿qué es el checkpoint? Checkpoint hace una cosa muy simple: escribe todas las páginas sucias desde la memoria al disco, las marca como "limpias" ("clean") en shared_buffers, y almacena la información que todos los wal que ahora están aplicados. Esto ocurre sin ningún bloqueo por supuesto. Así, la información inmediata desde aquí, es que la cantidad de trabajo que el nuevamente arrancado Postgresql tiene que hacer es relativa a cuánto tiempo pasó antes del último CHECKPONT y el momento en el que Postgresql se paró.

Esto nos trae de vuelva a - cuándo ocurre. Los checkpoints manuales no son comunes, habitualmente uno no piensa sobre ello - ocurre en segundo plano. ¿Cómo sabe Postgresql cuándo hacer checkpoint, entonces? Simple, gracias a dos parámetros de configuración:

checkpoint_segments
checkpoint_timeout

Y aquí, nosotros tenemos que aprender un poco sobre segmentos.

Tal y como yo escribí antes - WAL es (en teoría) un fichero infinito, que obtiene sólo nuevos datos (añadidos), y nunca sobreescribe.

Mientras esto es bonito en la teoría, la práctica es un poco más compleja. Por ejemplo - no hay de verdad ningún uso para los datos de WAL que fueron registrados antes del último checkpoint. Y los ficheros de tamaño infinito son (al menos por ahora) no posibles.

Los desarrolladores de Postgresql decidieron segmentar este WAL infinito en segmentos. Cada segmento tiene su número consecutivo, y es de 16MB en tamaño. Cuando un segmento se llena, Postgresql simplemente conmuta al siguiente.

Ahora, que nosotros sabemos qué son los segments nosotros podemos comprender qué hay acerca de checkpoint_segments. Es un número (por defecto: 3) que significa: si ese cantidad de segmentos fue llenada desde el último checkpoint, emite un nuevo checkpoint.

Con los valores por defecto, esto significa que si tú insertas datos que toman (en formato Postgresql) 6144 páginas (16MB de segmento es 2048 páginas, así que 3 segmentos son 6144 páginas) - automáticamente emitiría un checkpoint.

Segundo parámetro - checkpoint_timeout, es un intervalo de tiempo (por defecto a 5 minutos), y si este tiempo pasa desde el último checkpoint - un nuevo checkpoint será emitido. Tiene que entenderse que (generalmente) cuanto más a menudo hagas los checkpoint, menos invasivos son.

Esto viene de un hecho simple - generalmente, con el paso del tiempo, más y más páginas diferentes están sucias. Si hicieras checkpoint cada minuto - sólo las páginas del minuto tendrían que ser escritas a disco. 5 minutos - más páginas. 1 hora - incluso más páginas.

Mientras que hacer checkpoint no bloquea nada, tiene que entenderse que el checkpoint de (por ejemplo) 30 segmentos, causará escritura de alta intensidad de 480 MB de datos a disco. Y esto puede causar algunos relantizaciones para lecturas concurrentes.

Hasta aquí, yo espero, esto es muy claro.

Ahora, la siguiente parte del rompecabezas - segmentos de wal.

Estos ficheros (segmentos de wal) residen en el directorio pg_xlog/ del PGDATA de Postgresql:

=$ ls -l data/pg_xlog/
total 131076
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:39 000000010000000800000058
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:03 000000010000000800000059
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:03 00000001000000080000005A
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:04 00000001000000080000005B
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:04 00000001000000080000005C
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:04 00000001000000080000005D
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:06 00000001000000080000005E
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:06 00000001000000080000005F
drwx------ 2 pgdba pgdba     4096 2011-07-14 01:14 archive_status/

Cada nombre segmento contiene 3 bloques de 8 digitos en hexadecimal. Por ejemplo: 00000001000000080000005C significa:

00000001 – línea de tiempo 1
00000008 – 8º bloque
0000005C – segmento hexadecimal (5C) dentro del bloque

La última parte va sólo desde 00000000 a 000000FE (no FF!).

La 2ª parte del fichero más los 2 caracteres al final de la 3ª parte nos da la localización en este teórico fichero WAL infinito.

Dentro de Postresql nosotros podemos siempre comprobar cuál es la localización WAL actual:

$ select pg_current_xlog_location();
 pg_current_xlog_location
--------------------------
 8/584A62E0
(1 row)

Esto significa que nosotros estamos usando ahora el fichero xxxxxxxx000000080000058, y Postgresql está escribiendo en el desplazamiento 4A62E0 en él - el cual es 4874976, el cual, como el segmento de WAL es 16MB significa que el segmento de wal está lleno en ~ 25% ahora.

La cosa más misteriosa es la línea de tiempo. La línea de tiempo arranca desde 1, y se incrementa (por uno) cada vez que tú haces WAL-esclavo (WAL-slave) desde el servidor, y este esclavo es promocionado a autónomo (standalone).

Todo esta información nosotros podemos también conseguirla usando el programa pg_controldata:

=$ pg_controldata data
pg_control version number:            903
Catalog version number:               201107031
Database system identifier:           5628532665370989219
Database cluster state:               in production
pg_control last modified:             Thu 14 Jul 2011 01:49:12 AM CEST
Latest checkpoint location:           8/584A6318
Prior checkpoint location:            8/584A6288
Latest checkpoint's REDO location:    8/584A62E0
Latest checkpoint's TimeLineID:       1
Latest checkpoint's NextXID:          0/33611
Latest checkpoint's NextOID:          28047
Latest checkpoint's NextMultiXactId:  1
Latest checkpoint's NextMultiOffset:  0
Latest checkpoint's oldestXID:        727
Latest checkpoint's oldestXID's DB:   1
Latest checkpoint's oldestActiveXID:  33611
Time of latest checkpoint:            Thu 14 Jul 2011 01:49:12 AM CEST
Minimum recovery ending location:     0/0
Backup start location:                0/0
Current wal_level setting:            hot_standby
Current max_connections setting:      100
Current max_prepared_xacts setting:   0
Current max_locks_per_xact setting:   64
Maximum data alignment:               8
Database block size:                  8192
Blocks per segment of large relation: 131072
WAL block size:                       8192
Bytes per WAL segment:                16777216
Maximum length of identifiers:        64
Maximum columns in an index:          32
Maximum size of a TOAST chunk:        1996
Date/time type storage:               64-bit integers
Float4 argument passing:              by value
Float8 argument passing:              by value

Esto tiene alguna información interesante - por ejemplo la localización (en el fichero infinito de WAL) del último checkpoint, previo checkpoint, y localización REDO.

La localización REDO es muy importante - este es el lugar en el WAL desde el que Postgresql tendrá que leer si es matado, y rearrancado.

Los valores de encima no varían mucho porque este es mi sistema de pruebas el cual no tiene ningún tráfico ahora, pero nosotros podemos verlo en otra máquina:

=> pg_controldata data/
...
Latest checkpoint location:           623C/E07AC698
Prior checkpoint location:            623C/DDD73588
Latest checkpoint's REDO location:    623C/DE0915B0
...

La última cosa que es importante es para comprender qué ocurre con los segmentos de WAL obsoletos, y cómo "nuevos" segmentos de wal son creados.

Permíteme mostrarte una cosa de nuevo:

=$ ls -l data/pg_xlog/
total 131076
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:39 000000010000000800000058
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:03 000000010000000800000059
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:03 00000001000000080000005A
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:04 00000001000000080000005B
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:04 00000001000000080000005C
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:04 00000001000000080000005D
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:06 00000001000000080000005E
-rw------- 1 pgdba pgdba 16777216 2011-07-14 01:06 00000001000000080000005F
drwx------ 2 pgdba pgdba     4096 2011-07-14 01:14 archive_status/

Esto estaba en el sistema sin escrituras, y localización REDO 8/584A62E0.

Ya que el arranque de Postgresql necesitará leer desde esta localización, todos los segmentos de WAL anteriores a 000000010000000800000058 (por ejemplo 000000010000000800000057, 000000010000000800000056 y así) están obsoletos.

En el otro lado - por favor observa que nosotros tenemos listos 7 ficheros para uso futuro.

Postgresql trabaja de esta forma: si los segmentos WAL se quedan obsoletos (por ejemplo la localización REDO es posterior en WAL que este segmento) el fichero es renombrado. Esto es correcto. No es eliminado, es renombrado. ¿Renombrado a qué? Al siguiente fichero en WAL. Así cuando yo haga algunas esctiruas, y después haya checkpoint en la localización 8/59*, el fichero 000000010000000800000058 será renombrado a 000000010000000800000060.

Este es una de las razones por las que tus checkpoint_segments no deben ser demasiado bajos.

Pensemos por un momento sobre qué ocurriría si nosotros tuviéramos un checkpoint_timeout muy largo, y nosotros llenáramos todos los checkpoint_segments. Para grabar una nueva escritura Postgresql tendría que o hacer un checkpoint (lo cual hará), pero al mismo tiempo - no tendría más sementos listos dejados para uso. Así tendría que crear un nuevo fichero. Nuevo fichero, 16MB de datos (\x00 probablemente) - tendría que ser escrito a disco antes de que Postgresql pudiera escribir cualquier cosa que el usuario solicitara. Lo cual significa que si tú nunca alcanzaras checkpoint_segments la actividad de usuario concurrente se ralentizaría, porque postgresql tendrá que crear nuevos ficheros para alojar nuevas escrituras de datos solicitadas por los usuarios.

Habitualmente no es un problema, tú sólo configura checkpoint_segments a un número relativamente alto, y estás listo.

De todas formas. Cuando mirando en el directorio pg_xlog, los segmentos de WAL actuales (el que consigue las escrituras) está habitualmente en algún lugar en el medio. Lo cual pueda causar alguna confusión, porque el mtime (N. del T.: fecha y hora de modificación) de los ficheros no cambiarán en la misma dirección como los números de los ficheros. Como aquí:

$ ls -l
total 704512
-rw------- 1 postgres postgres 16777216 Jul 13 16:51 000000010000002B0000002A
-rw------- 1 postgres postgres 16777216 Jul 13 16:55 000000010000002B0000002B
-rw------- 1 postgres postgres 16777216 Jul 13 16:55 000000010000002B0000002C
-rw------- 1 postgres postgres 16777216 Jul 13 16:55 000000010000002B0000002D
-rw------- 1 postgres postgres 16777216 Jul 13 16:55 000000010000002B0000002E
-rw------- 1 postgres postgres 16777216 Jul 13 16:55 000000010000002B0000002F
-rw------- 1 postgres postgres 16777216 Jul 13 16:55 000000010000002B00000030
-rw------- 1 postgres postgres 16777216 Jul 13 17:01 000000010000002B00000031
-rw------- 1 postgres postgres 16777216 Jul 13 17:16 000000010000002B00000032
-rw------- 1 postgres postgres 16777216 Jul 13 17:21 000000010000002B00000033
-rw------- 1 postgres postgres 16777216 Jul 13 14:31 000000010000002B00000034
-rw------- 1 postgres postgres 16777216 Jul 13 14:32 000000010000002B00000035
-rw------- 1 postgres postgres 16777216 Jul 13 14:19 000000010000002B00000036
-rw------- 1 postgres postgres 16777216 Jul 13 14:36 000000010000002B00000037
-rw------- 1 postgres postgres 16777216 Jul 13 14:37 000000010000002B00000038
-rw------- 1 postgres postgres 16777216 Jul 13 14:38 000000010000002B00000039
-rw------- 1 postgres postgres 16777216 Jul 13 14:39 000000010000002B0000003A
-rw------- 1 postgres postgres 16777216 Jul 13 14:40 000000010000002B0000003B
-rw------- 1 postgres postgres 16777216 Jul 13 14:41 000000010000002B0000003C
-rw------- 1 postgres postgres 16777216 Jul 13 14:41 000000010000002B0000003D
-rw------- 1 postgres postgres 16777216 Jul 13 14:42 000000010000002B0000003E
-rw------- 1 postgres postgres 16777216 Jul 13 14:43 000000010000002B0000003F
-rw------- 1 postgres postgres 16777216 Jul 13 14:33 000000010000002B00000040
-rw------- 1 postgres postgres 16777216 Jul 13 14:34 000000010000002B00000041
-rw------- 1 postgres postgres 16777216 Jul 13 14:45 000000010000002B00000042
-rw------- 1 postgres postgres 16777216 Jul 13 14:55 000000010000002B00000043
-rw------- 1 postgres postgres 16777216 Jul 13 14:55 000000010000002B00000044
-rw------- 1 postgres postgres 16777216 Jul 13 14:55 000000010000002B00000045
-rw------- 1 postgres postgres 16777216 Jul 13 14:55 000000010000002B00000046
-rw------- 1 postgres postgres 16777216 Jul 13 14:55 000000010000002B00000047
-rw------- 1 postgres postgres 16777216 Jul 13 14:55 000000010000002B00000048
-rw------- 1 postgres postgres 16777216 Jul 13 15:09 000000010000002B00000049
-rw------- 1 postgres postgres 16777216 Jul 13 15:25 000000010000002B0000004A
-rw------- 1 postgres postgres 16777216 Jul 13 15:35 000000010000002B0000004B
-rw------- 1 postgres postgres 16777216 Jul 13 15:51 000000010000002B0000004C
-rw------- 1 postgres postgres 16777216 Jul 13 15:55 000000010000002B0000004D
-rw------- 1 postgres postgres 16777216 Jul 13 15:55 000000010000002B0000004E
-rw------- 1 postgres postgres 16777216 Jul 13 15:55 000000010000002B0000004F
-rw------- 1 postgres postgres 16777216 Jul 13 15:55 000000010000002B00000050
-rw------- 1 postgres postgres 16777216 Jul 13 15:55 000000010000002B00000051
-rw------- 1 postgres postgres 16777216 Jul 13 15:55 000000010000002B00000052
-rw------- 1 postgres postgres 16777216 Jul 13 16:19 000000010000002B00000053
-rw------- 1 postgres postgres 16777216 Jul 13 16:35 000000010000002B00000054
drwx------ 2 postgres postgres       96 Jun  4 23:28 archive_status

Por favor observa que el fichero más nuevo - 000000010000002B00000033 no es ni el primero, ni el último. Y el fichero más viejo - está cerca despúes del más nuevo - 000000010000002B00000036.

Esto es todo natural. Todos los ficheros antes del actual, son los que aún son necesitados, y sus mtimes irán en la misma dirección como la numeración de los segmentos de WAL.

El último fichero (basado en nombre de ficheros) - *54 tiene un mtime justo antes de *2A - lo cual nos dice que fue previamente *29, pero que fue renombrado cuando la localización REDO movió a algún lugar al fichero *2A.

Espero que esté claro desde explicación de arriba, si no - por favor expresa tus preguntas/asuntos en los comentarios.

Así, para envolverlo. WAL existe para guardar tu tocino en caso de emergencia. Gracias a WAL es muy difícil conseguir ningún problema con los datos - yo incluso diría que imposible, pero es aún posible en caso de mal comportamiento de hardware - como: mentiras sobre las escrituras de disco actuales.

WAL es almacenado en un número de ficheros en el directorio pg_xlog/, y los ficheros son reutilizados, así el directorio no debe de crecer. El número de estos ficheros es habitualmente 2 * checkpoint_segments + 1.

¿Espera? ¿Por qué 2 * checkpoint_segments?

La razón es muy simple. Asumamos que tú tienes checkpoint_segments configurado a 5. Tú los llenaste todos, y el checkpoint es llamado. Checkpoint es llamado en el segmento WAL #x. En #x + 5 nosotros tendremos otro checkpoint. Pero Postgresql siempre mantiene (al menos) checkpoint_segments por delante de la actual localización, para evitar necesitar crear nuevos segmentos para datos desde las consultas de los usuarios. Así, en cualquier momento dado, tú podrías tener:

segmento actual
segmentos checkpoint_segments, desde la localización REDO
"buffer" de checkpoint_segments en frente de la localización actual

Algunas veces, cuando tú tienes más escrituras checkpoint_segments, in tal caso Postgresql creará nuevos segmentos (como yo describí arriba). Lo cual inflará el número de ficheros en el directorio pg_xlog/. Pero esto será restaurado después de algún tiempo - simplemente algunos segmentos obsoletos no será renombrados, aunque en cambio serán eliminados.

Finalmente, la última cosa. GUC (N. del T.: GUC es es el acrónimo de Grand Unified Configuration Setting, es decir, las configuraciones de postgresl, como las que se ponen en postgresql.conf) "checkpoint_warning". Esto es también (como checkpoint_timeout) un intervalor, habitualmente mucho más corto - por defecto 30 segundos. Esto es usado para registrar (no a WAL, sino al registro normal) información si los checkpoint automáticos ocurren demasiado a menudo.

Como checkpoint_timeout es supuestamente más grande que el checkpoint_warning, esto normalmente significa que alerta si tú llenaste más cantidad de checkpoint_segments de registro en el tiempo del checkpoint_timeout.

Tal información se parece a esta:

2011-07-14 01:03:22.160 CEST @ 7370  LOG:  checkpoint starting: xlog
2011-07-14 01:03:26.175 CEST @ 7370  LOG:  checkpoint complete: wrote 1666 buffers (40.7%); 0 transaction log file(s) added, 0 removed, 3 recycled; write=3.225 s, sync=0.720 s, total=4.014 s; sync files=5, longest=0.292 s, average=0.144 s
2011-07-14 01:03:34.904 CEST @ 7370  LOG:  checkpoints are occurring too frequently (12 seconds apart)
2011-07-14 01:03:34.904 CEST @ 7370  HINT:  Consider increasing the configuration parameter "checkpoint_segments".
2011-07-14 01:03:34.904 CEST @ 7370  LOG:  checkpoint starting: xlog
2011-07-14 01:03:39.239 CEST @ 7370  LOG:  checkpoint complete: wrote 1686 buffers (41.2%); 0 transaction log file(s) added, 0 removed, 3 recycled; write=3.425 s, sync=0.839 s, total=4.334 s; sync files=5, longest=0.267 s, average=0.167 s
2011-07-14 01:03:48.077 CEST @ 7370  LOG:  checkpoints are occurring too frequently (14 seconds apart)
2011-07-14 01:03:48.077 CEST @ 7370  HINT:  Consider increasing the configuration parameter "checkpoint_segments".
2011-07-14 01:03:48.077 CEST @ 7370  LOG:  checkpoint starting: xlog

Por favor, observa las líneas "HINT".

Esas son sólo consejos (no es un aviso, o fatalidades) porque checkpoint_segments demasiado bajo no causa ningún riesgo a tus datos - eso solo puede ralentizar la interacción con los clientes, si el usuario enviara consultas de modificación, que tendrán que esperar para que nuevos segmentos de WAL sean creados (por ejemplo 16MB escritos a disco).

Como última nota - si tú estás teniendo alguna clase de monitorización de su Postgresql (como cati, o ganglia, o munin o alguno comercial, como circonus) tú podrías querer añadir gráficos que te mostraran tu progreso WAL en el tiempo.

Para hacerlo, tú necesitarías convertir la localización xlog actual a algún número decimal normal, y después dibujar las diferencias. Por ejemplo como esto:

=$ psql -qAtX -c "select pg_current_xlog_location()" | \
    awk -F/ 'BEGIN{print "ibase=16"} {printf "%s%08s\n", $1, $2}' | \
    bc
108018127223360

O, si los números son demasiado grandes, sólo el "número" decimal del fichero:

=$ (
    echo "ibase=16"
    psql -qAtX -c "select pg_xlogfile_name(pg_current_xlog_location())" | \
        cut -b 9-16,23-24
) | bc
6438394

Dibujar incrementos del 2º valor (6438394) en incrementos de 5 minutos te dirá cuál es el checkpoint_segments óptimo (aunque siempre recuerda hacerlo un poco más grande que el realmente necesitado, por si acaso un pico repentino de tráfico).


1 comentario: