65.1. Índices B-Tree #

65.1.1. Introducción
65.1.2. Comportamiento de las clases de operadores B-Tree
65.1.3. Funciones de soporte de B-Tree
65.1.4. Implementación

65.1.1. Introducción #

PostgreSQL incluye una implementación de la estructura de datos de índice estándar btree (árbol balanceado multivía). Cualquier tipo de datos que se pueda clasificar en un orden lineal bien definido se puede indexar mediante un índice btree. La única limitación es que una entrada de índice no puede superar aproximadamente un tercio de una página (después de la compresión TOAST, si corresponde).

Debido a que cada clase de operadores btree impone un orden de clasificación en su tipo de datos, las clases de operadores btree (o, en realidad, las familias de operadores) se han venido utilizando como la representación general y la comprensión de PostgreSQL de la semántica de ordenación. Por lo tanto, han adquirido algunas características que van más allá de lo que se necesitaría solo para soportar índices btree, y partes del sistema que están bastante alejadas del método de acceso (AM) btree hacen uso de ellas.

65.1.2. Comportamiento de las clases de operadores B-Tree #

Como se muestra en la Table 36.3, una clase de operadores btree debe proporcionar cinco operadores de comparación: <, <=, =, >= y >. Se podría esperar que <> también formara parte de la clase de operadores, pero no es así, porque casi nunca sería útil utilizar una cláusula WHERE <> en la búsqueda de un índice. (Para algunos fines, el planificador trata <> como asociado a una clase de operadores btree; pero encuentra ese operador a través del enlace negador del operador =, en lugar de buscarlo en pg_amop).

Cuando varios tipos de datos comparten una semántica de ordenación casi idéntica, sus clases de operadores se pueden agrupar en una familia de operadores. Hacerlo es ventajoso porque permite al planificador hacer deducciones sobre las comparaciones entre distintos tipos. Cada clase de operadores dentro de la familia debe contener los operadores de un solo tipo (y las funciones de soporte asociadas) para su tipo de datos de entrada, mientras que los operadores de comparación entre distintos tipos y las funciones de soporte están «libres» (loose) en la familia. Es recomendable que se incluya un conjunto completo de operadores entre distintos tipos en la familia, asegurando así que el planificador pueda representar cualquier condición de comparación que deduzca por transitividad.

Existen algunas suposiciones básicas que debe cumplir una familia de operadores btree:

  • Un operador = debe ser una relación de equivalencia; es decir, para todos los valores no nulos A, B, C del tipo de datos:

    • A = A es verdadero (ley reflexiva)

    • si A = B, entonces B = A (ley simétrica)

    • si A = B y B = C, entonces A = C (ley transitiva)

  • Un operador < debe ser una relación de orden estricto; es decir, para todos los valores no nulos A, B, C:

    • A < A es falso (ley irreflexiva)

    • si A < B y B < C, entonces A < C (ley transitiva)

  • Además, la ordenación es total; es decir, para todos los valores no nulos A, B:

    • exactamente una de las siguientes afirmaciones es verdadera: A < B, A = B, o B < A (ley de tricotomía)

    (La ley de tricotomía justifica la definición de la función de soporte de comparación, por supuesto).

Los otros tres operadores se definen en términos de = y < de la forma obvia, y deben actuar de manera consistente con ellos.

Para una familia de operadores que soporta múltiples tipos de datos, las leyes anteriores deben cumplirse cuando A, B, C se toman de cualquier tipo de datos de la familia. Las leyes transitivas son las más difíciles de asegurar, ya que en situaciones entre distintos tipos representan declaraciones de que los comportamientos de dos o tres operadores diferentes son consistentes. Como ejemplo, no funcionaría poner float8 y numeric en la misma familia de operadores, al menos no con la semántica actual de que los valores numeric se convierten a float8 para compararlos con un float8. Debido a la precisión limitada de float8, esto significa que existen valores numeric distintos que se compararán como iguales al mismo valor float8, y por tanto la ley transitiva fallaría.

Otro requisito para una familia de múltiples tipos de datos es que cualquier conversión implícita o de coerción binaria (binary-coercion casts) que se defina entre los tipos de datos incluidos en la familia de operadores no debe cambiar el orden de clasificación asociado.

Debería estar bastante claro por qué un índice btree requiere que estas leyes se cumplan dentro de un solo tipo de datos: sin ellas no hay un orden con el que organizar las claves. Además, las búsquedas en el índice utilizando una clave de comparación de un tipo de datos diferente requieren que las comparaciones se comporten de forma sensata entre dos tipos de datos. Las extensiones a tres o más tipos de datos dentro de una familia no son estrictamente requeridas por el propio mecanismo del índice btree, pero el planificador confía en ellas para fines de optimización.

65.1.3. Funciones de soporte de B-Tree #

Como se muestra en la Table 36.9, btree define una función de soporte obligatoria y cinco opcionales. Los seis métodos definidos por el usuario son:

order

Para cada combinación de tipos de datos para los que una familia de operadores btree proporciona operadores de comparación, debe proporcionar una función de soporte de comparación, registrada en pg_amproc con el número de función de soporte 1 y amproclefttype/amprocrighttype igual a los tipos de datos izquierdo y derecho para la comparación (es decir, los mismos tipos de datos con los que están registrados los operadores correspondientes en pg_amop). La función de comparación debe tomar dos valores no nulos A y B y devolver un valor int32 que sea < 0, 0, o > 0 cuando A < B, A = B, o A > B, respectivamente. No se permite un resultado nulo: todos los valores del tipo de datos deben ser comparables. Consulta src/backend/access/nbtree/nbtcompare.c para ver ejemplos.

Si los valores comparados son de un tipo de datos que soporta intercalación (collation), se pasará el OID de intercalación adecuado a la función de soporte de comparación, utilizando el mecanismo estándar PG_GET_COLLATION().

sortsupport

Opcionalmente, una familia de operadores btree puede proporcionar función(es) de soporte de ordenación (sort support), registradas bajo el número de función de soporte 2. Estas funciones permiten implementar comparaciones para fines de ordenación de una manera más eficiente que llamando ingenuamente a la función de soporte de comparación. Las API implicadas en esto se definen en src/include/utils/sortsupport.h.

in_range

Opcionalmente, una familia de operadores btree puede proporcionar función(es) de soporte in_range, registradas bajo el número de función de soporte 3. Estas no se utilizan durante las operaciones del índice btree; en su lugar, extienden la semántica de la familia de operadores para que pueda soportar cláusulas de ventana que contengan los tipos de límite de marco RANGE offset PRECEDING y RANGE offset FOLLOWING (consulta la Section 4.2.8). Fundamentalmente, la información adicional proporcionada es cómo sumar o restar un valor offset de una manera que sea compatible con el orden de datos de la familia.

Una función in_range debe tener la firma

in_range(val type1, base type1, offset type2, sub bool, less bool)
returns bool

val y base deben ser del mismo tipo, que es uno de los tipos soportados por la familia de operadores (es decir, un tipo para el cual proporciona un orden). Sin embargo, offset podría ser de un tipo diferente, el cual podría ser uno no soportado de otro modo por la familia. Un ejemplo es que la familia integrada time_ops proporciona una función in_range que tiene offset de tipo interval. Una familia puede proporcionar funciones in_range para cualquiera de sus tipos soportados y uno o más tipos de offset. Cada función in_range debe introducirse en pg_amproc con amproclefttype igual a type1 y amprocrighttype igual a type2.

La semántica esencial de una función in_range depende de los dos parámetros de bandera booleanos. Debe sumar o restar base y offset, y luego comparar val con el resultado, de la siguiente manera:

  • si !sub y !less, devuelve val >= (base + offset)

  • si !sub y less, devuelve val <= (base + offset)

  • si sub y !less, devuelve val >= (base - offset)

  • si sub y less, devuelve val <= (base - offset)

Antes de hacerlo, la función debe comprobar el signo de offset: si es menor que cero, debe lanzar el error ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE (22013) con un texto de error como «invalid preceding or following size in window function» (tamaño precedente o siguiente no válido en la función de ventana). (Esto lo requiere el estándar SQL, aunque las familias de operadores no estándar podrían optar por ignorar esta restricción, ya que parece haber poca necesidad semántica para ella). Esta obligación se delega en la función in_range para que el código del núcleo no necesite entender qué significa «menor que cero» para un tipo de datos particular.

Una expectativa adicional es que las funciones in_range deben, si es práctico, evitar lanzar un error si base + offset o base - offset causaran desbordamiento. El resultado correcto de la comparación se puede determinar incluso si ese valor estuviera fuera del rango del tipo de datos. Ten en cuenta que si el tipo de datos incluye conceptos como «infinito» o «NaN», puede ser necesario un cuidado especial para asegurar que los resultados de in_range coincidan con el orden de ordenación normal de la familia de operadores.

Los resultados de la función in_range deben ser consistentes con el orden de ordenación impuesto por la familia de operadores. Para ser precisos, dados los valores fijos de offset y sub, entonces:

  • Si in_range con less = true es verdadero para algún val1 y base, debe ser verdadero para cada val2 <= val1 con la misma base.

  • Si in_range con less = true es falso para algún val1 y base, debe ser falso para cada val2 >= val1 con la misma base.

  • Si in_range con less = true es verdadero para algún val y base1, debe ser verdadero para cada base2 >= base1 con el mismo val.

  • Si in_range con less = true es falso para algún val y base1, debe ser falso para cada base2 <= base1 con el mismo val.

Declaraciones análogas con condiciones invertidas se aplican cuando less = false.

Si el tipo que se está ordenando (type1) soporta intercalación (collation), se pasará el OID de intercalación adecuado a la función in_range, utilizando el mecanismo estándar PG_GET_COLLATION().

Las funciones in_range no necesitan manejar entradas NULL, y normalmente se marcarán como strict.

equalimage

Opcionalmente, una familia de operadores btree puede proporcionar funciones de soporte equalimage («la igualdad implica la igualdad de la imagen»), registradas bajo el número de función de soporte 4. Estas funciones permiten al código del núcleo determinar cuándo es seguro aplicar la optimización de deduplicación btree. Actualmente, las funciones equalimage solo se llaman cuando se construye o reconstruye un índice.

Una función equalimage debe tener la firma

equalimage(opcintype oid) returns bool

El valor de retorno es información estática sobre una clase de operadores e intercalación. Devolver true indica que se garantiza que la función order de la clase de operadores solo devolverá 0 («los argumentos son iguales») cuando sus argumentos A y B sean también intercambiables sin pérdida alguna de información semántica. No registrar una función equalimage o devolver false indica que no se puede asumir que esta condición se cumpla.

El argumento opcintype es el OID pg_type.oid del tipo de datos que indexa la clase de operadores. Se trata de una conveniencia que permite reutilizar la misma función equalimage subyacente en distintas clases de operadores. Si opcintype es un tipo de datos que soporta intercalación, se pasará el OID de intercalación adecuado a la función equalimage, utilizando el mecanismo estándar PG_GET_COLLATION().

En lo que respecta a la clase de operadores, devolver true indica que la deduplicación es segura (o segura para la intercalación cuyo OID se pasó a su función equalimage). Sin embargo, el código del núcleo solo considerará segura la deduplicación para un índice cuando cada columna indexada utilice una clase de operadores que registre una función equalimage, y cada función devuelva efectivamente true cuando se la llame.

La igualdad de la imagen es casi la misma condición que la simple igualdad a nivel de bits. Hay una diferencia sutil: al indexar un tipo de datos varlena, la representación en disco de dos datos con imágenes iguales puede no ser igual bit a bit debido a la aplicación inconsistente de la compresión TOAST en la entrada. Formalmente, cuando la función equalimage de una clase de operadores devuelve true, es seguro asumir que la función de C datum_image_eq() siempre coincidirá con la función order de la clase de operadores (siempre que se pase el mismo OID de intercalación tanto a la función equalimage como a la función order).

El código del núcleo es fundamentalmente incapaz de deducir nada sobre el estado de «la igualdad implica la igualdad de la imagen» de una clase de operadores dentro de una familia de múltiples tipos de datos basándose en detalles de otras clases de operadores de la misma familia. Además, no tiene sentido que una familia de operadores registre una función equalimage entre tipos distintos, e intentar hacerlo provocará un error. Esto se debe a que el estado de «la igualdad implica la igualdad de la imagen» no depende únicamente de la semántica de ordenación/igualdad, que están más o menos definidas al nivel de la familia de operadores. En general, la semántica que implementa un tipo de datos particular debe considerarse por separado.

La convención seguida por las clases de operadores incluidas en la distribución principal de PostgreSQL es registrar una función equalimage genérica y estándar. La mayoría de las clases de operadores registran btequalimage(), lo que indica que la deduplicación es segura incondicionalmente. Las clases de operadores para tipos de datos que soportan intercalación, como text, registran btvarstrequalimage(), lo que indica que la deduplicación es segura con intercalaciones deterministas. La mejor práctica para las extensiones de terceros es registrar su propia función personalizada para mantener el control.

options

Opcionalmente, una familia de operadores B-tree puede proporcionar funciones de soporte options («opciones específicas de la clase de operadores»), registradas bajo el número de función de soporte 5. Estas funciones definen un conjunto de parámetros visibles para el usuario que controlan el comportamiento de la clase de operadores.

Una función de soporte options debe tener la firma

options(relopts local_relopts *) returns void

A la función se le pasa un puntero a una estructura local_relopts, que debe llenarse con un conjunto de opciones específicas de la clase de operadores. Se puede acceder a las opciones desde otras funciones de soporte utilizando las macros PG_HAS_OPCLASS_OPTIONS() y PG_GET_OPCLASS_OPTIONS().

Actualmente, ninguna clase de operadores de B-Tree tiene una función de soporte options. B-tree no permite una representación flexible de claves como lo hacen GiST, SP-GiST, GIN y BRIN. Por lo tanto, options probablemente no tenga mucha aplicación en el método de acceso al índice B-tree actual. No obstante, esta función de soporte se añadió a B-tree por uniformidad, y probablemente encontrará usos durante la evolución futura de B-tree en PostgreSQL.

skipsupport

Opcionalmente, una familia de operadores btree puede proporcionar una función de soporte de omisión (skip support), registrada bajo el número de función de soporte 6. Estas funciones dan al código B-tree una forma de iterar a través de cada valor posible que puede ser representado por el tipo de entrada subyacente de una clase de operadores, en el orden del espacio de claves. Esto lo utiliza el código del núcleo cuando aplica la optimización de escaneo con omisión (skip scan). Las API implicadas en esto se definen en src/include/utils/skipsupport.h.

Las clases de operadores que no proporcionan una función de soporte de omisión siguen siendo elegibles para utilizar el escaneo con omisión. El código del núcleo aún puede utilizar su estrategia alternativa, aunque esto podría ser subóptimo para algunos tipos discretos. Por lo general, no tiene sentido (y puede que ni siquiera sea factible) que las clases de operadores en tipos continuos proporcionen una función de soporte de omisión.

No tiene sentido que una familia de operadores registre una función skipsupport entre tipos distintos, e intentar hacerlo provocará un error. Esto se debe a que la determinación del siguiente valor indexable debe realizarse incrementando un valor copiado de una tupla de índice. Los valores generados deben ser todos del mismo tipo de datos subyacente (el tipo de entrada de la opclass de la columna del índice «omitido»).

65.1.4. Implementación #

Esta sección cubre los detalles de implementación del índice B-Tree que pueden ser de utilidad para usuarios avanzados. Consulta src/backend/access/nbtree/README en la distribución de las fuentes para obtener una descripción mucho más detallada y centrada en los aspectos internos de la implementación de B-Tree.

65.1.4.1. Estructura de B-Tree #

Los índices B-Tree de PostgreSQL son estructuras de árbol multinivel, donde cada nivel del árbol se puede utilizar como una lista doblemente enlazada de páginas. Se almacena una única metapágina en una posición fija al principio del primer archivo de segmento del índice. Todas las demás páginas son páginas hoja (leaf pages) o páginas internas. Las páginas hoja son las páginas del nivel más bajo del árbol. Todos los demás niveles constan de páginas internas. Cada página hoja contiene tuplas que apuntan a filas de la tabla. Cada página interna contiene tuplas que apuntan al siguiente nivel inferior en el árbol. Normalmente, más del 99% de todas las páginas son páginas hoja. Tanto las páginas internas como las páginas hoja utilizan el formato de página estándar descrito en la Section 66.6.

Se añaden nuevas páginas hoja a un índice B-Tree cuando una página hoja existente no puede albergar una tupla entrante. Una operación de división de página (page split) hace sitio para los elementos que originalmente pertenecían a la página desbordada moviendo una parte de los elementos a una página nueva. Las divisiones de página también deben insertar un nuevo enlace descendente (downlink) a la nueva página en la página madre, lo que a su vez puede hacer que la madre se divida. Las divisiones de página «se extienden en cascada hacia arriba» de forma recursiva. Cuando la página raíz finalmente no puede albergar un nuevo enlace descendente, se lleva a cabo una operación de división de página raíz. Esto añade un nuevo nivel a la estructura del árbol creando una nueva página raíz que está un nivel por encima de la página raíz original.

65.1.4.2. Eliminación de índices de abajo hacia arriba (Bottom-up) #

Los índices B-Tree no son directamente conscientes de que bajo MVCC, puede haber múltiples versiones existentes de la misma fila de tabla lógica; para un índice, cada tupla es un objeto independiente que necesita su propia entrada de índice. Las tuplas de acumulación de versiones (version churn) pueden a veces acumularse y afectar negativamente a la latencia y rendimiento de las consultas. Esto suele ocurrir con cargas de trabajo intensivas en UPDATE, donde la mayoría de las actualizaciones individuales no pueden aplicar la optimización HOT. Cambiar el valor de una sola columna cubierta por un índice durante un UPDATE requiere siempre un nuevo conjunto de tuplas de índice: una para cada uno de los índices de la tabla. Ten en cuenta en particular que esto incluye los índices que no fueron «modificados lógicamente» por el UPDATE. Todos los índices necesitarán una tupla física sucesora que apunte a la versión más reciente de la tabla. Cada nueva tupla dentro de cada índice necesitará generalmente coexistir con la tupla original «actualizada» durante un breve período de tiempo (normalmente hasta poco después de que la transacción UPDATE se confirme).

Los índices B-Tree eliminan incrementalmente las tuplas de índice obsoletas por acumulación de versiones mediante la realización de pasadas de eliminación de índices de abajo hacia arriba (bottom-up). Cada pasada de eliminación se desencadena como reacción a una anticipada «división de página por acumulación de versiones». Esto solo ocurre con los índices que no son modificados lógicamente por sentencias UPDATE, donde de lo contrario se produciría una acumulación concentrada de versiones obsoletas en páginas particulares. Normalmente se evitará una división de página, aunque es posible que ciertas heurísticas a nivel de implementación no logren identificar y eliminar ni siquiera una tupla de índice basura (en cuyo caso una división de página o una pasada de deduplicación resuelve el problema de que una nueva tupla entrante no quepa en una página hoja). El número de versiones del peor caso que debe recorrer cualquier escaneo de índice (para cualquier fila lógica individual) contribuye de forma importante a la capacidad de respuesta y rendimiento general del sistema. Una pasada de eliminación de índice de abajo hacia arriba apunta a las tuplas sospechosas de ser basura en una sola página hoja basándose en distinciones cualitativas que implican filas lógicas y versiones. Esto contrasta con la limpieza de índices «de arriba hacia abajo» realizada por los trabajadores de autovacuum, la cual se desencadena cuando se superan ciertos umbrales cuantitativos a nivel de tabla (ver Section 24.1.6).

Note

No todas las operaciones de eliminación que se realizan dentro de los índices B-Tree son eliminaciones de abajo hacia arriba. Existe una categoría distinta de eliminación de tuplas de índice: la eliminación simple de tuplas de índice. Se trata de una operación de mantenimiento diferida que elimina las tuplas de índice que se sabe que es seguro eliminar (aquellas cuyo bit LP_DEAD del identificador de elemento ya está establecido). Al igual que la eliminación de índices de abajo hacia arriba, la eliminación simple de índices tiene lugar en el punto en que se anticipa una división de página como forma de evitar la división.

La eliminación simple es oportunista en el sentido de que solo puede tener lugar cuando los escaneos de índices recientes establecen de paso los bits LP_DEAD de los elementos afectados. Antes de PostgreSQL 14, la única categoría de eliminación en B-Tree era la eliminación simple. Las principales diferencias entre ella y la eliminación de abajo hacia arriba son que solo la primera es impulsada de forma oportunista por la actividad de los escaneos de índices que pasan, mientras que solo la segunda se dirige específicamente a la acumulación de versiones de los UPDATEs que no modifican lógicamente las columnas indexadas.

La eliminación de índices de abajo hacia arriba realiza la gran mayoría de la limpieza de tuplas de índice basura para índices particulares con ciertas cargas de trabajo. Esto es de esperar con cualquier índice B-Tree que esté sujeto a una acumulación significativa de versiones por UPDATEs que rara vez o nunca modifican lógicamente las columnas que cubre el índice. El número medio y en el peor de los casos de versiones por fila lógica se puede mantener bajo puramente mediante pasadas de eliminación incremental dirigidas. Es muy posible que el tamaño en disco de ciertos índices nunca aumente ni siquiera en una sola página/bloque a pesar del constante trasiego de versiones por UPDATEs. Incluso entonces, finalmente se requerirá un barrido completo exhaustivo por una operación VACUUM (ejecutada normalmente en un proceso trabajador de autovacuum) como parte de la limpieza colectiva de la tabla y cada uno de sus índices.

A diferencia de VACUUM, la eliminación de índices de abajo hacia arriba no proporciona ninguna garantía sólida sobre qué tan vieja puede ser la tupla de índice basura más antigua. No se puede permitir que ningún índice retenga tuplas de índice de «basura flotante» que murieron antes de un punto de corte conservador compartido por la tabla y todos sus índices colectivamente. Esta invariante fundamental a nivel de tabla hace que sea seguro reciclar los TID de la tabla. Así es como es posible que distintas filas lógicas reutilicen el mismo TID de tabla a lo largo del tiempo (aunque esto nunca puede suceder con dos filas lógicas cuyas vidas abarquen el mismo ciclo de VACUUM).

65.1.4.3. Deduplicación #

Un duplicado es una tupla de página hoja (una tupla que apunta a una fila de tabla) donde todos los atributos indexados tienen valores que coinciden con los valores de columna correspondientes de al menos otra tupla de página hoja en el mismo índice. Las tuplas duplicadas son bastante comunes en la práctica. Los índices B-Tree pueden utilizar una representación especial y eficiente en espacio para los duplicados cuando se activa una técnica opcional: la deduplicación.

La deduplicación funciona fusionando periódicamente grupos de tuplas duplicadas, formando una única tupla de lista de publicación (posting list) para cada grupo. El valor o valores de la clave de columna solo aparecen una vez en esta representación. A esto le sigue un array ordenado de TIDs que apuntan a las filas de la tabla. Esto reduce significativamente el tamaño de almacenamiento de los índices donde cada valor (o cada combinación distinta de valores de columna) aparece varias veces de media. La latencia de las consultas puede reducirse significativamente. El rendimiento general de las consultas puede aumentar significativamente. La sobrecarga del autovacuum rutinario de los índices también puede reducirse significativamente.

Note

La deduplicación B-Tree es igual de eficaz con los «duplicados» que contienen un valor NULL, a pesar de que los valores NULL nunca son iguales entre sí según el miembro = de cualquier clase de operadores B-Tree. En lo que respecta a cualquier parte de la implementación que entienda la estructura B-Tree en disco, NULL es simplemente otro valor del dominio de los valores indexados.

El proceso de deduplicación ocurre de forma perezosa (lazily), cuando se inserta un nuevo elemento que no cabe en una página hoja existente, aunque solo cuando la eliminación de tuplas de índice no pudiera liberar espacio suficiente para el nuevo elemento (normalmente la eliminación se considera brevemente y luego se omite). A diferencia de las tuplas de lista de publicación de GIN, las tuplas de lista de publicación de B-Tree no necesitan expandirse cada vez que se inserta un nuevo duplicado; son simplemente una representación física alternativa del contenido lógico original de la página hoja. Este diseño prioriza un rendimiento constante con cargas de trabajo mixtas de lectura y escritura. La mayoría de las aplicaciones cliente verán al menos un beneficio de rendimiento moderado al utilizar la deduplicación. La deduplicación está activada por defecto.

CREATE INDEX y REINDEX aplican la deduplicación para crear tuplas de listas de publicación, aunque la estrategia que utilizan es ligeramente diferente. Cada grupo de tuplas ordinarias duplicadas que se encuentra en la entrada ordenada tomada de la tabla se fusiona en una tupla de lista de publicación antes de ser añadida a la página hoja pendiente actual. Las tuplas de lista de publicación individuales se empaquetan con tantos TIDs como sea posible. Las páginas hoja se escriben de la manera habitual, sin ninguna pasada de deduplicación separada. Esta estrategia es muy adecuada para CREATE INDEX y REINDEX porque son operaciones por lotes que se realizan una sola vez.

Las cargas de trabajo con un alto volumen de escrituras que no se beneficien de la deduplicación por tener pocos o ningún valor duplicado en los índices incurrirán en una pequeña penalización fija de rendimiento (a menos que la deduplicación esté explícitamente desactivada). El parámetro de almacenamiento deduplicate_items se puede utilizar para desactivar la deduplicación dentro de índices individuales. Nunca hay ninguna penalización de rendimiento con cargas de trabajo de solo lectura, ya que leer tuplas de listas de publicación es al menos tan eficiente como leer la representación de tuplas estándar. Desactivar la deduplicación no suele ser útil.

A veces es posible que los índices únicos (así como las restricciones de unicidad) utilicen la deduplicación. Esto permite a las páginas hoja «absorber» temporalmente los duplicados adicionales de la acumulación de versiones. La deduplicación en índices únicos aumenta la eliminación de índices de abajo hacia arriba, especialmente en los casos en que una transacción de larga duración mantiene una instantánea que bloquea la recolección de basura. El objetivo es ganar tiempo para que la estrategia de eliminación de índices de abajo hacia arriba vuelva a ser eficaz. Retrasar las divisiones de páginas hasta que una única transacción de larga duración desaparezca de forma natural puede permitir que una pasada de eliminación de abajo hacia arriba tenga éxito allí donde una pasada de eliminación anterior falló.

Tip

Se aplica una heurística especial para determinar si debe realizarse una pasada de deduplicación en un índice único. A menudo puede saltar directamente a la división de una página hoja, evitando una penalización de rendimiento al gastar ciclos en pasadas de deduplicación inútiles. Si te preocupa la sobrecarga de la deduplicación, considera establecer deduplicate_items = off de forma selectiva. Dejar la deduplicación activada en índices únicos tiene pocos inconvenientes.

La deduplicación no se puede utilizar en todos los casos debido a restricciones a nivel de implementación. La seguridad de la deduplicación se determina cuando se ejecuta CREATE INDEX o REINDEX.

Ten en cuenta que la deduplicación se considera insegura y no se puede utilizar en los siguientes casos que implican diferencias semánticamente significativas entre datums iguales:

  • Los tipos text, varchar y char no pueden utilizar la deduplicación cuando se utiliza una intercalación (collation) no determinada. Deben preservarse las diferencias de mayúsculas y acentos entre datos iguales.

  • El tipo numeric no puede utilizar la deduplicación. Se debe conservar la escala de visualización numérica entre datos iguales.

  • El tipo jsonb no puede utilizar la deduplicación, ya que la clase de operadores B-Tree de jsonb utiliza numeric internamente.

  • Los tipos float4 y float8 no pueden utilizar la deduplicación. Estos tipos tienen representaciones distintas para -0 y 0, que no obstante se consideran iguales. Esta diferencia debe preservarse.

Existe otra restricción a nivel de implementación que podría eliminarse en una futura versión de PostgreSQL:

  • Los tipos contenedor (como los tipos compuestos, los arrays o los tipos de rango) no pueden utilizar la deduplicación.

Existe otra restricción a nivel de implementación que se aplica independientemente de la clase de operadores o de la intercalación utilizadas:

  • Los índices INCLUDE nunca pueden utilizar la deduplicación.