Contexto
Estoy diseñando una base de datos (en PostgreSQL 9.6) que almacenará datos de una aplicación distribuida. Debido a la naturaleza distribuida de la aplicación, no puedo usar enteros de incremento automático ( SERIAL
) como mi clave principal debido a las posibles condiciones de carrera.
La solución natural es usar un UUID, o un identificador único global. Postgres viene con un built-in UUID
tipo , que es un ajuste perfecto.
El problema que tengo con UUID está relacionado con la depuración: es una cadena no amigable para los humanos. El identificador ff53e96d-5fd7-4450-bc99-111b91875ec5
no me dice nada, mientras que ACC-f8kJd9xKCd
, aunque no se garantiza que sea único, me dice que estoy tratando con un ACC
objeto.
Desde una perspectiva de programación, es común depurar consultas de aplicaciones relacionadas con varios objetos diferentes. Suponga que el programador busca erróneamente un ACC
objeto (cuenta) en la ORD
tabla (orden). Con un identificador legible por humanos, el programador identifica instantáneamente el problema, mientras usa UUID, pasaría algún tiempo descubriendo qué estaba mal.
No necesito la unicidad "garantizada" de los UUID; Yo no necesito un poco de espacio para la generación de claves sin conflictos, pero UUID es un exceso. Además, en el peor de los casos, no sería el fin del mundo si ocurriera una colisión (la base de datos lo rechaza y la aplicación puede recuperarse). Entonces, considerando las compensaciones, un identificador más pequeño pero amigable para los humanos sería la solución ideal para mi caso de uso.
Identificar objetos de aplicación
El identificador que se me ocurrió tiene el siguiente formato:, {domain}-{string}
donde {domain}
se reemplaza con el dominio del objeto (cuenta, pedido, producto) y {string}
es una cadena generada aleatoriamente. En algunos casos, incluso podría tener sentido insertar un {sub-domain}
antes de la cadena aleatoria. Ignoremos la duración {domain}
y {string}
con el fin de garantizar la unicidad.
El formato puede tener un tamaño fijo si ayuda al rendimiento de indexación / consulta.
El problema
Sabiendo que:
- Quiero tener claves primarias con un formato como
ACC-f8kJd9xKCd
. - Estas claves principales serán parte de varias tablas.
- Todas estas claves se utilizarán en varias uniones / relaciones, en una base de datos 6NF.
- La mayoría de las tablas tendrán un tamaño medio a grande (promediando ~ 1M filas; las más grandes con ~ 100M filas).
En cuanto al rendimiento, ¿cuál es la mejor manera de almacenar esta clave?
A continuación hay cuatro posibles soluciones, pero dado que tengo poca experiencia con bases de datos, no estoy seguro de cuál (si es que hay alguna) es la mejor.
Soluciones consideradas
1. Almacenar como cadena ( VARCHAR
)
(Postgres no hace diferencia entre CHAR(n)
y VARCHAR(n)
, así que estoy ignorando CHAR
).
Después de investigar un poco, descubrí que la comparación de cadenas con VARCHAR
, especialmente en operaciones de unión, es más lenta que el uso INTEGER
. Esto tiene sentido, pero ¿es algo de lo que debería preocuparme a esta escala?
2. Almacenar como binario ( bytea
)
A diferencia de Postgres, MySQL no tiene un UUID
tipo nativo . Hay varias publicaciones que explican cómo almacenar un UUID usando un BINARY
campo de 16 bytes , en lugar de uno de 36 bytes VARCHAR
. Estas publicaciones me dieron la idea de almacenar la clave como binaria ( bytea
en Postgres).
Esto ahorra tamaño, pero me preocupa más el rendimiento. Tuve poca suerte al encontrar una explicación sobre qué comparación es más rápida: binaria o de cadena. Creo que las comparaciones binarias son más rápidas. Si lo son, entonces bytea
probablemente sea mejor que eso VARCHAR
, a pesar de que el programador ahora tiene que codificar / decodificar los datos cada vez.
Podría estar equivocado, pero pienso en ambos bytea
y VARCHAR
compararé (igualdad) byte por byte (o carácter por carácter). ¿Hay alguna manera de "omitir" esta comparación paso a paso y simplemente comparar "todo"? (No lo creo, pero no cuesta comprobarlo).
Creo que almacenar bytea
es la mejor solución, pero me pregunto si hay otras alternativas que estoy ignorando. Además, la misma preocupación que expresé en la solución 1 es cierta: ¿es suficiente la sobrecarga en las comparaciones por las que debería preocuparme?
"Soluciones creativas
Se me ocurrieron dos soluciones muy "creativas" que podrían funcionar, pero no estoy seguro de en qué medida (es decir, si tuviera problemas para escalarlas en más de un par de miles de filas en una tabla).
3. Almacene como UUID
pero con una "etiqueta" pegada
La razón principal para no usar UUID es para que los programadores puedan depurar mejor la aplicación. Pero qué UUID
pasa si podemos usar ambos: la base de datos almacena todas las claves solo como s, pero envuelve el objeto antes / después de realizar consultas.
Por ejemplo, el programador lo solicita ACC-{UUID}
, la base de datos ignora la ACC-
parte, obtiene los resultados y los devuelve todos como {domain}-{UUID}
.
Tal vez esto sería posible con algún hacker con procedimientos o funciones almacenados, pero algunas preguntas me vienen a la mente:
- ¿Es esto (eliminar / agregar el dominio en cada consulta) una sobrecarga sustancial?
- ¿Es esto posible?
Nunca he usado procedimientos o funciones almacenados antes, así que no estoy seguro de si esto es posible. ¿Alguien puede arrojar algo de luz? Si puedo agregar una capa transparente entre el programador y los datos almacenados, parece una solución perfecta.
4. (Mi favorito) Almacenar como IPv6 cidr
Sí, has leído bien. Resulta que el formato de dirección IPv6 resuelve mi problema perfectamente .
- Puedo agregar dominios y subdominios en los primeros octetos, y usar los restantes como cadena aleatoria.
- Las probabilidades de colisión están bien. (Sin embargo, no estaría usando 2 ^ 128, pero todavía está bien).
- Las comparaciones de igualdad están (con suerte) optimizadas, por lo que podría obtener un mejor rendimiento que simplemente usarlas
bytea
. - De hecho, puedo realizar algunas comparaciones interesantes, como
contains
, dependiendo de cómo se representan los dominios y su jerarquía.
Por ejemplo, supongamos que uso un código 0000
para representar el dominio "productos". La clave 0000:0db8:85a3:0000:0000:8a2e:0370:7334
representaría el producto 0db8:85a3:0000:0000:8a2e:0370:7334
.
La pregunta principal aquí es: en comparación con bytea
, ¿hay alguna ventaja o desventaja principal en el uso del cidr
tipo de datos?
varchar
entre muchos otros problemas. No conocía los dominios de pg, lo cual es genial para aprender. Veo que se usan dominios para validar si una consulta dada está usando el objeto correcto, pero aún así dependería de tener un índice no entero. No estoy seguro si hay una forma "segura" de usar serial
aquí (sin un paso de bloqueo).
varchar
. Considere convertirlo en un FK
integer
tipo y agregue una tabla de búsqueda. De esa manera, puede tener legibilidad humana y proteger su compuestoPK
de las anomalías de inserción / actualización (poniendo un dominio inexistente).
text
Es preferible terminar varchar
. Mira depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text y postgresql.org/docs/current/static/datatype-character.html
ACC-f8kJd9xKCd
. ”← Eso parece ser un trabajo para la vieja y mejor CLAVE PRIMARIA compuesta .