Si tengo una base de datos con 100 mil millones de nombres de usuario, ¿cómo construyo eficientemente una matriz ordenada a partir de eso para realizar fácilmente una búsqueda binaria?

P. “Si tengo una base de datos con 100 mil millones de nombres de usuario, ¿cómo construyo eficientemente una matriz ordenada a partir de eso para realizar fácilmente una búsqueda binaria?”

A menos que tenga un terabyte de RAM, tendría que acceder a su disco duro, mucho.

A menos que tenga un gran presupuesto para comprar un SSD por valor de terabyte, tendría que usar un disco duro giratorio.

Suponiendo que tiene un disco duro giratorio, el acceso aleatorio será muy costoso, de 10 a 100 ms. Por lo tanto, le gustaría minimizarlos tanto como pueda. Eso plantea la pregunta de si realmente desea realizar una búsqueda binaria en su estructura de datos final. Una búsqueda binaria en una matriz ordenada de 10 ^ 11 entradas, necesitaría [math] \ lceil log_2 (10 ^ {11}) \ rceil = 37 [/ math] operaciones de disco de acceso aleatorio. Suponiendo que los primeros 17 se ajusten a su caché de disco, necesitaría entre 0.2 y 2 segundos para encontrar un solo elemento con búsqueda binaria. Esto es horrible

Para eso, es mucho mejor usar una estructura de datos como B-tree o B + tree. Estos son n -arboles, donde cada nodo interno tiene n hijos. Dado que cuesta lo mismo traer 1 byte o 512 bytes del disco, o incluso más, dependiendo del tamaño del bloque. La lectura de varios bloques de discos adyacentes también es muy barata, por lo que un gran nodo de árbol B no es muy costoso. Si el nodo b-tree tiene 32 entradas, entonces necesita [math] \ lceil log_ {32} (10 ^ {11}) \ rceil = 8 [/ math] accesos de disco, y con caché de disco esto puede ser tan bajo como 4 accesos a disco. Esto costará solo 0.04–0.4 segundos, lo cual es mucho mejor.

También puede usar un Trie, que tendrá alrededor de 26 hijos en el nodo raíz, pero empeorará con cada paso que profundice. Para un nombre con 20 caracteres, deberá realizar 20 operaciones de acceso aleatorio en el disco, lo cual es bastante malo. En nombres cortos como Bill Gates, necesitará 10 operaciones de acceso aleatorio, lo cual es peor que el peor de los casos del árbol B.

Para mejorar las cosas, puede hacer un Trie que se ramifica en base a pares de letras. Entonces Bill Gates será representado como los pares [Bi] [ll] [G] [at] [es]. Esto acortará un poco el número de accesos aleatorios, pero aún así para nombres más largos como Kiefer William Frederick Dempsey George Rufus Sutherland todavía tendría que ejecutar 28 accesos aleatorios de disco. Esto será bastante lento. Si solo hay una persona cuyo nombre comienza con Kiefer William Frederick , podrá usar el Trie para almacenar este prefijo único y almacenar el nombre completo solo en la hoja. Sin embargo, esto todavía le da 12 solicitudes de disco, que todavía es bastante más lento que el árbol B.

Recientemente he implementado el árbol B + para optimizar el uso de la memoria caché de la CPU, lo que significa que el árbol b + es mucho más pequeño que el tuyo, pero todavía tengo algo de experiencia. En mi caso, los datos son dos veces más grandes que la clave, por lo que vale la pena usar el árbol B + en lugar de un árbol B. Además, las claves son lo suficientemente pequeñas como para caber 8 claves por línea de caché, lo que hace que valga la pena con respecto a la eficiencia del caché de la CPU.

Entonces, ¿cómo se crea un árbol b a partir de una lista aleatoria de nombres? Hay dos enfoques:

  1. Primero ordene la lista y cree un árbol B + de abajo hacia arriba de la lista. Encontré que este es el más eficiente para árboles B + basados ​​en memoria, con nodos internos de tamaño 8. Sin embargo, la clasificación en disco tiene un conjunto de costos diferente que la clasificación en RAM, por lo que esta observación puede ser completamente irrelevante para su caso. Sin embargo, la clasificación se puede realizar de manera relativamente eficiente en el disco (ver más abajo), y la construcción de abajo hacia arriba se puede hacer casi secuencialmente, minimizando el número de escrituras aleatorias de disco.
  2. Inserte un elemento a la vez en el árbol B. Esto incurrirá en escrituras en disco O (N), lo que me parece horrible. No creo que sea práctico construir ni B-tree ni B + tree para 10 ^ 11 entradas de esta manera.

Entonces, ¿cómo se ordena una gran lista en el disco? Lo haces por partes, para minimizar las escrituras aleatorias en el disco.

disk_sort (disk_array, from, to) {
if (a – desde <= MAX_IN_RAM) {
in_mem: = read (disk_array, offset = from,
tamaño = de – a + 1);
in_mem.sort ();
escribir (disk_array, offset = from);
} más {
medio = (a + desde) / 2;
disk_sort (disk_array, from, middle -1);
disk_sort (disk_array, middle, to);
fusionar (disk_array, from, middle, to);
}

Si permite un búfer de 8 GB de RAM, que podría permitir 250 * 10 ^ 6 entradas, entonces podría fusionar solo 400 fragmentos ordenados. Si puede permitirse varios discos y varios hosts, estos bloques se pueden ordenar en paralelo. La fusión de 400 fragmentos requerirá 9 profundidades de fusión, donde cada profundidad procesará 10 ^ 11 entradas. Esto va a ser lento, pero al menos la mayor parte del trabajo es secuencial, por lo que estará limitado solo por el rendimiento del disco y no por el tiempo de acceso.

Si puede dividir el trabajo entre 100 hosts, esto será mucho más rápido. Incluso la fusión en el último paso se puede paralelizar, cuando tiene dos grandes conjuntos, se puede paralelizar bastante bien incluso si están muy desequilibrados.

EDITAR:

Mirando la respuesta de Eugene Yarovoi, perdí una gran oportunidad de optimización y paralelización. Si hay varios hosts disponibles, es posible, durante una fase inicial, dividir el trabajo entre estos hosts según la primera letra, o dos, del nombre. Entonces, si la lista original se encuentra en un disco de 2TB, la tarea principal repasará todos los nombres y enviará todos los nombres que comienzan con “aa” a un host, con “ab” a otro, y así sucesivamente. Cada host ordenará los resultados por sí mismo, y más tarde será posible fusionar los resultados en el archivo final en el disco de 2TB o guardarlos en hosts separados. Cada host contendrá un árbol B para el sufijo de los nombres.

Entonces “Bill Gates” irá al host “Bi”, que tendrá la entrada “ll Gates” en su árbol B. Esto hace que las cosas sean mucho más rápidas tanto para clasificar como para servir, y permitirá un mayor rendimiento de servicio. Probablemente debería replicar los datos varias veces, para un mayor rendimiento de acceso y una mayor confiabilidad.

Debería haber un servicio de búsqueda, que puede centralizarse, que elegirá el mejor host para cada consulta. Este servicio tendrá que tener en cuenta la carga, y si un prefijo de nombre dado tiene demasiadas solicitudes, tendrá que replicar ese host durante el tiempo de inactividad. También tendrá que replicar un host en caso de falla del disco.

EDIT2:

De nuevo, la respuesta de Eugene Yarovoi. Sugiere poner los nombres en una tabla hash distribuida. Podría ser una solución mucho mejor que la mía, pero depende de los detalles que nos faltan en la pregunta. Si todo lo que necesita es encontrar un nombre en su estructura de datos, una tabla hash distribuida podría ser una solución mucho mejor, suponiendo que no obtenga demasiados nombres en un cubo. En teoría, es posible que haya más de 1,000 entradas en un solo depósito, incluso si la función hash es razonablemente efectiva.

Soy demasiado vago para hacer los cálculos, pero suponiendo algo como la paradoja del cumpleaños, es muy posible que algunos cubos de hash obtengan más de 1,000 entradas. Es posible colocar todas las entradas en un área consecutiva del disco, por lo que acceder a todas las entradas en el depósito no será demasiado costoso. Incluso si la entrada de las entradas en el depósito puede ser rápida, existe el riesgo de fragmentación debido a futuras actualizaciones y reasignaciones del depósito. Para superar este problema, es posible usar árboles B para almacenar las entradas en cada depósito. Así que volvemos a los árboles B.

Otra cosa a tener en cuenta es que no sabemos por la pregunta si se requiere o no tener una lista ordenada, ya que no conocemos todos los casos de uso. Si una búsqueda es todo lo que necesitamos, entonces un hash distribuido es perfecto. Si queremos hacer otras cosas, como encontrar el siguiente nombre en una lista. ¿Qué pasa si queremos contar la cantidad de nombres que comienzan con George , como George Washington y George Lucas , entonces no podemos usar la tabla hash?

Otra cosa que no sabemos es cuántos nombres únicos hay. Si solo hay 1,000,000 de nombres únicos que se repiten una y otra vez, entonces las cosas son bastante triviales y podemos hacerlo todo en RAM.

EDITAR3:

Estoy de acuerdo con la respuesta de Can Baysal: usar un buen servidor SQL es más seguro y menos propenso a errores que implementarlo usted mismo. Un servidor SQL hace que toda mi respuesta sea bastante redundante.

Sin embargo, si tiene curiosidad por saber cómo se puede hacer esto desde cero, o si desea construir algo grande (como un servidor SQL o Google), entonces debe aprender cómo hacerlo desde cero, sin usar una base de datos SQL. Esto es como preguntar ” ¿cómo implemento una tabla hash ?”, Que se enseña en los cursos de CS a pesar de que está disponible, de fábrica, en la mayoría de los lenguajes de programación. Y sí, tuve que implementar una tabla hash recientemente, debido a los requisitos especiales de rendimiento.

EDITAR4:

Lea la excelente respuesta de Cameron Purdy con respecto a Trie. Tiene una experiencia práctica mucho mejor que mis predicciones para Trie. Mi respuesta fue demasiado conservadora para asumir que tener 2TiB RAM no es práctico. Sin embargo, Cameron menciona esta cifra como práctica, y estoy de acuerdo. Suponiendo que distribuya el Trie entre 32 hosts, donde cada host tiene 64 GiB, entonces esta configuración es completamente plausible. Una vez que guarda todo en la memoria, ordenar y acceder a los datos se vuelve muy rápido: micro segundos, como nos dice Cameron.

La estructura de datos que está buscando es una Patricia Trie con resolución a nivel de personaje. Bien diseñado, y con transformadores frontales (es decir, manipulación de los nombres de usuario antes de la inserción, búsqueda, etc., con el fin de eliminar el duplicado), podría tener 100 mil millones de nombres de usuario en una estructura de datos en memoria utilizando una cantidad de memoria que es una fracción del tamaño de la base de datos.

Por ejemplo, si utiliza direcciones de correo electrónico como nombres de usuario, [correo electrónico protegido] podría ser “traducido por front-end” a “com / gmail / smith /./ joe”. Dado que hay muchas direcciones de gmail, y dado que hay muchos herreros, y dado que hay janes y josephs y johns, este nombre de usuario usaría un carácter (es decir, posiblemente un byte) para el almacenamiento del nombre de usuario, además de cualquier sobrecarga que tenga el nodo Trie (probablemente un límite de 8 o 16 bytes, si se empaqueta en C / C ++).

Entonces, para 100 mil millones de nombres de usuario, su costo de almacenamiento mínimo y medio y de modo es de 8 bytes por nombre de usuario, y solo tiene que tratar (optimizar para) valores atípicos. El dorso de la mano es de 90 mil millones a 8 bytes cada uno, y los 10 mil millones restantes a 64 bytes por pieza, más espacio adicional para los nodos no hoja del trie. Posiblemente podría empacar eso en un espacio de 2TB con tiempos de acceso de micro segundos.

El desafío es cuando desea asociar cualquier información adicional con esos nombres de usuario. Si desea almacenar un número de teléfono con cada uno, por ejemplo, ¡tendrá más del doble del tamaño de la estructura de datos!

Editar: Debo agregar que los Tries más grandes de Patricia (radix) que construí tenían alrededor de 60 GB y usaban una resolución de octeto (y un procesamiento de-dup global de fondo). Eran dramáticamente más rápidos que las búsquedas de tablas hash (ya que el costo del recorrido de Trie es casi el mismo que el costo de solo hash la clave !!!), y entre 3 y 10 veces más compacto (dependiendo del conjunto de datos). La única información asociada era un valor de 64 bits que podía usarse para representar información en la memoria o en el disco, es decir, cualquier cantidad de información asociada a través de un nivel de desreferencia. Como parte de un almacén de datos distribuido, esto permitía un acceso por debajo de milésimas de clave primaria a unas pocas docenas de terabytes de datos.

Respuesta corta: no lo haces.

Respuesta media: no lo haces.

Respuesta larga:

Por lo tanto, no tengo la menor idea de cómo conseguiste 100 mil millones de nombres de usuario, ya que el mundo solo contiene ocho mil millones de usuarios posibles, la mayoría de los cuales ni siquiera usan tu aplicación. Supongo que tiene una máquina del tiempo e invitó a un montón de personas medievales, galas y de la edad de piedra a usar su aplicación. Esto en contraste con solo haber raspado una tonelada de sitios y robado miles de millones de nombres de usuario de Google, Facebook y Quora.

¿Por qué es importante? Metadatos

El primer escenario implica que tiene datos que no sean solo nombres de usuario en su aplicación. Posiblemente petabytes de datos. Como está trabajando con un gran sistema distribuido, simplemente ordenar en un disco ya no es una opción.

Y ahora que lo pienso, tampoco es una búsqueda binaria.

Por lo tanto, en lugar de intentar la gigantesca tarea de ordenar todos los nombres de usuario, le propongo: la tabla de hash distribuida. Y no importa cuánto escriba sobre este tema, no será suficiente para explicar todos los usos y opciones de diseño de un DHT, por lo que me limitaré al núcleo.

Tome 26 nodos, almacene en el nodo 1 todos los nombres de usuario que comienzan con A, en el nodo 2 todos los nombres de usuario que comienzan con B, y así sucesivamente. Cuando necesite buscar un nombre de usuario, simplemente verifique la primera letra y luego acceda al nodo que contiene los nombres de usuario que comienzan con esa letra.

Eso es lo básico. Las verdaderas aplicaciones DHT también permiten agregar nodos dinámicos, fallas, etc.

Su pregunta es la motivación para que tome una clase de estructuras de datos.

Las diferentes estructuras de datos tienen diferentes algoritmos y propiedades de rendimiento para diferentes tareas.

No dijo si su base de datos estaba en memoria, estaba en un archivo de disco, estaba en un archivo de disco indexado, si los nombres eran texto ASCII, UTF-8 o enteros de 64 bits que representan los 10 caracteres más significativos de nombre, si iba a ordenar los nombres una vez, si iba a ordenar los nombres miles de veces por minuto, si iba a usar los nombres ordenados para indexar su base de datos o alguna otra base de datos … y obtendría un nombre diferente responda para cada una de estas variaciones.

En primer lugar, si tiene 100 mil millones de nombres de usuario, felicidades señor, mi tarifa de consultoría suele ser de 1000 € / día, pero para usted iría por 10 k. € / hora, ya que está vendiendo su servicio 14 veces a todos y cada uno de los humanos en el planeta con algo de repuesto …

Aparte de la parte comercial anterior; Si tiene que ordenar y buscar 100 mil millones de registros, no podrá utilizar la mayoría de los formatos de almacenamiento sin formato convencionales, los archivos serían más grandes que los tamaños de archivo permitidos (incluso la partición), cualquier corrección de datos implicaría varias lecturas de archivos no tan regulares / operaciones de escritura, etc. En teoría, puede dividir datos en varios archivos, como AA, AB, AC, …… ZY, ZX, ZZ y colocar registros relevantes en archivos relevantes y luego ordenar estos archivos dentro de ellos mismos. Para la búsqueda de manera similar, abre el archivo relevante y realiza la búsqueda solo en ese archivo.

Sin embargo, si estamos hablando de un caso de la vida real (consulte el primer párrafo nuevamente 🙂), es mejor dejar la administración de datos de un conjunto de datos tan grande a un sistema de administración de datos como un servidor SQL. Luego, el servidor administraría la clasificación, la indexación y la búsqueda sin molestarlo con detalles como la búsqueda binaria o la clasificación manual, etc.

Si tengo una base de datos con 100 mil millones de nombres de usuario, ¿cómo construyo eficientemente una matriz ordenada a partir de eso para realizar fácilmente una búsqueda binaria?

Esta es la pregunta incorrecta por un par de razones.

Como otros han señalado, tener 100 mil millones de nombres de usuario en un mundo con 7,4 mil millones de personas es problemático.

Pero, quizás lo más importante, ha decidido prematuramente una implementación y esto le ha llevado a hacer la pregunta equivocada.

Estás utilizando una base de datos, pero no la aprovechas. Cuando haces cosas como “voy a obtener datos de la base de datos, y luego los ordenaré alfabéticamente” o “voy a obtener todos los registros de la base de datos para poder buscarlos”, doble -Compruebe para asegurarse de que esto es algo que realmente quiere hacer.

Suponiendo que desea conservar sus 100 mil millones de elementos alfabéticos, la pregunta probablemente debería ser qué bases de datos pueden buscar en esos registros de manera rápida y efectiva. Por ejemplo, no creo que SQL Server tenga problemas con tantas filas en una tabla: Especificaciones de capacidad máxima para SQL Server.

Suponiendo que tiene nombres de usuario de 20 letras en promedio, se trata de 2 TB de datos. Si desea ejecutarlo en una máquina, tiene algunas opciones.

Opción 1. B-tree. Lo almacena en un B-Tree y un sistema de base de datos moderno también proporcionará aproximadamente 2x de compresión para que termine con 1Tb B-Tree. Si está haciendo búsquedas aleatorias, pagará IO, si su conjunto de trabajo es lo suficientemente pequeño, buscará en la memoria y la mayor parte del costo es navegar a una página B-Tree y analizar la página.

Opción 2. Distribuido en memoria. En un sistema como MemSQL, puede almacenar este conjunto de datos en una tabla en memoria. No tendrá beneficios de compresión, pero tendrá un rendimiento increíble para sus búsquedas: sus búsquedas utilizarán todos los núcleos en el clúster y no tendrá gastos generales al analizar las páginas B-Tree.

Opción 3. Almacén de columnas ordenado. Si coloca nombres de usuario en un almacén de columnas en el disco, comprimirá los datos entre 5 y 10 veces. Esta será una de las representaciones más compactas del conjunto de datos. En el caso de MemSQL, puede ordenar el almacén de columnas y MemSQL dividirá el conjunto de datos en segmentos comprimidos si hay 100K (configurables) filas de tamaño. Una búsqueda en dicha representación requerirá navegar a un segmento usando búsqueda binaria (superrápido en más de 200K segmentos) y un escaneo de tabla de 1M con 100M / filas de segundo para que pueda navegar a una fila en aproximadamente 1 milisegundo.

Opción 4. Las estructuras de datos personalizadas, como los intentos o los árboles de sufijos, le proporcionarán muy buenos tiempos de búsqueda en papel, pero no proporcionarán el valor que proviene de la compresión.

Otros dicen que no debes hacer la pregunta porque no hay 100B personas o deberías haber usado una mejor estructura de datos en primer lugar.

Pero, ¿qué pasa si realmente tienes extraterrestres en tu archivo? ¿Qué pasa si es solo un archivo plano? Oye, sucede una mierda … ¿No tienes suficiente RAM para la clasificación rápida o algo así?

No te preocupes

Es un viejo problema. La gente tenía que ordenar archivos grandes cuando las computadoras solo tenían unos pocos kb de RAM y el único almacenamiento externo era la cinta.

La pregunta fue respondida en 1948.

La solución es un tipo de fusión. Necesitas tres medios. Cada uno lo suficientemente grande como para contener todos los datos. Solo se leen y escriben de forma secuencial para que las cintas hagan el trabajo. O archivos. Por razones de rendimiento, póngalos en tres discos duros separados.

Tiene que escanear el registro de datos 2 (n) veces, para registros de 100B que es 38 veces. Como escanea todos los datos durante cada pasada, la complejidad del tiempo total es O (n * log n).

Si sus datos son de 1 TB y las unidades producen 100 MB por segundo, el proceso completo tardaría unos 5 días. Menos de un día si utiliza SSD modernos de alto rendimiento. Por lo tanto, está dentro de las limitaciones prácticas.

Busque el tipo de combinación en Wikipedia, esp. sobre la implementación usando cinta.

cristiano

La respuesta es en parte que no construyes una matriz ordenada para realizar búsquedas binarias; porque leer un volumen tan grande en la memoria conduce a problemas. Más bien, haces una búsqueda binaria en el disco. Por lo tanto, debe almacenar su colección en un Árbol (Cálculo Aplicado – Árbol) o Diccionario (Cálculo Aplicado – Diccionario) y hacer las búsquedas en el disco. 100 mil millones a 1000 bytes por elemento lleva a 100 terabytes de almacenamiento, por lo que deberá acelerar su incursión, pero AVL Trees manejará la tarea de manera eficiente en SSD.

Esta estructura de datos o algoritmo no le dará una matriz ordenada de nombre de usuario, sino una forma eficiente de buscar un nombre de usuario desde una base de datos.

La estructura de datos utilizada aquí es un nodo Trie. La estructura de datos comprende tres tipos de datos, una char ‘x’, una cadena ‘contraseña’ y un mapa hash del nodo Trie o un diccionario.

El primer paso es almacenar los nombres de usuario dados,

  1. Primero creamos una raíz
  2. Tome el primer nombre de usuario, el primer carácter es ‘a’ en mi ejemplo. Por lo tanto, busca en el mapa hash la clave ‘a’ y si está presente se mueve al nodo Trie presente en esa clave y, si no está, crea una clave con el nombre ‘a’ y se mueve a ese nodo.
  3. Ahora, busca ‘b’ en el mapa hash del nodo ‘a’ y hace lo mismo que se mencionó anteriormente y se mueve de nuevo.
  4. Este método continúa hasta que hemos llegado al final de la palabra tomada. Ahora, como sabemos que el nodo en el que estamos presentes ahora es el final de uno de los nombres de usuario, almacene la contraseña de este nombre de usuario en la cadena de este nodo. (La mejor parte es que, dado que ningún otro nombre de usuario termina en este nodo, esta es la mejor manera de almacenar la contraseña).
  5. Ahora, ha almacenado todos los nombres de usuario y contraseñas y le gustaría saber o recuperar la contraseña de algún nombre de usuario, luego comenzar a recorrer desde el nodo raíz y llegar al nodo donde se había atravesado el último carácter del nombre de usuario y si el la cadena no es nula o ninguna toma / proporciona la contraseña y si la cadena en este nodo está configurada como nula o ninguna, eso significa que no hay un nombre de usuario presente en la base de datos. (Se puede aplicar el mismo truco para verificar la disponibilidad de un nombre de usuario)

B + árboles teniendo en cuenta la carga perezosa

More Interesting

Un k-palíndromo es una cadena que se transforma en un palíndromo al eliminar como máximo k caracteres de él. Dada una cadena S y un número entero K, ¿encuentra si S es un k-palíndromo o no? Restricciones: S tiene como máximo 20,000 caracteres y 0 <= k <= 30

¿Cuál es el algoritmo más eficiente para encontrar el késimo elemento más pequeño en una matriz que tiene n elementos desordenados?

Cómo hacer que el código de una ordenación de inserción sea más optimizado utilizando una lista vinculada

¿Qué técnicas eficientes ha intentado rastrear un algoritmo o un código de programa manualmente, sin usar una computadora?

¿Cuánta codificación necesito saber antes de comenzar con los algoritmos?

¿Cómo elige Chrome el color de las rayas en las miniaturas de la página "Más visitadas"? Está claramente basado en el favicon, pero no puedo decir exactamente cómo se deriva.

¿Cómo uso vectores para una matriz 2D en C ++?

Cómo inicializar una matriz de cadenas en una clase

Para (I = 0; I <3; I ++) fork (), ¿cómo puedo hacer un algoritmo para contar el número de procesos y mostrarlo solo una vez?

¿Cuál es el número total de comparaciones en un tipo de burbuja?

¿Cómo se escriben los algoritmos de espacio?

¿Qué algoritmo puedo usar para encontrar el camino más corto en un sistema de variante de tiempo?

¿Cómo implementas quicksort en c? Sé que hay respuestas disponibles en línea, pero estoy buscando idealmente la forma más elegante.

¿Por qué usamos el árbol de búsqueda binario?

¿Cómo funcionan los algoritmos de Quora para las respuestas?