Estoy desarrollando un servidor de base de datos similar a Cassandra.
El desarrollo comenzó en C, pero las cosas se volvieron muy complicadas sin clases.
Actualmente porté todo en C ++ 11, pero todavía estoy aprendiendo C ++ "moderno" y tengo dudas sobre muchas cosas.
La base de datos funcionará con pares clave / valor. Cada par tiene más información: cuándo se crea y cuándo caducará (0 si no caduca). Cada par es inmutable.
La clave es una cadena C, el valor es nulo *, pero al menos por el momento también estoy operando con el valor como cadena C.
Hay IList
clases abstractas . Se hereda de tres clases.
VectorList
- C matriz dinámica - similar a std :: vector, pero usarealloc
LinkList
- hecho para controles y comparación de rendimientoSkipList
- la clase que finalmente se usará.
En el futuro también podría hacer Red Black
árbol.
Cada uno IList
contiene cero o más punteros a pares, ordenados por clave.
Si se IList
hizo demasiado largo, se puede guardar en el disco en un archivo especial. Este archivo especial es algo así read only list
.
Si necesita buscar una clave,
- primero en la memoria
IList
se busca (SkipList
,SkipList
oLinkList
). - Luego, la búsqueda se envía a los archivos ordenados por fecha
(el archivo más nuevo primero, el archivo más antiguo - el último).
Todos estos archivos están mmap-ed en la memoria. - Si no se encuentra nada, entonces no se encuentra la clave.
No tengo dudas sobre la implementación de las IList
cosas.
Lo que me desconcierta actualmente es lo siguiente:
Los pares son de diferente tamaño, se asignan por new()
y los han std::shared_ptr
señalado.
class Pair{
public:
// several methods...
private:
struct Blob;
std::shared_ptr<const Blob> _blob;
};
struct Pair::Blob{
uint64_t created;
uint32_t expires;
uint32_t vallen;
uint16_t keylen;
uint8_t checksum;
char buffer[2];
};
La variable miembro "buffer" es la que tiene un tamaño diferente. Almacena la clave + valor.
Por ejemplo, si la clave es de 10 caracteres y el valor es de otros 10 bytes, el objeto completo será sizeof(Pair::Blob) + 20
(el búfer tiene un tamaño inicial de 2, debido a dos bytes de terminación nulos)
Este mismo diseño también se usa en el disco, así que puedo hacer algo como esto:
// get the blob
Pair::Blob *blob = (Pair::Blob *) & mmaped_array[pos];
// create the pair, true makes std::shared_ptr not to delete the memory,
// since it does not own it.
Pair p = Pair(blob, true);
// however if I want the Pair to own the memory,
// I can copy it, but this is slower operation.
Pair p2 = Pair(blob);
Sin embargo, este tamaño diferente es un problema en muchos lugares con código C ++.
Por ejemplo no puedo usar std::make_shared()
. Esto es importante para mí, porque si tengo 1M de pares, tendría asignaciones de 2M.
Por otro lado, si hago "buffer" a matriz dinámica (por ejemplo, nuevo char [123]), perderé "truco" de mmap, tendré que hacer dos desreferencias si quiero verificar la clave y agregaré un puntero único - 8 bytes a la clase.
También probé a "tirar" a todos los miembros de Pair::Blob
dentro Pair
, de modo Pair::Blob
que sólo la memoria intermedia, pero cuando lo probé, fue bastante lenta, probablemente debido a la copia de los datos de objetos alrededor.
Otro cambio en el que también estoy pensando es eliminar la Pair
clase y reemplazarla std::shared_ptr
por "empujar" todos los métodos Pair::Blob
, pero esto no me ayudará con la Pair::Blob
clase de tamaño variable .
Me pregunto cómo puedo mejorar el diseño del objeto para ser más amigable con C ++.
El código fuente completo está aquí:
https://github.com/nmmmnu/HM3
IList::remove
o cuando se destruye IList. Lleva mucho tiempo, pero lo voy a hacer en hilo separado. Será fácil porque IList lo será de std::unique_ptr<IList>
todos modos. así que podré "cambiarlo" con una nueva lista y guardar el objeto antiguo en algún lugar donde pueda llamar a d-tor.
C string
y los datos siempre son algún búfer void *
o char *
, por lo que puede pasar la matriz de caracteres. Puedes encontrar similar en redis
o memcached
. En algún momento, podría decidir usar std::string
una matriz de caracteres fija o fija para la clave, pero subrayar que seguirá siendo una cadena C.
std::map
ostd::unordered_map
? ¿Por qué algunos valores (asociados a claves) son algunosvoid*
? Probablemente necesite destruirlos en algún momento; ¿como cuando? ¿Por qué no usas plantillas?