66.6. Disposición de las páginas de la base de datos #

66.6.1. Disposición de las filas de la tabla

Esta sección proporciona una visión general del formato de página utilizado dentro de las tablas y los índices de PostgreSQL.[19] Las secuencias y las tablas TOAST tienen el mismo formato que una tabla normal.

En la siguiente explicación, se asume que un byte contiene 8 bits. Además, el término elemento (item) se refiere a un valor de datos individual que se almacena en una página. En una tabla, un elemento es una fila; en un índice, un elemento es una entrada de índice.

Cada tabla e índice se almacena como un array de páginas de tamaño fijo (normalmente 8 kB, aunque se puede seleccionar un tamaño de página diferente al compilar el servidor). En una tabla, todas las páginas son lógicamente equivalentes, por lo que un elemento (fila) particular se puede almacenar en cualquier página. En los índices, la primera página se reserva generalmente como una metapágina que contiene información de control, y puede haber diferentes tipos de páginas dentro del índice, dependiendo del método de acceso al índice.

El Table 66.2 muestra la disposición general de una página. Hay cinco partes en cada página.

Table 66.2. Disposición general de la página

Elemento Descripción
PageHeaderDataTiene una longitud de 24 bytes. Contiene información general sobre la página, incluidos los punteros de espacio libre.
ItemIdDataArray de identificadores de elementos que apuntan a los elementos reales. Cada entrada es un par (desplazamiento, longitud). 4 bytes por elemento.
Espacio libreEl espacio no asignado. Los nuevos identificadores de elementos se asignan desde el principio de este área, los nuevos elementos desde el final.
Elementos (Items)Los elementos reales en sí mismos.
Espacio especialDatos específicos del método de acceso al índice. Los diferentes métodos almacenan diferentes datos. Vacío en tablas ordinarias.

Los primeros 24 bytes de cada página consisten en una cabecera de página (PageHeaderData). Su formato se detalla en el Table 66.3. El primer campo realiza el seguimiento de la entrada de WAL más reciente relacionada con esta página. El segundo campo contiene la suma de comprobación (checksum) de la página si las sumas de comprobación de datos de -k están habilitadas. A continuación hay un campo de 2 bytes que contiene bits de bandera (flags). Esto es seguido por tres campos enteros de 2 bytes (pd_lower, pd_upper y pd_special). Estos contienen desplazamientos de bytes desde el inicio de la página hasta el inicio del espacio no asignado, hasta el final del espacio no asignado y hasta el inicio del espacio especial. Los siguientes 2 bytes de la cabecera de la página, pd_pagesize_version, almacenan tanto el tamaño de la página como un indicador de versión. A partir de PostgreSQL 8.3, el número de versión es 4; PostgreSQL 8.1 y 8.2 utilizaron el número de versión 3; PostgreSQL 8.0 utilizó el número de versión 2; PostgreSQL 7.3 y 7.4 utilizaron el número de versión 1; los lanzamientos anteriores utilizaron el número de versión 0. (La disposición básica de la página y el formato de la cabecera no han cambiado en la mayoría de estas versiones, pero la disposición de las cabeceras de las filas de montón sí lo ha hecho). El tamaño de la página está presente básicamente solo como una doble comprobación; no hay soporte para tener más de un tamaño de página en una instalación. El último campo es una sugerencia que indica si es probable que sea rentable realizar una poda de la página: realiza el seguimiento del XMAX sin podar más antiguo de la página.

Table 66.3. Disposición de PageHeaderData

CampoTipoLongitudDescripción
pd_lsnPageXLogRecPtr8 bytesLSN: siguiente byte después del último byte del registro de WAL para el último cambio en esta página
pd_checksumuint162 bytesSuma de comprobación de la página
pd_flagsuint162 bytesBits de banderas
pd_lowerLocationIndex2 bytesDesplazamiento al inicio del espacio libre
pd_upperLocationIndex2 bytesDesplazamiento al final del espacio libre
pd_specialLocationIndex2 bytesDesplazamiento al inicio del espacio especial
pd_pagesize_versionuint162 bytesInformación sobre el tamaño de la página y el número de versión de la disposición
pd_prune_xidTransactionId4 bytesXMAX sin podar más antiguo de la página, o cero si no hay ninguno

Todos los detalles se pueden encontrar en src/include/storage/bufpage.h.

A continuación de la cabecera de la página se encuentran los identificadores de elementos (ItemIdData), cada uno de los cuales requiere cuatro bytes. Un identificador de elemento contiene un desplazamiento de bytes al inicio de un elemento, su longitud en bytes y algunos bits de atributos que afectan a su interpretación. Los nuevos identificadores de elementos se asignan según sea necesario desde el principio del espacio no asignado. El número de identificadores de elementos presentes se puede determinar observando pd_lower, el cual se incrementa al asignar un nuevo identificador. Debido a que un identificador de elemento nunca se mueve hasta que se libera, su índice se puede utilizar a largo plazo para hacer referencia a un elemento, incluso cuando el elemento en sí se mueve dentro de la página para compactar el espacio libre. De hecho, cada puntero a un elemento (ItemPointer, también conocido como CTID) creado por PostgreSQL consta de un número de página y el índice de un identificador de elemento.

Los elementos en sí se almacenan en el espacio asignado hacia atrás desde el final del espacio no asignado. La estructura exacta varía según lo que vaya a contener la tabla. Tanto las tablas como las secuencias utilizan una estructura denominada HeapTupleHeaderData, que se describe a continuación.

La sección final es la sección especial que puede contener cualquier cosa que el método de acceso desee almacenar. Por ejemplo, los índices b-tree almacenan enlaces a las páginas hermanas izquierda y derecha, así como algunos otros datos relevantes para la estructura del índice. Las tablas ordinarias no utilizan una sección especial en absoluto (lo que se indica estableciendo pd_special de manera que sea igual al tamaño de la página).

El Figure 66.1 ilustra cómo se distribuyen estas partes en una página.

Figure 66.1. Disposición de la página


66.6.1. Disposición de las filas de la tabla #

Todas las filas de la tabla están estructuradas de la misma manera. Hay una cabecera de tamaño fijo (que ocupa 23 bytes en la mayoría de las máquinas), seguida de un mapa de bits de nulos opcional, un campo opcional para el ID del objeto y los datos del usuario. La cabecera se detalla en el Table 66.4. Los datos reales del usuario (columnas de la fila) comienzan en el desplazamiento indicado por t_hoff, el cual siempre debe ser un múltiplo de la distancia MAXALIGN para la plataforma. El mapa de bits de nulos solo está presente si el bit HEAP_HASNULL está establecido en t_infomask. Si está presente, comienza justo después de la cabecera fija y ocupa suficientes bytes para tener un bit por columna de datos (es decir, el número de bits que equivale al recuento de atributos en t_infomask2). En esta lista de bits, un bit 1 indica que no es nulo y un bit 0 indica que es nulo. Cuando el mapa de bits no está presente, se asume que todas las columnas no son nulas. El ID del objeto solo está presente si el bit HEAP_HASOID_OLD está establecido en t_infomask. Si está presente, aparece justo antes del límite de t_hoff. Cualquier relleno necesario para hacer de t_hoff un múltiplo de MAXALIGN aparecerá entre el mapa de bits de nulos y el ID del objeto. (Esto a su vez garantiza que el ID del objeto esté adecuadamente alineado).

Table 66.4. Disposición de HeapTupleHeaderData

CampoTipoLongitudDescripción
t_xminTransactionId4 bytesmarca XID de inserción
t_xmaxTransactionId4 bytesmarca XID de eliminación
t_cidCommandId4 bytesmarca CID de inserción y/o eliminación (se superpone con t_xvac)
t_xvacTransactionId4 bytesXID para la operación VACUUM que mueve una versión de fila
t_ctidItemPointerData6 bytesTID actual de esta o de una versión de fila más nueva
t_infomask2uint162 bytesnúmero de atributos, más varios bits de banderas
t_infomaskuint162 bytesvarios bits de banderas
t_hoffuint81 bytedesplazamiento a los datos del usuario

Todos los detalles se pueden encontrar en src/include/access/htup_details.h.

La interpretación de los datos reales solo se puede realizar con la información obtenida de otras tablas, principalmente de pg_attribute. Los valores clave necesarios para identificar las ubicaciones de los campos son attlen y attalign. No hay forma de obtener directamente un atributo particular, excepto cuando solo hay campos de ancho fijo y no hay valores nulos. Todo este truco está envuelto en las funciones heap_getattr, fastgetattr y heap_getsysattr.

Para leer los datos es necesario examinar cada atributo por turnos. Primero verifica si el campo es NULL de acuerdo con el mapa de bits de nulos. Si lo es, pasa al siguiente. Luego asegúrate de tener la alineación correcta. Si el campo es un campo de ancho fijo, simplemente se colocan todos los bytes. Si es un campo de longitud variable (attlen = -1), es un poco más complicado. Todos los tipos de datos de longitud variable comparten la estructura de cabecera común struct varlena, que incluye la longitud total del valor almacenado y algunos bits de banderas. Dependiendo de las banderas, los datos pueden estar en línea o en una tabla TOAST; también podrían estar comprimidos (ver Section 66.2).



[19] En realidad, el uso de este formato de página no es obligatorio ni para los métodos de acceso a tablas ni para los de índices. El método de acceso a tablas heap siempre utiliza este formato. Todos los métodos de índice existentes también utilizan el formato básico, pero los datos mantenidos en las metapáginas de índice normalmente no siguen las reglas de disposición de elementos.