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 |
|---|---|
| PageHeaderData | Tiene una longitud de 24 bytes. Contiene información general sobre la página, incluidos los punteros de espacio libre. |
| ItemIdData | Array de identificadores de elementos que apuntan a los elementos reales. Cada entrada es un par (desplazamiento, longitud). 4 bytes por elemento. |
| Espacio libre | El 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 especial | Datos 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
| Campo | Tipo | Longitud | Descripción |
|---|---|---|---|
| pd_lsn | PageXLogRecPtr | 8 bytes | LSN: siguiente byte después del último byte del registro de WAL para el último cambio en esta página |
| pd_checksum | uint16 | 2 bytes | Suma de comprobación de la página |
| pd_flags | uint16 | 2 bytes | Bits de banderas |
| pd_lower | LocationIndex | 2 bytes | Desplazamiento al inicio del espacio libre |
| pd_upper | LocationIndex | 2 bytes | Desplazamiento al final del espacio libre |
| pd_special | LocationIndex | 2 bytes | Desplazamiento al inicio del espacio especial |
| pd_pagesize_version | uint16 | 2 bytes | Información sobre el tamaño de la página y el número de versión de la disposición |
| pd_prune_xid | TransactionId | 4 bytes | XMAX 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
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
| Campo | Tipo | Longitud | Descripción |
|---|---|---|---|
| t_xmin | TransactionId | 4 bytes | marca XID de inserción |
| t_xmax | TransactionId | 4 bytes | marca XID de eliminación |
| t_cid | CommandId | 4 bytes | marca CID de inserción y/o eliminación (se superpone con t_xvac) |
| t_xvac | TransactionId | 4 bytes | XID para la operación VACUUM que mueve una versión de fila |
| t_ctid | ItemPointerData | 6 bytes | TID actual de esta o de una versión de fila más nueva |
| t_infomask2 | uint16 | 2 bytes | número de atributos, más varios bits de banderas |
| t_infomask | uint16 | 2 bytes | varios bits de banderas |
| t_hoff | uint8 | 1 byte | desplazamiento 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.