¿Rendimiento de UUID en MySQL?

83

Estamos considerando usar valores UUID como claves principales para nuestra base de datos MySQL. Los datos que se insertan se generan a partir de docenas, cientos o incluso miles de computadoras remotas y se insertan a una velocidad de 100 a 40 000 inserciones por segundo, y nunca realizaremos ninguna actualización.

La base de datos en sí generalmente alcanzará alrededor de 50 millones de registros antes de que comencemos a seleccionar datos, por lo que no es una base de datos masiva, pero tampoco pequeña. También estamos planeando ejecutar InnoDB, aunque estamos abiertos a cambiar eso si hay un motor mejor para lo que estamos haciendo.

Estábamos listos para usar el UUID Tipo 4 de Java, pero en las pruebas hemos visto un comportamiento extraño. Por un lado, estamos almacenando como varchar (36) y ahora me doy cuenta de que estaríamos mejor usando binary (16), aunque no estoy seguro de cuánto mejor.

La pregunta más importante es: ¿en qué medida estos datos aleatorios arruinan el índice cuando tenemos 50 millones de registros? ¿Estaríamos mejor si usáramos, por ejemplo, un UUID de tipo 1 en el que los bits más a la izquierda tuvieran una marca de tiempo? ¿O tal vez deberíamos deshacernos de los UUID por completo y considerar las claves primarias auto_increment?

Estoy buscando ideas / consejos generales sobre el rendimiento de diferentes tipos de UUID cuando se almacenan como un índice / clave principal en MySQL. ¡Gracias!

Patrick Lightbody
fuente
2
falta un detalle importante: ¿las claves primarias serán generadas por el servidor de registro o por las propias máquinas cliente?
1
@hop, están siendo generados por los clientes 10-1000 que insertan los datos
Patrick Lightbody
¿Dónde necesita la singularidad universal en su escenario? Mi consejo es ceñirse a auto_increment y usar un campo separado para describir la computadora remota que envía los datos. No es necesario reinventar la rueda aquí.
Theodore Zographos

Respuestas:

35

Un UUID es una identificación única universal. Es la parte universal que deberías considerar aquí.

¿ Realmente necesitas que los ID sean universalmente únicos? Si es así, los UUID pueden ser su única opción.

Me gustaría sugerir fuertemente que si haces UUID de uso, los almacena como un número y no como una cadena. Si tiene más de 50 millones de registros, el ahorro de espacio de almacenamiento mejorará su rendimiento (aunque no podría decir cuánto).

Si sus ID no necesitan ser universalmente únicos, entonces no creo que pueda hacerlo mucho mejor que simplemente usando auto_increment, lo que garantiza que los ID serán únicos dentro de una tabla (ya que el valor se incrementará cada vez)

Dancrumb
fuente
2
Punto interesante; esto paralelizaría la generación de las claves. Creo que esto aumentaría el rendimiento de la generación de claves. Sin embargo, está eligiendo INSERT performance sobre SELECT performance si usa VARCHAR para almacenar el UUID. Definitivamente, debe elegir VARBINARY para almacenar y garantizar el rendimiento de SELECT. El paso adicional puede afectar el rendimiento de INSERT, pero se verá recompensado con la mejora del rendimiento de SELECT.
Dancrumb
12
Terminamos haciendo una evaluación comparativa con datos reales y los GUID sin claves fueron bastante rápidos, los GUID con claves fueron horribles (incluso cuando se almacenaron como BINARIO) e int w / AUTO_COMPLETE fue el más rápido. Creo que en nuestro caso, de hecho, estábamos perdiendo el bosque de los árboles, ya que la generación de la secuencia parecía intrascendente en comparación con el costo de almacenar más datos + tener un BTREE realmente malo debido a la aleatoriedad de los GUID
Patrick Lightbody
1
almacenar como un número significa almacenar en formato binario? pero el formato binario es ilegible para humanos. ¿Es lento porque grandes bytes de clave primaria uuid? Si es así, entonces podría almacenar el incremento automático con otra columna para uuid. Entonces, el rendimiento no se verá afectado. Estoy en lo cierto?
Chamnap
4
Estrictamente hablando, UUID es universalmente único, lo que significa que nunca aparecerá en ningún otro lugar del mundo. Solo lo necesita si comparte sus datos públicamente. En cuanto a almacenar un UUID como número, no me refiero al binaryformato. Me refiero a un número de 128 bits, en lugar de una cadena de 288 bits. Por ejemplo, la palabra 'hola' en ASCII es 68 65 6C 6C 6F, que es el número 448,378,203,247. El almacenamiento de la cadena '68656C6C6F' requiere 10 bytes. El número 448,378,203,247 requiere solo 5. En total, a menos que realmente necesite la primera U en UUID, no puede hacerlo mucho mejor queauto_increment
Dancrumb
1
@Chamnap: sugiero que haga una pregunta de desbordamiento de pila: o)
Dancrumb
78

En mi trabajo, usamos UUID como PK. Lo que puedo decirles por experiencia es que NO LOS USE como PKs (SQL Server por cierto).

Es una de esas cosas que cuando tienes menos de 1000 registros está bien, pero cuando tienes millones, es lo peor que puedes hacer. ¿Por qué? Debido a que los UUID no son secuenciales, cada vez que se inserta un nuevo registro, MSSQL debe ir a buscar la página correcta para insertar el registro y luego insertar el registro. La consecuencia realmente desagradable de esto es que las páginas terminan todas en diferentes tamaños y terminan fragmentadas, por lo que ahora tenemos que hacer una desfragmentación periódica.

Cuando usa un autoincremento, MSSQL siempre irá a la última página, y terminará con páginas del mismo tamaño (en teoría) por lo que el rendimiento para seleccionar esos registros es mucho mejor (también porque los INSERTs no bloquearán la tabla / página para hasta la vista).

Sin embargo, la gran ventaja de usar UUID como PK es que si tenemos clústeres de DB, no habrá conflictos al fusionar.

Recomendaría el siguiente modelo: 1. PK INT Identity 2. Columna adicional generada automáticamente como UUID.

De esta manera, el proceso de fusión es posible (UUID sería su clave REAL, mientras que PK sería algo temporal que le brinda un buen rendimiento).

NOTA: Que la mejor solución es usar NEWSEQUENTIALID (como decía en los comentarios), pero para la aplicación heredada con poco tiempo para refactorizar (y peor aún, no controlar todas las inserciones), no es posible hacerlo. Pero, de hecho, a partir de 2017, diría que la mejor solución aquí es NEWSEQUENTIALID o hacer Guid.Comb con NHibernate.

Espero que esto ayude

Kat Lim Ruiz
fuente
Realmente no sé qué significan esos términos, pero el hecho es que los índices deben volver a indexarse ​​todos los meses. Si lo que mencionas elimina la tarea de reindexar, no lo sé, pero puedo preguntar.
Kat Lim Ruiz
3
Algo que he estado pensando es que esto puede no funcionar tan bien para las relaciones entre padres e hijos. En este caso, creo que debe agregar en la tabla secundaria: parent-pk, parent-guid. De lo contrario, podría perder referencias entre bases de datos. No he pensado demasiado en esto, ni he hecho ningún ejemplo, pero esto puede ser necesario
Kat Lim Ruiz
4
@KatLimRuiz en el servidor SQL, puede usar NEWSEQUENTIALID () technet.microsoft.com/en-us/library/ms189786.aspx para evitar el problema de rendimiento
giammin
De hecho, pero NEWSEQUENTIALID solo funciona como DEFAULT. Por lo tanto, debe diseñar todo su DAL en torno a esto, lo cual está bien para nuevos proyectos pero no tan fácil para un gran legado
Kat Lim Ruiz
@KatLimRuiz genio. Ese es un gran compromiso
jmgunn87
26

Algo a tener en cuenta es que los aumentos automáticos se generan uno a la vez y no se pueden resolver con una solución en paralelo. La lucha por el uso de UUID finalmente se reduce a lo que desea lograr frente a lo que potencialmente sacrifica.

Sobre el rendimiento, brevemente :

Un UUID como el anterior tiene 36 caracteres, incluidos los guiones. Si almacena este VARCHAR (36), reducirá drásticamente el rendimiento de comparación. Esta es tu clave principal, no quieres que sea lenta.

En su nivel de bits, un UUID es de 128 bits, lo que significa que cabrá en 16 bytes, tenga en cuenta que esto no es muy legible por humanos, pero mantendrá el almacenamiento bajo y es solo 4 veces más grande que un int de 32 bits, o 2 veces más grande que un int de 64 bits. Usaré un VARBINARIO (16) Teóricamente, esto puede funcionar sin mucha sobrecarga.

Recomiendo leer las siguientes dos publicaciones:

Calculo que entre los dos responden a tu pregunta.

Kyle Rosendo
fuente
2
De hecho, leí ambos artículos antes de publicar esta pregunta y todavía no tenía una buena respuesta aquí. Por ejemplo, no se habla de UUIDS tipo 1 frente a tipo 4 :(
Patrick Lightbody
Justo eso, actualicé un poco mi respuesta. Sin embargo, no creo que proporcione demasiada información adicional.
Kyle Rosendo
@Patrick: pusiste demasiados temas diferentes en tu pregunta.
1
9 años después, pero también debe tenerse en cuenta para la posteridad que, a diferencia de las ID enteras, las aplicaciones pueden generar UUID de forma segura, eliminando la generación de la base de datos por completo. La manipulación de los UUID para la optimización del rendimiento (basada en la marca de tiempo pero modificada para que se puedan ordenar de forma ingenua) es notablemente más fácil en casi cualquier lenguaje que no sea SQL. Afortunadamente, casi todas las bases de datos actuales (incluido MySQL) manejan las claves primarias UUID mucho mejor de lo que solían hacerlo.
Miles Elam
5

Tiendo a evitar UUID simplemente porque es difícil de almacenar y difícil de usar como clave primaria, pero hay ventajas. El principal es que son ÚNICOS.

Por lo general, resuelvo el problema y evito UUID mediante el uso de campos de clave dual.

COLECTOR = ÚNICO ASIGNADO A UNA MÁQUINA

ID = REGISTRO RECOGIDO POR EL COLECTOR (campo auto_inc)

Esto me ofrece dos cosas. Velocidad de los campos de autoinclución y singularidad de los datos que se almacenan en una ubicación central después de que se recopilan y agrupan. También sé mientras navego por los datos dónde se recopilaron, lo que a menudo es bastante importante para mis necesidades.

He visto muchos casos al tratar con otros conjuntos de datos para clientes en los que han decidido usar UUID pero aún tienen un campo para el lugar donde se recopilaron los datos, lo que realmente es una pérdida de esfuerzo. Simplemente usar dos (o más si es necesario) campos como clave realmente ayuda.

Acabo de ver demasiados golpes de rendimiento con UUID. Se sienten tramposos ...

Glenn J. Schworak
fuente
3

En lugar de generar claves únicas de forma centralizada para cada inserción, ¿qué tal si asignamos bloques de claves a servidores individuales? Cuando se quedan sin claves, pueden solicitar un nuevo bloque. Luego, resuelve el problema de la sobrecarga conectando para cada inserto.

Keyserver mantiene la siguiente identificación disponible

  • El servidor 1 solicita el bloque de identificación.
  • El servidor de claves devuelve (1,1000) El
    servidor 1 puede insertar 1000 registros hasta que necesite solicitar un nuevo bloque
  • El servidor 2 solicita el bloque de índice.
  • Devuelve el servidor de claves (1001,2000)
  • etc ...

Podría crear una versión más sofisticada en la que un servidor podría solicitar la cantidad de claves necesarias o devolver los bloques no utilizados al servidor de claves, que por supuesto necesitaría mantener un mapa de bloques usados ​​/ no utilizados.

Bouke Versteegh
fuente
Sugerencia interesante en teoría. Esto sería complejo de gestionar en la práctica. Una solución más práctica probablemente sería la respuesta propuesta por schworak.
Simon East
2

Asignaría a cada servidor una identificación numérica de manera transaccional. Luego, cada registro insertado aumentará automáticamente su propio contador. La combinación de ServerID y RecordID será única. El campo ServerID se puede indexar y el rendimiento de selección futuro basado en ServerID (si es necesario) puede ser mucho mejor.

Nikolai
fuente
2

La respuesta corta es que muchas bases de datos tienen problemas de rendimiento (en particular con volúmenes INSERT altos) debido a un conflicto entre su método de indexación y la entropía deliberada de los UUID en los bits de orden superior. Hay varios trucos comunes:

  • elija un tipo de índice diferente (por ejemplo, no agrupado en MSSQL) que no le importe
  • munge los datos para mover la entropía a bits de orden inferior (por ejemplo, reordenar bytes de UUID V1 en MySQL)
  • hacer del UUID una clave secundaria con una clave primaria int de incremento automático

... pero todos estos son trucos, y probablemente frágiles.

La mejor respuesta, pero desafortunadamente la más lenta, es exigirle a su proveedor que mejore su producto para que pueda manejar los UUID como claves primarias como cualquier otro tipo. No deberían obligarlo a lanzar su propio truco a medias para compensar su fracaso en resolver lo que se ha convertido en un caso de uso común y solo continuará creciendo.

StephenS
fuente
1

¿Qué pasa con algunos UID hechos a mano? Dé a cada uno de los miles de servidores un ID y convierta la clave principal en una clave combinada de autoincremento, MachineID ???

MindStalker
fuente
He pensado en eso y es posible que necesite ejecutar algunos puntos de referencia. Incluso una secuencia local temporal en cada una de las 1000 máquinas, combinada con una marca de tiempo, podría ser suficiente. Ejemplo: machine_id + temp_seq + timestamp
Patrick Lightbody
¿Es posible tener una temp_sequence que se restablezca cada marca de tiempo? No estoy seguro.
MindStalker
1

Dado que la clave principal se genera de manera descentralizada, no tiene la opción de usar un auto_increment de todos modos.

Si no tiene que ocultar la identidad de las máquinas remotas, utilice UUID de tipo 1 en lugar de UUID. Son más fáciles de generar y al menos no pueden dañar el rendimiento de la base de datos.

Lo mismo ocurre con varchar (char, en realidad) frente a binario: solo puede ayudar en las cosas. ¿Es realmente importante, cuánto se mejora el rendimiento?


fuente