¿Cuáles son las mejoras de rendimiento de Sequential Guid sobre Guid estándar?

¿Alguien ha medido alguna vez el rendimiento de la guía secuencial frente a la guía estándar cuando se utiliza como claves principales dentro de una base de datos?

GUID vs. GUID SECUENCIAL

Un patrón típico es usar Guid como PK para tablas, pero, como se menciona en otras discusiones (ver Ventajas y desventajas de las claves de base de datos GUID / UUID ), hay algunos problemas de rendimiento.

Esta es una secuencia típica de Guid

f3818d69-2552-40b7-a403-01a6db4552f7
7ce31615-fafb-42c4-b317-40d21a6a3c60
94732fc7-768e-4cf2-9107-f0953f6795a5

Los problemas de este tipo de datos son: <

  • Amplia distribución de valores
  • Casi aleatoriamente unos
  • El uso del índice es muy, muy, muy malo
  • Mucho movimiento de hojas
  • Casi todos los PK deben estar al menos en un índice no agrupado
  • El problema ocurre tanto en Oracle como en SQL Server

Una posible solución es usar Guiado Secuencial, que se genera de la siguiente manera:

cc6466f7-1066-11dd-acb6-005056c00008
cc6466f8-1066-11dd-acb6-005056c00008
cc6466f9-1066-11dd-acb6-005056c00008

Cómo generarlos Desde el código C #:

[DllImport("rpcrt4.dll", SetLastError = true)] static extern int UuidCreateSequential(out Guid guid); public static Guid SequentialGuid() { const int RPC_S_OK = 0; Guid g; if (UuidCreateSequential(out g) != RPC_S_OK) return Guid.NewGuid(); else return g; } 

Beneficios

  • Mejor uso del índice
  • Permitir el uso de claves agrupadas (para ser verificadas en escenarios NLB)
  • Menos uso de disco
  • 20-25% del rendimiento aumenta a un costo mínimo

Medida de la vida real: Escenario:

  • Guid almacenado como tipos UniqueIdentifier en SQL Server
  • Guid almacenado como CHAR (36) en Oracle
  • Muchas operaciones de inserción, agrupadas en una sola transacción
  • De 1 a 100 pies de insertos dependiendo de la mesa
  • Algunas tablas> 10 millones de filas

Prueba de laboratorio – SQL Server

Prueba VS2008, 10 usuarios simultáneos, sin tiempo de reflexión, proceso de referencia con 600 inserciones en lote para la tabla de hojas
Guid estándar
Promedio Duración del proceso: 10.5 segundos
Promedio Solicitud de segundo: 54.6
Promedio Resp. Tiempo: 0.26

Guiado Secuencial
Promedio Duración del proceso: 4.6 segundos
Promedio Solicitud de segundo: 87.1
Promedio Resp. Tiempo: 0.12

Resultados en Oracle (lo siento, herramienta diferente utilizada para la prueba) 1.327.613 insertar en una tabla con un Guid PK

Guía estándar , 0.02 seg. tiempo transcurrido para cada inserción, 2.861 seg. de tiempo de CPU, un total de 31.049 seg. transcurrido

Guía secuencial , 0.00 seg. tiempo transcurrido para cada inserción, 1.142 seg. de tiempo de CPU, un total de 3.667 seg. transcurrido

El tiempo de espera de lectura secuencial del archivo DB pasó de 6.4 millones a eventos de espera durante 62.415 segundos a 1.2 millones de eventos de espera durante 11.063 segundos.

Es importante ver que se puede adivinar todo el guid secuencial, por lo que no es una buena idea usarlos si la seguridad es una preocupación, aún usando el guid estándar.
Para abreviar … si usa Guid como PK, utilice el guiado secuencial cada vez que no se pasen de una interfaz de usuario hacia adelante y hacia atrás, acelerarán la operación y no le costará nada implementar.

Puede que me falta algo aquí (no dude en corregirme si lo hago), pero puedo ver muy poco beneficio en el uso de GUID / UUID secuenciales para claves primarias.

El objective de usar GUID o UUID sobre enteros autoincrementados es:

  • Se pueden crear en cualquier lugar sin ponerse en contacto con la base de datos
  • Son identificadores que son completamente únicos dentro de su aplicación (y en el caso de los UUID, universalmente únicos)
  • Dado un identificador, no hay forma de adivinar el siguiente o el anterior (o incluso cualquier otro identificador válido) fuera de la fuerza bruta de un espacio de teclado enorme .

Desafortunadamente, al usar tu sugerencia, pierdes todas esas cosas.

Entonces sí. Has mejorado los GUID. Pero en el proceso, has desechado casi todas las razones para usarlas en primer lugar.

Si realmente desea mejorar el rendimiento, use una clave primaria de entero autoincrementing estándar. Eso proporciona todos los beneficios que describió (y más) a la vez que es mejor que una “guía secuencial” en casi todos los sentidos.

Es muy probable que esto se reduzca al olvido, ya que no responde específicamente a su pregunta (que al parecer es cuidadosamente diseñada para que usted mismo pueda responderla de inmediato), pero creo que es un punto mucho más importante que plantear.

Como ya dijo massimogentilini, el rendimiento puede mejorarse al usar UuidCreateSequential (cuando se generan las guías en el código). Pero parece que falta un hecho: SQL Server (al menos Microsoft SQL 2005/2008) utiliza la misma funcionalidad, PERO: la comparación / ordenación de Guids difiere en .NET y en SQL Server, lo que aún causaría más IO, porque las guías no se ordenarán correctamente. Para generar las guías ordenadas correctamente para el servidor sql (pedido), debe hacer lo siguiente (ver detalles de comparación ):

 [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)] static extern int UuidCreateSequential(byte[] buffer); static Guid NewSequentialGuid() { byte[] raw = new byte[16]; if (UuidCreateSequential(raw) != 0) throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); byte[] fix = new byte[16]; // reverse 0..3 fix[0x0] = raw[0x3]; fix[0x1] = raw[0x2]; fix[0x2] = raw[0x1]; fix[0x3] = raw[0x0]; // reverse 4 & 5 fix[0x4] = raw[0x5]; fix[0x5] = raw[0x4]; // reverse 6 & 7 fix[0x6] = raw[0x7]; fix[0x7] = raw[0x6]; // all other are unchanged fix[0x8] = raw[0x8]; fix[0x9] = raw[0x9]; fix[0xA] = raw[0xA]; fix[0xB] = raw[0xB]; fix[0xC] = raw[0xC]; fix[0xD] = raw[0xD]; fix[0xE] = raw[0xE]; fix[0xF] = raw[0xF]; return new Guid(fix); } 

o este enlace o este enlace .

Si necesita utilizar GUIds secuenciales, SQL Server 2005 puede generarlos para usted con la función NEWSEQUENTIALID() .

Sin embargo, dado que el uso básico de las GUIds es generar claves (o claves alternativas) que no pueden adivinarse (por ejemplo, para evitar que las personas pasen las claves adivinadas en GET), no veo qué tan aplicables son porque son fáciles de adivinar.

Desde MSDN :

Importante:
Si la privacidad es una preocupación, no use esta función. Es posible adivinar el valor del siguiente GUID generado y, por lo tanto, acceder a los datos asociados con ese GUID.

Vea este artículo: ( http://www.shirmanov.com/2010/05/generating-newsequentialid-compatible.html )

Aunque MSSql usa esta misma función para generar NewSequencialIds (UuidCreateSequential (out Guid guid)), MSSQL invierte los patrones de byte 3 y 4 que no le dan el mismo resultado que obtendría al usar esta función en su código. Shirmanov muestra cómo obtener exactamente los mismos resultados que crearía MSSQL.

Echa un vistazo a los COMB por Jimmy Nilsson: un tipo de GUID donde se han reemplazado varios bits con un valor similar a la marca de tiempo. Esto significa que los COMB pueden ordenarse y, cuando se utilizan como clave principal, se obtienen menos divisiones de páginas de índice cuando se insertan valores nuevos.

¿Está bien utilizar un identificador único (GUID) como clave principal?

Modifiqué la diferencia entre Guid (agrupado y no agrupado), Sequential Guid e int (Identity / autoincrement) usando Entity Framework. El Guid Secuencial fue sorprendentemente rápido en comparación con el int con identidad. Los resultados y el código del Guid Secuencial aquí .

No veo la necesidad de claves únicas para ser adivinable o no, pasándolos desde una IU web o en alguna otra parte parece una mala práctica por sí mismo y no veo, si tiene problemas de seguridad, cómo puede mejorarse el uso de un GUID cosas (si este es el problema, use un generador de números aleatorios reales usando las funciones criptográficas correctas del marco).
Los otros elementos están cubiertos por mi enfoque, se puede generar un guid secuencial a partir del código sin necesidad de acceso a BD (también si solo para Windows) y es único en tiempo y espacio.
Y sí, la pregunta fue formulada con la intención de responderla, para brindar a las personas que eligieron Guids para su PK una forma de mejorar el uso de la base de datos (en mi caso, los clientes pudieron soportar una carga de trabajo mucho mayor sin tener que cambiar de servidor).

Parece que las preocupaciones de seguridad son muchas, en este caso, no use Sequential Guid o, mejor aún, use Guid estándar para PK que se transfiere hacia adelante y hacia atrás desde su UI y guía secuencial para todo lo demás. Como siempre no hay una verdad absoluta, he editado también la respuesta principal para reflejar esto.

OK, finalmente llegué a este punto en diseño y producción.

Genero un COMB_GUID donde los 32 bits superiores se basan en los bits 33 a 1 del tiempo de Unix en milisegundos. Entonces, hay 93 bits de aleatoriedad cada 2 milisegundos y la reinversión en los bits superiores ocurre cada 106 años. La representación física real de COMB_GUID (o UUID de tipo 4) es una versión codificada en base64 de los 128 bits, que es una cadena de 22 caracteres.

Al insertar en postgres, la relación de velocidad entre un UUID completamente aleatorio y un _CUID COMB se mantiene como beneficioso para el COMB_GUID. El COMB_GUID es 2 veces más rápido en mi hardware en múltiples pruebas, para una prueba de registro de un millón. Los registros contienen la identificación (22 caracteres), un campo de cadena (110 caracteres), una precisión doble y una INT.

En ElasticSearch, NO hay diferencia discernible entre los dos para la indexación. Todavía voy a usar COMB_GUIDS en caso de que el contenido vaya a los índices de BTREE en cualquier parte de la cadena ya que el contenido está relacionado con el tiempo de alimentación, o puede ser preseleccionado en el campo de identificación para que sea temporal y parcialmente secuencial, se acelerará.

Bastante interesante. El código de Java para hacer un COMB_GUID está debajo.

 import java.util.Arrays; import java.util.UUID; import java.util.Base64; //Only avail in Java 8+ import java.util.Date; import java.nio.ByteBuffer; private ByteBuffer babuffer = ByteBuffer.allocate( (Long.SIZE/8)*2 ); private Base64.Encoder encoder = Base64.getUrlEncoder(); public String createId() { UUID uuid = java.util.UUID.randomUUID(); return uuid2base64( uuid ); } public String uuid2base64(UUID uuid){ Date date= new Date(); int intFor32bits; synchronized(this){ babuffer.putLong(0,uuid.getLeastSignificantBits() ); babuffer.putLong(8,uuid.getMostSignificantBits() ); long time=date.getTime(); time=time >> 1; // makes it every 2 milliseconds intFor32bits = (int) time; // rolls over every 106 yers + 1 month from epoch babuffer.putInt( 0, intFor32bits); } //does this cause a memory leak? return encoder.encodeToString( babuffer.array() ); } 

}