Introduccion
En Internet, hay mucha informaci贸n y debate sobre la elecci贸n del enfoque sql / nosql, as铆 como las ventajas y desventajas de uno u otro almacenamiento KV. Lo que est谩 leyendo en este momento no es un manual o agitaci贸n de rocksdb para usar este repositorio y mi controlador para 茅l. Me gustar铆a compartir el resultado provisional de la optimizaci贸n del proceso de desarrollo de NIF para Erlang. Este art铆culo presenta un controlador viable para rocksdb, desarrollado durante un par de noches.
Entonces, en uno de los proyectos, la tarea surgi贸 del procesamiento confiable de un gran volumen de eventos. Cada evento toma de 50 a 350 bytes, se generan m谩s de 80 millones de eventos por nodo por d铆a. Solo quiero se帽alar que no se consideran los problemas de tolerancia a fallos de la entrega de mensajes a los nodos. Tambi茅n una de las restricciones de procesamiento es el cambio at贸mico y consistente del grupo de eventos.
Por lo tanto, los requisitos principales para el conductor son:
- Fiabilidad
- Rendimiento
- Seguridad (en el sentido can贸nico)
- Funcionalidad
- Todas las funciones b谩sicas de kv
- Familias de columnas
- Transacciones
- Compresi贸n de datos
- Soporte de configuraci贸n de almacenamiento flexible.
- Base m铆nima de c贸digo
Resumen de soluciones existentes
- erocksdb es una soluci贸n de desarrolladores de leofs. Las ventajas incluyen la aprobaci贸n en un proyecto real. Por contra: una base de c贸digo obsoleta y falta de transaccionalidad. Este controlador se basa en rocksdb 4.13.
- Rockse tiene una serie de limitaciones, por ejemplo, la falta de opciones de configuraci贸n, pero lo m谩s importante, todas las claves y valores deben ser cadenas. Se meti贸 en la revisi贸n solo como un ejemplo de una serie de controladores que implementan uno u otro funcional y limitan otro.
- erlang-rocksdb es un proyecto completo, cuyo desarrollo comenz贸 en 2014. Al igual que erocksdb se usa en proyectos reales. Tiene una gran base de c贸digo en C / C ++ y una amplia funcionalidad. Este controlador es adecuado para la pr谩ctica general y el uso en la mayor铆a de los proyectos.
Despu茅s de un an谩lisis r谩pido de la situaci贸n actual con los controladores de erlang para rocksdb, qued贸 claro que ninguno de ellos cumple completamente con los requisitos del proyecto. Aunque ser铆a posible usar erlang-rocksdb, hubo un par de tardes libres, y despu茅s del exitoso desarrollo e implementaci贸n del filtro Bloom sobre 贸xido y curiosidad: 驴es posible implementar todos los requisitos del proyecto actual e implementar la mayor铆a de las funciones en NIF en un corto per铆odo de tiempo?
Rockero
Rocker es un NIF para Erlang, que usa el contenedor Rust para rocksdb. Las caracter铆sticas clave son la seguridad, el rendimiento y una base de c贸digo m铆nima. Las claves y los datos se almacenan en forma binaria, lo que no impone restricciones en el formato de almacenamiento. Por el momento, el proyecto es adecuado para su uso en soluciones de terceros.
El c贸digo fuente est谩 en el repositorio del proyecto .
Descripci贸n general de la API
Apertura de la base
Trabajar con la base de datos es posible en dos modos:
El espacio clave total. En este modo, todas sus llaves se colocar谩n en un conjunto. Rocksdb le permite configurar de manera flexible las opciones de almacenamiento para las tareas actuales. Dependiendo de ellos, la base de datos se puede abrir de dos maneras:
utilizando un conjunto est谩ndar de opciones
rocker:open_default(<<"/project/priv/db_default_path">>) -> {ok, Db}.
El resultado de esta operaci贸n ser谩 un puntero para trabajar con la base de datos, y la base de datos ser谩 bloqueada por cualquier otro intento de abrir. La base de datos se desbloquear谩 autom谩ticamente inmediatamente despu茅s de borrar este puntero.
- ya sea configurar opciones para la tarea
{ok, Db} = rocker:open(<<"/project/priv/db_path">>, #{ create_if_missing => true, set_max_open_files => 1000, set_use_fsync => false, set_bytes_per_sync => 8388608, optimize_for_point_lookup => 1024, set_table_cache_num_shard_bits => 6, set_max_write_buffer_number => 32, set_write_buffer_size => 536870912, set_target_file_size_base => 1073741824, set_min_write_buffer_number_to_merge => 4, set_level_zero_stop_writes_trigger => 2000, set_level_zero_slowdown_writes_trigger => 0, set_max_background_compactions => 4, set_max_background_flushes => 4, set_disable_auto_compactions => true, set_compaction_style => universal }).
- Desglose en varios espacios. Las claves se almacenan en las llamadas familias de columnas, y cada familia de columnas puede tener diferentes opciones. Consideremos un ejemplo de abrir una base de datos con opciones est谩ndar para todas las familias de columnas.
{ok, Db} = case rocker:list_cf(BookDbPath) of {ok, CfList} -> rocker:open_cf_default(BookDbPath, CfList); _Else -> CfList = [], rocker:open_default(BookDbPath) end.
Remoci贸n de base
Para eliminar correctamente la base de datos, debe llamar a rocker:destroy(Path).
En este caso, la base no debe usarse.
Recuperaci贸n de la base de datos luego de una falla
En el caso de una falla del sistema, la base de datos puede restaurarse usando el m茅todo rocker:repair(Path)
. Este proceso consta de 4 pasos:
- b煤squeda de archivos
- restaurar tablas jugando WAL
- extraer metadatos
- registro descriptor
Crear una familia de columnas
Cf = <<"testcf1">>, rocker:create_cf_default(Db, Cf) -> ok.
Retiro de familia de columna
Cf = <<"testcf1">>, rocker:drop_cf(Db, Cf) -> ok.
Operaciones CRUD
Entrada de datos clave
rocker:put(Db, <<"key">>, <<"value">>) -> ok.
Obtener datos por clave
rocker:get(Db, <<"key">>) -> {ok, <<"value">>} | notfound
Eliminaci贸n de datos clave
rocker:delete(Db, <<"key">>) -> ok.
Entrada de datos clave en CF
rocker:put_cf(Db, <<"testcf">>, <<"key">>, <<"value">>) -> ok.
Recuperaci贸n de datos clave dentro de la FQ
rocker:get_cf(Db, <<"testcf">>, <<"key">>) -> {ok, <<"value">>} | notfound
Eliminaci贸n de llave CF
rocker:delete_cf(Db, <<"testcf">>, <<"key">>) -> ok
Iteradores
Como saben, uno de los principios b谩sicos de rocksdb es el almacenamiento ordenado de claves. Esta caracter铆stica es muy 煤til en tareas reales. Para usarlo, necesitamos iteradores de datos. Rocksdb tiene varios modos para pasar datos (se pueden encontrar ejemplos de c贸digo detallados en las pruebas ):
- Desde el comienzo de la mesa. El rockero es responsable de esto en el iterador
{'start'}
- Desde el final de la tabla:
{'end'}
- Comenzando desde una clave espec铆fica hacia adelante
{'from', Key, forward}
- Comenzando desde una tecla espec铆fica hacia atr谩s
{'from', Key, reverse}
Vale la pena se帽alar que estos modos tambi茅n funcionan para pasar por los datos almacenados en las familias de columnas.
Crea un iterador
rocker:iterator(Db, {'start'}) -> {ok, Iter}.
Comprobaci贸n de iterador
rocker:iterator_valid(Iter) -> {ok, true} | {ok, false}.
Crear un iterador para la FQ
rocker:iterator_cf(Db, Cf, {'start'}) -> {ok, Iter}.
Crear un iterador de prefijo
El iterador de prefijo requiere especificar expl铆citamente la longitud del prefijo al crear la base de datos.
{ok, Db} = rocker:open(Path, #{ prefix_length => 3 }).
Un ejemplo de creaci贸n de un iterador utilizando el prefijo "aaa":
{ok, Iter} = rocker:prefix_iterator(Db, <<"aaa">>).
Crear un iterador de prefijo para CF
Similar al iterador de prefijo anterior, requiere una prefix_length
de prefix_length
expl铆cita para la familia de columnas
{ok, Iter} = rocker:prefix_iterator_cf(Db, Cf, <<"aaa">>).
Obt茅n el siguiente art铆culo
El m茅todo devuelve la siguiente clave / valor, o bien, si el iterador se completa.
rocker:next(Iter) -> {ok, <<"key">>, <<"value">>} | ok
Transacciones
Una ocurrencia bastante com煤n es el requisito de registrar simult谩neamente los cambios en un grupo clave. Rocker le permite combinar operaciones CRUD tanto en un conjunto com煤n como en CF.
Este ejemplo ilustra el trabajo con transacciones:
{ok, 6} = rocker:tx(Db, [ {put, <<"k1">>, <<"v1">>}, {put, <<"k2">>, <<"v2">>}, {delete, <<"k0">>, <<"v0">>}, {put_cf, Cf, <<"k1">>, <<"v1">>}, {put_cf, Cf, <<"k2">>, <<"v2">>}, {delete_cf, Cf, <<"k0">>, <<"v0">>} ]).
Rendimiento
Puede encontrar una prueba de rendimiento en el conjunto de pruebas. Muestra alrededor de 30k RPS para escribir y 200k RPS para leer en mi m谩quina. En condiciones reales, puede esperar 15-20k RPS para escritura y aproximadamente 120k RPS para lectura con un tama帽o de datos promedio de aproximadamente 1 Kb por clave y el n煤mero total de claves es m谩s de mil millones.
Conclusi贸n
El desarrollo y la aplicaci贸n de Rocker en nuestro proyecto nos permiti贸 reducir el tiempo de respuesta del sistema, aumentar la confiabilidad y reducir el tiempo de reinicio. Estas ventajas se obtuvieron con costos m铆nimos de desarrollo e implementaci贸n.
Por mi parte, conclu铆 que para los proyectos de Erlang que requieren optimizaci贸n, el uso de Rust es 贸ptimo. Erlang logra implementar r谩pida y eficientemente el 95% del c贸digo, mientras que Rust reescribe / agrega un inhibidor del 5% sin comprometer la confiabilidad general del sistema.
PD: Existe una experiencia positiva en el desarrollo de NIF para la aritm茅tica de precisi贸n arbitraria en Erlang, que se puede escribir en un art铆culo separado. Me gustar铆a aclarar si el tema de NIF sobre Rust es interesante para la comunidad.