Я розробляю сервер баз даних, схожий на Cassandra.
Розробка була розпочата в С, але все стало дуже складним без занять.
Наразі я все портував на C ++ 11, але я все ще навчаюсь "сучасного" C ++ і маю сумніви щодо багатьох речей.
База даних буде працювати з парами ключ / значення. Кожна пара має додаткову інформацію - коли створюється також, коли вона закінчується (0, якщо не закінчується). Кожна пара незмінна.
Ключ - це рядок C, значення - недійсне *, але принаймні на даний момент я оперую зі значенням також як рядок C.
Є абстрактний IListклас. Він успадковується від трьох класів
VectorList- C динамічний масив - схожий на std :: vector, але використовуєreallocLinkList- зроблено для перевірок та порівняння ефективностіSkipList- клас, який нарешті буде використаний.
У майбутньому я можу зробити і Red Blackдерево.
Кожен IListмістить нуль або більше вказівників на пари, відсортовані за клавішами.
Якщо він IListстав занадто довгим, його можна зберегти на диску в спеціальному файлі. Цей спеціальний файл є своєрідним read only list.
Якщо вам потрібно знайти ключ,
- спочатку в пам'яті
IListшукається (SkipList,SkipListабоLinkList). - Потім пошук надсилається до файлів, відсортованих за датою
(найновіший файл перший, найстаріший файл - останній).
Усі ці файли зберігаються в пам'яті. - Якщо нічого не знайдено, то ключ не знайдено.
У мене немає сумнівів щодо реалізації IListречей.
Що зараз мене спантеличує:
Пари мають різну величину, вони виділяються по new()і std::shared_ptrвказують на них.
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];
};
Змінна члена "буфер" - це одна з різними розмірами. Тут зберігається ключ + значення.
Наприклад, якщо ключ становить 10 символів, а значення - ще 10 байт, весь об'єкт буде sizeof(Pair::Blob) + 20(буфер має початковий розмір 2, через два нульові закінчувальні байти)
Цей же макет використовується і на диску, тому я можу зробити щось подібне:
// 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);
Однак цей різний розмір є проблемою для багатьох місць з кодом C ++.
Наприклад, я не можу використовувати std::make_shared(). Це важливо для мене, тому що якби я мав пари 1М, я мав би виділення 2М.
З іншого боку, якщо я виконаю "буфер" для динамічного масиву (наприклад, новий char [123]), я втрачу mmap "трюк", я зроблю два відхилення, якщо хочу перевірити ключ, і я додаю єдиний покажчик - 8 байт до класу.
Я також намагався "витягнути" всіх членів Pair::Blobзсередини Pair, Pair::Blobщоб бути лише буфером, але коли я тестував це, він був досить повільним, ймовірно, через копіювання даних об'єкта навколо.
Ще одна зміна, про яку я також думаю, - це видалити Pairклас і замінити його std::shared_ptrта "підштовхнути" всі методи назад Pair::Blob, але це не допоможе мені з Pair::Blobкласом змінних розмірів .
Мені цікаво, як я можу вдосконалити дизайн об'єкта, щоб бути більш сприятливим C ++.
Повний вихідний код тут:
https://github.com/nmmmnu/HM3
IList::removeабо коли знищено IList. Це займає багато часу, але я збираюся робити це окремо. Це буде легко, тому що IList все std::unique_ptr<IList>одно буде . тож я зможу "переключити" його з новим списком і зберегти старий об'єкт десь, де я можу викликати d-tor.
C stringа дані - це завжди якийсь буфер void *або char *, тож ви можете передавати масив char. Ви можете знайти подібні в redisабо memcached. У якийсь момент я міг вирішити використовувати std::stringабо виправлений масив char для ключа, але підкреслити це буде все-таки рядок C.
std::mapабоstd::unordered_map? Чому значення (пов'язані з ключами) деякіvoid*? Вам, певно, потрібно було б знищити їх у якийсь момент; як і коли? Чому ви не використовуєте шаблони?