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 IListclases abstractas . Se hereda de tres clases.
VectorList- C matriz dinámica - similar a std :: vector, pero usareallocLinkList- 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 IListcontiene cero o más punteros a pares, ordenados por clave.
Si se IListhizo 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
IListse busca (SkipList,SkipListoLinkList). - 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 IListcosas.
Lo que me desconcierta actualmente es lo siguiente:
Los pares son de diferente tamaño, se asignan por new()y los han std::shared_ptrseñ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::Blobdentro Pair, de modo Pair::Blobque 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 Pairclase y reemplazarla std::shared_ptrpor "empujar" todos los métodos Pair::Blob, pero esto no me ayudará con la Pair::Blobclase 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::removeo 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 stringy los datos siempre son algún búfer void *o char *, por lo que puede pasar la matriz de caracteres. Puedes encontrar similar en rediso memcached. En algún momento, podría decidir usar std::stringuna matriz de caracteres fija o fija para la clave, pero subrayar que seguirá siendo una cadena C.
std::mapostd::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?