Problema con el I/O cuando tenemos muchas bases de datos en un mismo servidor


Hace unos dias instalamos un nuevo servidor PostgreSQL 9.2 que se va a utilizar por diferentes cursos de bases de datos que se imparten en la facultad de informática de la Universidad de Oslo.

La idea es que cada alumno registrado en alguno de estos cursos tenga acceso a su base de datos postgreSQL para realizar sus prácticas.

Hasta aquí todo sin problemas, una instalación que sigue nuestros estándares de configuración, backup, seguridad y alta disponibilidad. La única diferencia con respecto a otros sistemas de los que estamos a cargo es el número de bases de datos que estamos ejecutando en el servidor.

Nada más crear las casi 400 bases de datos que necesitabamos y sin empezar a utilizar el servidor, el disco en donde se alojan estas bases de datos empezó a trabajar sin parar.

¿Entre 50MB/s y 60MB/s constantes de escritura sin nadie utilizando el servidor?

Empezamos a investigar y a buscar en Internet si alguien habia tenido el mismo problema y pronto dimos con la explicación.

PostgreSQL mantiene por defecto un fichero en el directorio $PGDATA/pg_stat_tmp/ con información sobre las estadísticas internas de todas las bases de datos en el servidor. Estas estadísticas se pueden configurar con los parámetros definidos en la sección “18.9. Run-time Statistics” de la documentación.

El problema hasta la versión 9.2 es que todas las estadísticas de todas las bases de datos se graban en el mismo fichero. Cuando se tienen muchas bases de datos el proceso “stats collector” accede a este fichero muchas veces por segundo creando una carga I/O considerable en el disco de datos.

En un servidor de pruebas usando postgreSQL 9.2.5, con 500 bases de datos que contienen una simple tabla vacia de dos columnas, obtenemos estos datos de utilización de discos sin ningun usuario conectado al servidor:

Linux 2.6.32-358.18.1.el6.x86_64 _x86_64_  (32 CPU)

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn

sdb             184.00         0.00        84.08          0         84
sdb             176.00         0.00        81.47          0         81
sdb             195.00         0.00        91.61          0         91
sdb             161.00         0.00        78.18          0         78
sdb             204.00         0.00        98.00          0         97
sdb             173.00         0.00        82.80          0         82
sdb             171.00         0.00        82.84          0         82
sdb             186.00         0.00        90.01          0         90
sdb             166.00         0.00        80.68          0         80
Datos strace del proceso stats collector despues de aprox. 30 segundos:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.51    1.470643        5978       246           rename
  2.01    0.030306           0    642919           write
  0.33    0.005000           7       708           poll
  0.14    0.002098           8       247           open
  0.01    0.000108           0      1941       708 recvfrom
  0.00    0.000048           0       246           munmap
  0.00    0.000006           0       247           fstat
  0.00    0.000000           0       708       708 read
  0.00    0.000000           0       246           close
  0.00    0.000000           0       247           mmap
  0.00    0.000000           0         1           restart_syscall
------ ----------- ----------- --------- --------- ----------------
100.00    1.508209                647756      1416 total

Como podeis ver, con 500 bases de datos el problema se agrava todavía más y producimos sin utilizar el sistema aproxidamente entre 80MB/s y 90MB/s de carga en el disco.

Una solución es empezar a utilizar la reciente versión 9.3 de postgreSQL. Con esta versión la cosa ha mejorado muchisimo al crearse un fichero por base de datos en vez de uno común.

La utilización del disco de datos en vacio disminuye aproximadamente un 98,5% en nuestro ejemplo. Y el número de llamadas write del sistema desciende considerablemente, casi un 99%.

Linux 2.6.32-358.18.1.el6.x86_64 _x86_64_  (32 CPU)

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn

sdb              20.00         0.00         1.11          0          1
sdb              20.00         0.00         1.09          0          1
sdb              20.00         0.00         1.09          0          1
sdb              20.00         0.00         1.11          0          1
sdb              22.00         0.00         1.19          0          1
sdb              22.00         0.00         1.21          0          1
sdb              18.00         0.00         0.98          0          0
sdb              20.00         0.00         1.11          0          1
sdb              22.00         0.00         1.21          0          1
Datos strace del proceso stats collector despues de aprox. 30 segundos:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 60.15    0.008014           7      1180           poll
 29.63    0.003948           6       622           rename
  4.32    0.000576           1       622           open
  3.70    0.000493           0      8754           write
  1.44    0.000192           0      2524      1181 recvfrom
  0.29    0.000038           0       622           mmap
  0.27    0.000036           0       622           close
  0.16    0.000021           0       622           munmap
  0.04    0.000005           0      1181      1181 read
  0.00    0.000000           0       622           fstat
  0.00    0.000000           0         1           restart_syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.013323                 17372      2362 total

Por desgracia no todos podemos actualizar a la versión 9.3 ahora mismo.

La solución que se encuentra en Internet para sistemas menores que la versión 9.3 y que nosotros hemos implementado es crear un sistema de ficheros tmpfs en memoria para no tener que cargar el sistema de almacenamiento con el I/O producido por el proceso “stats collector” de postgres.

Supongo que tmpfs estará disponible por defecto en la mayoria de distribuciones linux de hoy en día. En nuestra Red Hat ELS 6.4 solamente hemos tenido que hacer lo siguiente para empezar a utilizarlo:

Crear un directorio donde montar nuestro sistema de ficheros tmpfs:

mkdir -p /var/lib/pgsql/9.2/pg_stat_tmp

Actualizar el fichero /etc/fstab con esta linea, para crear una partición de 1GB en memoria:

tmpfs      /var/lib/pgsql/9.2/pg_stat_tmp   tmpfs size=1G,uid=postgres,gid=postgres 0 0

Y ejecutar:

mount /var/lib/pgsql/9.2/pg_stat_tmp

Una vez que tenemos el el area tmpfs configurada y disponible tenemos que actualizar el fichero de configuración postgresql.conf de postgreSQL con la siguiente linea:

stats_temp_directory = '/var/lib/pgsql/9.2/pg_stat_tmp' 

Y ponemos este cambio en producción haciendo un reload del servidor postgreSQL.

Después de esto el problema desaparece y tenemos todos los recursos del disco disponible para los usuarios, incluso sin poder utilizar la versión 9.3 todavia.

Linux 2.6.32-358.18.1.el6.x86_64 _x86_64_  (32 CPU)

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb               9.00         0.00         0.14          0          0
sdb              11.00         0.00         0.94          0          0
sdb               9.00         0.00         0.14          0          0
sdb               7.00         0.00         0.11          0          0
sdb              10.00         0.00         0.16          0          0
sdb               7.00         0.00         0.11          0          0
sdb              13.00         0.00         1.28          0          1
sdb               8.00         0.00         0.12          0          0
sdb               9.00         0.00         0.14          0          0
Datos strace del proceso stats collector despues de aprox. 30 segundos:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 94.53    0.404986        1607       252           rename
  3.07    0.013168           0    657173           write
  2.10    0.008997          13       719           poll
  0.22    0.000929           4       253           open
  0.03    0.000125           0       719       719 read
  0.03    0.000116           0      1981       719 recvfrom
  0.02    0.000081           0       252           munmap
  0.01    0.000023           0       252           close
  0.00    0.000000           0       253           fstat
  0.00    0.000000           0       253           mmap
  0.00    0.000000           0         1           restart_syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.428425                662108      1438 total

El I/O producido en el disco de datos ha desaparecido aunque el sistema sigue haciendo aproximadamente el mismo número de llamadas write de sistema.