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.
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.
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(valtype1,basetype1,offsettype2,subbool,lessbool) 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(opcintypeoid) 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
del tipo de datos que indexa la clase de operadores. Se trata de una conveniencia que permite reutilizar la misma
función pg_type.oidequalimage 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(reloptslocal_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»).
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.
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.
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).
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).
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.
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ó.
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.