Tworzę serwer bazy danych podobny do Cassandry.
Prace rozwojowe rozpoczęto w C, ale bez zajęć zajęcia stały się bardzo skomplikowane.
Obecnie przenosiłem wszystko w C ++ 11, ale wciąż uczę się „nowoczesnego” C ++ i mam wątpliwości co do wielu rzeczy.
Baza danych będzie działać z parami klucz / wartość. Każda para ma więcej informacji - kiedy jest tworzona, także kiedy wygaśnie (0, jeśli nie wygasa). Każda para jest niezmienna.
Kluczem jest ciąg C, wartość jest nieważna *, ale przynajmniej w tej chwili działam również z wartością jako ciąg C.
Istnieją IList
klasy abstrakcyjne . Jest dziedziczony z trzech klas
VectorList
- C dynamiczna tablica - podobna do std :: vector, ale wykorzystujerealloc
LinkList
- stworzony do kontroli i porównania wydajnościSkipList
- klasa, która w końcu zostanie użyta.
W przyszłości mógłbym również zrobić Red Black
drzewo.
Każdy IList
zawiera zero lub więcej wskaźników do par, posortowanych według klucza.
Jeśli IList
stał się zbyt długi, można go zapisać na dysku w specjalnym pliku. Ten specjalny plik jest swego rodzaju read only list
.
Jeśli musisz wyszukać klucz,
- Pierwsza pamięć
IList
jest przeszukiwana (SkipList
,SkipList
iLinkList
). - Następnie wyszukiwanie jest wysyłane do plików posortowanych według daty
(najnowszy plik pierwszy, najstarszy plik - ostatni).
Wszystkie te pliki są mmap-edowane w pamięci. - Jeśli nic nie znaleziono, klucz nie zostanie znaleziony.
Nie mam wątpliwości co do realizacji IList
rzeczy.
To, co mnie obecnie zastanawia, to:
Pary są różnej wielkości, są przydzielane przez new()
i std::shared_ptr
wskazały na nie.
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];
};
Zmienna składowa „buforowa” jest zmienna o innym rozmiarze. Przechowuje klucz + wartość.
Np. Jeśli klucz ma 10 znaków, a wartość to kolejne 10 bajtów, cały obiekt będzie sizeof(Pair::Blob) + 20
(bufor ma początkowy rozmiar 2, z powodu dwóch bajtów kończących wartość null)
Ten sam układ jest również używany na dysku, więc mogę zrobić coś takiego:
// 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);
Jednak ten inny rozmiar jest problemem w wielu miejscach z kodem C ++.
Na przykład nie mogę użyć std::make_shared()
. Jest to dla mnie ważne, ponieważ gdybym miał 1M pary, miałbym przydziały 2M.
Z drugiej strony, jeśli zrobię „buforowanie” dynamicznej tablicy (np. Nowy znak [123]), stracę „lewę” mmapa, zrobię dwie dereferencje, jeśli chcę sprawdzić klucz i dodam pojedynczy wskaźnik - 8 bajtów do klasy.
Próbowałem też do „pull” wszystkich członków z Pair::Blob
pod Pair
, więc Pair::Blob
będzie tylko bufor, ale gdy testowałem go, to był dość powolna, prawdopodobnie z powodu kopiowania danych obiektów wokół.
Inną zmianą, o której myślę, jest również usunięcie Pair
klasy i zastąpienie jej std::shared_ptr
oraz „wypchnięcie” wszystkich metod z powrotem Pair::Blob
, ale to nie pomoże mi w przypadku Pair::Blob
klasy o zmiennej wielkości .
Zastanawiam się, jak mogę ulepszyć projektowanie obiektów, aby być bardziej przyjaznym dla C ++.
Pełny kod źródłowy znajduje się tutaj:
https://github.com/nmmmnu/HM3
IList::remove
lub gdy IList jest zniszczony. To zajmuje dużo czasu, ale zrobię to w osobnym wątku. Będzie to łatwe, ponieważ IList i std::unique_ptr<IList>
tak będzie . więc będę mógł „przełączyć” go z nową listą i zatrzymać stary obiekt w miejscu, w którym będę mógł wywołać d-tor.
C string
a dane to zawsze bufor void *
lub char *
, więc można przekazać tablicę znaków. Możesz znaleźć podobne w redis
lub memcached
. W pewnym momencie mogłem zdecydować się na użycie std::string
lub naprawienie tablicy znaków dla klucza, ale podkreślę, że nadal będzie to ciąg C.
std::map
lubstd::unordered_map
? Dlaczego wartości (powiązane z kluczami) są niektórevoid*
? Prawdopodobnie w pewnym momencie będziesz musiał je zniszczyć; jak kiedy? Dlaczego nie używasz szablonów?