PostgreSQL es un sistema de bases de datos relacional orientado a objetos, transaccional y ACID-compliant (sus transacciones cumplen con las propiedades de atomicidad, consistencia, aislación y durabilidad). Dentro de mi organización surgió la necesidad de montar una instancia de PostgreSQL sobre un dataset ZFS, para soportar bases de datos de gran tamaño y aprovechar todos los beneficios de ZFS (compresión, eficiencia, flexibilidad y confiabilidad, entre otras). Por supuesto sobre FreeBSD.

En el artículo Introducción a ZFS en FreeBSD expliqué detalladamente cómo trabajar con sistemas de archivos ZFS, pero mencioné poco sobre su configuración avanzada. Sólo expliqué cómo utilizar compresión y deduplicación. En este artículo voy a explicar cómo optimizar un dataset ZFS para PostgreSQL, con el objetivo de mejorar la performance, lo cual sirve de ejemplo para entender cómo se modifican y cuáles son algunas de las variables de configuración avanzadas de un pool o dataset ZFS.

Supongamos que se ha creado un pool ZFS "zdata" y un dataset "postgres", donde se alojará una instancia de PostgreSQL:

# zpool create zdata /dev/da1
# zfs create zdata/postgres

Para obtener todas las variables de configuración de un pool o dataset, es posible utilizar el comando zfs get all:

# zfs get all /zdata/postgres/

Este comando presenta un listado de cada variable junto con su valor actual y su "origen". El origen de un valor para una variable puede ser local, heredado, por defecto o temporario. Un valor local es uno que ha sido establecido específicamente por el usuario para el pool o dataset en cuestión. Un valor heredado es uno que ha sido configurado localmente en un pool/dataset padre. Un valor temporario, en cambio, es uno que ha sido configurado en la línea de comando al momento de montar el dataset, sobrescribiendo el valor almacenado en el dataset. Los valores por defecto son lo que utiliza ZFS al momento de crear un dataset si el usuario no los ha especificado explícitamente. Los valores marcados con un guión del medio (-) son aquellos que no tienen origen, pues no son almacenados en ningún dataset (por ejemplo la fecha de creación, espacio utilizado y disponible, versión de ZFS, y más).

Cuando se creó el dataset /zdata/postgres, se utilizaron los siguientes valores por defecto (pues al momento de crear el pool origen no se indicó explícitamente ningún valor específico):

root@fbsd10:~ # zfs get all /zdata/postgres/
NAME            PROPERTY              VALUE                  SOURCE
zdata/postgres  type                  filesystem             -
zdata/postgres  creation              Wed Oct 14 13:10 2015  -
zdata/postgres  used                  19K                    -
zdata/postgres  available             96.4G                  -
zdata/postgres  referenced            19K                    -
zdata/postgres  compressratio         1.00x                  -
zdata/postgres  mounted               yes                    -
zdata/postgres  quota                 none                   default
zdata/postgres  reservation           none                   default
zdata/postgres  recordsize            128K                   default
zdata/postgres  mountpoint            /zdata/postgres        default
zdata/postgres  sharenfs              off                    default
zdata/postgres  checksum              on                     default
zdata/postgres  compression           off                    default
zdata/postgres  atime                 on                     default
zdata/postgres  devices               on                     default
zdata/postgres  exec                  on                     default
zdata/postgres  setuid                on                     default
zdata/postgres  readonly              off                    default
zdata/postgres  jailed                off                    default
zdata/postgres  snapdir               hidden                 default
zdata/postgres  aclmode               discard                default
zdata/postgres  aclinherit            restricted             default
zdata/postgres  canmount              on                     default
zdata/postgres  xattr                 off                    temporary
zdata/postgres  copies                1                      default
zdata/postgres  version               5                      -
zdata/postgres  utf8only              off                    -
zdata/postgres  normalization         none                   -
zdata/postgres  casesensitivity       sensitive              -
zdata/postgres  vscan                 off                    default
zdata/postgres  nbmand                off                    default
zdata/postgres  sharesmb              off                    default
zdata/postgres  refquota              none                   default
zdata/postgres  refreservation        none                   default
zdata/postgres  primarycache          all                    default
zdata/postgres  secondarycache        all                    default
zdata/postgres  usedbysnapshots       0                      -
zdata/postgres  usedbydataset         19K                    -
zdata/postgres  usedbychildren        0                      -
zdata/postgres  usedbyrefreservation  0                      -
zdata/postgres  logbias               latency                default
zdata/postgres  dedup                 off                    default
zdata/postgres  mlslabel                                     -
zdata/postgres  sync                  standard               default
zdata/postgres  refcompressratio      1.00x                  -
zdata/postgres  written               19K                    -
zdata/postgres  logicalused           9.50K                  -
zdata/postgres  logicalreferenced     9.50K                  -
zdata/postgres  volmode               default                default
zdata/postgres  filesystem_limit      none                   default
zdata/postgres  snapshot_limit        none                   default
zdata/postgres  filesystem_count      none                   default
zdata/postgres  snapshot_count        none                   default
zdata/postgres  redundant_metadata    all                    default

Se observa que ZFS utiliza por defecto un tamaño de bloque de 128 Kbytes, no utiliza compresión ni deduplicación, y la caché secundaria está habilitada tanto para datos como para metadatos.

La caché primaria de ZFS (conocida como ARC, Adaptative Replacement Cache) es una caché rápida, ubicada en memoria principal, que se utiliza para acelerar los accesos a disco más frecuentes (en servidores FreeBSD dedicados a proveer almacenamiento, el tamaño de esta caché debe ser toda la RAM disponible, menos 1GB). La caché secundaria (conocida como L2ARC, Level 2 Adaptative Replacement Cache), en cambio, tiene como objetivo proveer la posibilidad de agregar, opcionalmente, un nivel adicional de caché (probablemente de mayor tamaño) que podrá ser implementado con discos, presumiblemente SSD. Estos discos son mucho más lentos que la memoria RAM, pero de mucho mayor tamaño y mayor velocidad que los discos rígidos.

Al no contar en este caso con discos SSD para implementar correctamente la L2ARC, conviene desactivarla. Si se cuenta con discos SSD hay un buen artículo al respecto en uno de los blogs de Oracle: ZFS L2ARC .

Por otro lado, ya que PostgreSQL implementa su propio buffer pool de datos en memoria principal, se debe evitar utilizar la cahcé primaria de ZFS para datos a fin de evitar el doble buffer en memoria principal. De esta forma, es conveniente que ZFS utilice la caché únicamente para almacenar metadatos.

Como mencioné anteriormente, uno de los grandes beneficios de ZFS es la capacidad de implementar la compresión de datos al vuelo para optimizar el uso de espacio en disco. Esta es una de las más importantes características de ZFS junto con la deduplicación de bloques. Por ende es deseable habilitar la compresión. En mi caso utilizo el algoritmo de compresión lz4 por ser rápido y eficiente. Aunque también se puede utilizar gzip, lzjb o zle.

Una optimización importante para todo tipo de sistemas consiste en deshabilitar el registro del tiempo de acceso a archivos (atime). De lo contrario, cada vez que se accede a un archivo (aunque sea un acceso de sólo lectura) se debe actualizar esta fecha en los metadatos del archivo, lo cual produce un constante tráfico de escritura: cada acceso implica al menos una escritura. Esto significa que si se deshabilita el access time se gana mucho en cuanto a rendimiento, al costo de perder cuándo fue la última vez que fue accedido un archivo (información irrelevante de todos modos, al menos en la mayoría de los casos, pues lo que importa sólo es saber cuándo fueron modificados).

Finalmente está la cuestión del tamaño de bloque utilizado por el dataset. En general es conveniente que el tamaño de bloque a nivel sistema de archivos coincida con el tamaño de bloque utilizado por el motor de bases de datos. Si el tamaño de bloque en el motor de bases de datos (DBMS) es más grande que el tamaño de bloque en el sistema de archivos, por cada bloque modificado en el DBMS se modificarán al menos dos bloques de disco, lo que puede provocar eventualmente múltiples escrituras a disco (más aún si se ha deshabilitado la caché de datos a nivel ZFS). Si, en cambio, el tamaño de bloque en disco es mayor al del bloque en el DBMS, cada bloque escrito a nivel base de datos implicará una escritura de un bloque de mayor tamaño en disco.

Por defecto, PostgreSQL utiliza un tamaño de bloque de 8 Kbytes (establecido en tiempo de compilación: PostgreSQL: Documentation: 9.4: Installation Procedure), el cual puede consultarse ejecutando la consulta show block_size:

postgres=# show block_size;
 block_size 
------------
 8192
(1 fila)

Entonces es altamente recomendable utilizar un tamaño de bloque de 8 Kbytes en el dataset.

Con toda esta información, se modifican los valores de configuración del dataset ZFS ejecutando los siguientes comandos:

# zfs set recordsize=8K zdata/postgres
# zfs set compression=lz4 zdata/postgres
# zfs set secondarycache=none zdata/postgres
# zfs set atime=off zdata/postgres
# zfs set primarycache=metadata zdata/postgres

Verificar los cambios ejecutando nuevamente el comando zfs get all:

root@fbsd10:~ # zfs get all /zdata/postgres/
NAME            PROPERTY              VALUE                  SOURCE
zdata/postgres  type                  filesystem             -
zdata/postgres  creation              Wed Oct 14 13:10 2015  -
zdata/postgres  used                  19K                    -
zdata/postgres  available             96.4G                  -
zdata/postgres  referenced            19K                    -
zdata/postgres  compressratio         1.00x                  -
zdata/postgres  mounted               yes                    -
zdata/postgres  quota                 none                   default
zdata/postgres  reservation           none                   default
zdata/postgres  recordsize            8K                     local
zdata/postgres  mountpoint            /zdata/postgres        default
zdata/postgres  sharenfs              off                    default
zdata/postgres  checksum              on                     default
zdata/postgres  compression           lz4                    local
zdata/postgres  atime                 off                    local
zdata/postgres  devices               on                     default
zdata/postgres  exec                  on                     default
zdata/postgres  setuid                on                     default
zdata/postgres  readonly              off                    default
zdata/postgres  jailed                off                    default
zdata/postgres  snapdir               hidden                 default
zdata/postgres  aclmode               discard                default
zdata/postgres  aclinherit            restricted             default
zdata/postgres  canmount              on                     default
zdata/postgres  xattr                 off                    temporary
zdata/postgres  copies                1                      default
zdata/postgres  version               5                      -
zdata/postgres  utf8only              off                    -
zdata/postgres  normalization         none                   -
zdata/postgres  casesensitivity       sensitive              -
zdata/postgres  vscan                 off                    default
zdata/postgres  nbmand                off                    default
zdata/postgres  sharesmb              off                    default
zdata/postgres  refquota              none                   default
zdata/postgres  refreservation        none                   default
zdata/postgres  primarycache          metadata               local
zdata/postgres  secondarycache        none                   local
zdata/postgres  usedbysnapshots       0                      -
zdata/postgres  usedbydataset         19K                    -
zdata/postgres  usedbychildren        0                      -
zdata/postgres  usedbyrefreservation  0                      -
zdata/postgres  logbias               latency                default
zdata/postgres  dedup                 off                    default
zdata/postgres  mlslabel                                     -
zdata/postgres  sync                  standard               default
zdata/postgres  refcompressratio      1.00x                  -
zdata/postgres  written               19K                    -
zdata/postgres  logicalused           9.50K                  -
zdata/postgres  logicalreferenced     9.50K                  -
zdata/postgres  volmode               default                default
zdata/postgres  filesystem_limit      none                   default
zdata/postgres  snapshot_limit        none                   default
zdata/postgres  filesystem_count      none                   default
zdata/postgres  snapshot_count        none                   default
zdata/postgres  redundant_metadata    all                    default

Es posible ordenar los registros, para ubicar más rápidamente las variables, utilizando sort:

root@fbsd10:~ # zfs get all /zdata/postgres/ | sort
NAME            PROPERTY              VALUE                  SOURCE
zdata/postgres  aclinherit            restricted             default
zdata/postgres  aclmode               discard                default
zdata/postgres  atime                 off                    local
zdata/postgres  available             96.4G                  -
zdata/postgres  canmount              on                     default
zdata/postgres  casesensitivity       sensitive              -
zdata/postgres  checksum              on                     default
zdata/postgres  compression           lz4                    local
zdata/postgres  compressratio         1.00x                  -
zdata/postgres  copies                1                      default
zdata/postgres  creation              Wed Oct 14 13:10 2015  -
zdata/postgres  dedup                 off                    default
zdata/postgres  devices               on                     default
zdata/postgres  exec                  on                     default
zdata/postgres  filesystem_count      none                   default
zdata/postgres  filesystem_limit      none                   default
zdata/postgres  jailed                off                    default
zdata/postgres  logbias               latency                default
zdata/postgres  logicalreferenced     9.50K                  -
zdata/postgres  logicalused           9.50K                  -
zdata/postgres  mlslabel                                     -
zdata/postgres  mounted               yes                    -
zdata/postgres  mountpoint            /zdata/postgres        default
zdata/postgres  nbmand                off                    default
zdata/postgres  normalization         none                   -
zdata/postgres  primarycache          metadata               local
zdata/postgres  quota                 none                   default
zdata/postgres  readonly              off                    default
zdata/postgres  recordsize            8K                     local
zdata/postgres  redundant_metadata    all                    default
zdata/postgres  refcompressratio      1.00x                  -
zdata/postgres  referenced            19K                    -
zdata/postgres  refquota              none                   default
zdata/postgres  refreservation        none                   default
zdata/postgres  reservation           none                   default
zdata/postgres  secondarycache        none                   local
zdata/postgres  setuid                on                     default
zdata/postgres  sharenfs              off                    default
zdata/postgres  sharesmb              off                    default
zdata/postgres  snapdir               hidden                 default
zdata/postgres  snapshot_count        none                   default
zdata/postgres  snapshot_limit        none                   default
zdata/postgres  sync                  standard               default
zdata/postgres  type                  filesystem             -
zdata/postgres  used                  19K                    -
zdata/postgres  usedbychildren        0                      -
zdata/postgres  usedbydataset         19K                    -
zdata/postgres  usedbyrefreservation  0                      -
zdata/postgres  usedbysnapshots       0                      -
zdata/postgres  utf8only              off                    -
zdata/postgres  version               5                      -
zdata/postgres  volmode               default                default
zdata/postgres  vscan                 off                    default
zdata/postgres  written               19K                    -
zdata/postgres  xattr                 off                    temporary

Más información

The Z File System (ZFS) Quick Start Guide

ZFS Tuning Guide

man zfs
man zpool


Tal vez pueda interesarte


Compartí este artículo