java - ¿MappedByteBuffer o Direct ByteBuffer mapeado por memoria para la implementación de DB?
file-io database-design (2)
Esto parece una pregunta larga debido a todo el contexto. Hay 2 preguntas dentro de la novela a continuación. Gracias por tomarse el tiempo para leer esto y proporcionar asistencia.
Situación
Estoy trabajando en una implementación de almacenamiento de datos escalable que pueda admitir el trabajo con archivos de datos de unos pocos KB a un TB o más en un sistema de 32 o 64 bits.
El almacén de datos utiliza un diseño de copiado sobre escritura; siempre agregando datos nuevos o modificados al final del archivo de datos y nunca haciendo ediciones en el lugar de los datos existentes.
El sistema puede alojar 1 o más bases de datos; cada uno representado por un archivo en el disco.
Los detalles de la implementación no son importantes; el único detalle importante es que necesito anexar constantemente al archivo y hacerlo crecer desde KB, MB, GB a TB, al mismo tiempo omitiendo aleatoriamente el archivo para que las operaciones de lectura respondan a las solicitudes de los clientes.
Primeros pensamientos
A primera vista, sabía que quería usar archivos mapeados en memoria para poder cargar la carga de administrar eficientemente el estado en memoria de los datos en el sistema operativo host y fuera de mi código.
Entonces, todo mi código debe preocuparse por serializar las operaciones de anexar al archivo por escrito, y permitir que cualquier cantidad de lectores simultáneos busque en el archivo para responder a las solicitudes.
Diseño
Debido a que los archivos de datos individuales pueden crecer más allá del límite de 2 GB de un MappedByteBuffer, espero que mi diseño tenga que incluir una capa de abstracción que tome un desplazamiento de escritura y lo convierta en un desplazamiento dentro de un segmento específico de 2GB.
Hasta aquí todo bien...
Problemas
Aquí es donde empecé a colgar y creo que ir con un diseño diferente (propuesto a continuación) podría ser la mejor manera de hacerlo.
A partir de la lectura de hasta 20 o más preguntas relacionadas con la "asignación de memoria" aquí en SO, parece que las llamadas mmap son sensibles a querer ejecuciones de memoria contiguas cuando se asignan. Entonces, por ejemplo, en un sistema operativo host de 32 bits si traté de mapear un archivo de 2GB debido a la fragmentación de la memoria, mis posibilidades de que el mapeo sea limitado y en su lugar debería usar algo así como una serie de mapeos de 128MB para extraer todo presentar en.
Cuando pienso en ese diseño, incluso usando tamaños mmap de 1024MB, para un DBMS que aloja unas bases de datos enormes todas representadas por digamos archivos de 1TB, ahora tengo miles de regiones mapeadas en memoria en memoria y en mis propias pruebas en Windows 7 intentando para crear unos cientos de mmaps en un archivo de varios GB, no me encontré con excepciones, de hecho conseguí la JVM segfault cada vez que traté de asignar demasiado y en un caso obtuve el video en mi máquina con Windows 7 para recortar y reiniciar con una ventana emergente de error de SO que nunca había visto antes.
Independientemente del argumento de "nunca manejarás archivos tan grandes" o "este es un ejemplo inventado", el hecho de que podría codificar algo así con ese tipo de efectos secundarios pone a mi alarma interna en estado de alerta máxima y hecho considerar una alternativa impl (abajo).
ADEMAS de ese problema, mi comprensión de los archivos mapeados en memoria es que tengo que volver a crear la asignación cada vez que crezca el archivo, por lo que en el caso de este archivo que se agrega solo al diseño, literalmente crece constantemente.
Puedo combatir esto hasta cierto punto haciendo crecer el archivo en fragmentos (digamos 8MB a la vez) y solo volver a crear el mapeo cada 8MB, pero la necesidad de volver a crear constantemente estas asignaciones me pone nervioso especialmente sin una función explícita de eliminación de mapas compatible con Java .
Pregunta 1 de 2
Teniendo en cuenta todos mis hallazgos hasta este punto, descartaría los archivos mapeados en memoria como una buena solución para soluciones principalmente de lectura pesada o soluciones de solo lectura, pero no para soluciones de escritura, dada la necesidad de volver a crear el mapeo constantemente.
Pero luego miro alrededor del paisaje a mi alrededor con soluciones como MongoDB que abarcan archivos mapeados en memoria por todos lados y siento que me falta algún componente central aquí (sé que tiene allocs en algo así como extensiones de 2GB a la vez, entonces Me imagino que están trabajando en el costo de volver a mapear con esta lógica Y ayudando a mantener ejecuciones secuenciales en el disco).
En este punto, no sé si el problema es la falta de una operación de desasignar de Java que hace que esto sea mucho más peligroso e inadecuado para mis usos o si mi comprensión es incorrecta y alguien me puede señalar hacia el norte.
Diseño alternativo
Un diseño alternativo al mapeado en memoria propuesto anteriormente que iré si mi entendimiento de mmap es correcto es el siguiente:
Defina un ByteBuffer directo de un tamaño configurable razonable (2, 4, 8, 16, 32, 64, 128 KB aproximadamente), lo que lo hace fácilmente compatible con cualquier plataforma de host (no necesita preocuparse por que el mismo DBMS provoque escenarios complicados) y el uso el FileChannel original, realiza lecturas de desplazamiento específicas del archivo 1 buffer-capacity-chunk a la vez, omitiendo por completo los archivos mapeados en memoria.
La desventaja es que ahora mi código tiene que preocuparse por cosas como "¿He leído lo suficiente del archivo para cargar el registro completo?"
Otro inconveniente es que no consigo hacer uso de la lógica de la memoria virtual del sistema operativo, permitiéndole guardar más datos "activos" en la memoria automáticamente; en su lugar, solo tengo que esperar que la lógica de caché de archivos utilizada por el sistema operativo sea lo suficientemente grande como para hacer algo útil para mí aquí.
Pregunta n. ° 2 de 2
Esperaba obtener una confirmación de mi comprensión de todo esto.
Por ejemplo, tal vez el caché de archivos es fantástico, en ambos casos (memoria asignada o lecturas directas), el sistema operativo host mantendrá la mayor cantidad posible de datos calientes y la diferencia de rendimiento para los archivos grandes es insignificante.
O tal vez mi comprensión de los requisitos sensibles para los archivos mapeados en memoria (memoria contigua) es incorrecta y puedo ignorar todo eso.
Creo que no deberías preocuparte por mapear archivos de hasta 2GB de tamaño.
Mirando las fuentes de MongoDB como un ejemplo de DB haciendo uso de archivos mapeados en memoria, siempre encontrará un archivo completo de datos en MemoryMappedFile::mapWithOptions() (que llama a MemoryMappedFile::map() ). Los datos de base de datos abarcan múltiples archivos de hasta 2 GB de tamaño. También preasigna archivos de datos por lo que no es necesario reasignar a medida que crecen los datos y esto evita la fragmentación de archivos. En general, puede inspirarse con el código fuente de este DB.
Usted podría estar interesado en https://github.com/peter-lawrey/Java-Chronicle
En esto, creo múltiples asignaciones de memoria en el mismo archivo (el tamaño es una potencia de 2 hasta 1 GB) El archivo puede ser de cualquier tamaño (hasta el tamaño de su disco duro)
También crea un índice para que pueda encontrar cualquier registro al azar y cada registro puede ser de cualquier tamaño.
Puede compartirse entre procesos y utilizarse para eventos de baja latencia entre procesos.
Supongo que está utilizando un sistema operativo de 64 bits si desea utilizar grandes cantidades de datos. En este caso, una Lista de MappedByteBuffer será todo lo que necesites. Tiene sentido usar las herramientas adecuadas para el trabajo. ;)
Lo he encontrado muy bien incluso con tamaños de datos de alrededor de 10 veces el tamaño de tu memoria principal (estaba usando una unidad SSD rápida para YMMV)