Я видел много встраиваемых key — value (NoSql db) для Go. Но все что я находил хранили всё в озу и каждые n секунд отправляли данные на диск, а я же хотел гарантированное сохранение на диск и по сути из всех NoSql решений это могла делать mongoDB но а я же искал встраиваемое хранилище. И так накодил вот это github.com/v-grabko/Fly. Думаю что кому то пригодится. В репозитории есть бенчмарки
Система хранения данных
файловая система
basic dir -> db name -> key name
Как видите каждый ключ это просто файл.
Как нам известно файловые системы плохо работают когда в 1 директории куча елементов (директорий, файлов) и по этому я решил сделать птимизацию хранилища.
Теперь делается md5 каждого ключа и полученная строка разбивается посимвольно. И потом я просто для каждого символа создаю директорию.
basic dir -> db name -> 1 -> ... -> 31 -> key name
В ходе оптимизации этого процесса появился кеш распаренных ключей и кеш наличия директории. Вы спросите зачем сделано два кеша если достаточно проверить директорию? Это сделано сделано для случая если в разных бд одинаковый ключ то повторно он не парсится. (размер кеша ключей можно настроить. Минимальный размер 32 символа (на 1 ключ))
По факту у нас поключевая блокировка (ведь каждый ключ это отдельный файл)
Получение данных
изначально чтение каждый раз производилось с файла но позже я сделал репликацию данных в ram. При получении проверяется наличие данных в кеше и если их там нет то читает с диска. При Set/Del данные обновляются в кеше и файловой системе. Set кстати выступает как и Update.
Сжатие данных
Потом я решил что нужно данные сжимать для экономии пространства и прикрутил для всех данных на диске gzip компресию 9 уровня (изменить это нельзя. Но если хотите присылайте пулл реквест)
Поддерживаемые типы
- float
- string
- struct
package main
import (
"fmt"
db "github.com/v-grabko/Fly"
)
type tt struct {
G string
T int
}
func main() {
db.ConfigSet(&db.Config{
Dir: "./db",
CacheParseKey: 1,
FileRecurse:5,
})
DB := db.Open("test")
//string
DB.Set("testString", "test")
st, _ := DB.Get("testString")
DB.Del("testString")
fmt.Println(st.(string))
//float
DB.Set("testFloat", 99)
fl, _ := DB.Get("testFloat")
DB.Del("testFloat")
fmt.Println(fl.(float64))
//struct
DB.Set("testStruct", tt{
G:"dfdf",
T:54,
})
var p tt
DB.GetStruct("testStruct", &p)
fmt.Println(p)
}
А вот так мы можем проверить существование ключа
value, err = DB.Get("dfsdf")
if err == nil{
//ключ есть
} else if err.Error() == db.KeyNotExist {
//ключа нет
} else {
//ошибка
}
Комментарии (26)
EagleMoor
01.07.2016 22:17Вы в начале сравниваете с mongodb, а можно результаты бенчмарков и сравнений с той же mongodb глянуть?
VGrabko
01.07.2016 22:21-11нельзя :) У меня сейчас очень слабенький нет бук и толку от тестов на нём 0
lega
02.07.2016 00:39+8Судя по тому что идет насилование ФС, оно должно быть гораздо медленнее чем монга, а с учетом того что не пустой файл (и папка) занимают минимум один кластер, данная БД будет расходовать место на диске как все себя.
meln1k
01.07.2016 22:20+4Не очень понятен смысл содержания кеша в RAM. ОС скорее всего сама закеширует те данные что находятся на диске, и в результате у вас будет две копии данных в оперативной памяти.
RigelNM
10.11.2016 17:17Но ведь это будет равносильно убийству своего дедушки в прошлом. Вот он и парадокс.
umputun
01.07.2016 22:39+4насколько я понимаю, "… все что я находил хранили всё в озу и каждые n секунд отправляли данные на диск" не включает первый по популярности среди подобных проектов https://github.com/boltdb/bolt который вовсе не делает такое, но по каждой транзакции делает fsync.
VGrabko
01.07.2016 22:55-3он не сжимает данные (вроде бы) и весь обмазан колбеками (ну и не может автоматом результат запихнуть в структуру)
umputun
01.07.2016 23:25+12какими колбеками ин «обмазан», где? это ты так называешь передачу функций внутрь транзакций? Оно не имеет никакого отношения к колбекам. У болта очень лаконичный и мощный API, позволяющий делать сложные вещи просто. Я даже не буду спрашивать, как в твоем хранилище сделать выборку по префиксу ключа, как сделать вложенные бакеты, как сделать запросы по интервалам и прочее. В болте это все есть и оно работает.
По поводу «не сжимает данные» и «не может автоматом результат запихнуть в структуру» — это хорошо, что он этого не делает, т.к. это делать имеет мало смысла и даже вредно. Это не документное хранилище, но kv, и естественно что в него кладут value. От того, что ты к этим value решил делать json.Marshal/Unmarshal оно не стало чем-то большим, чем kv, просто ты решил что это правильный путь. Автор болта совершенно разумно решил, что это не его уровня сущность, и если пользователь хочет сериализацию в json или protobuf или еще во что — это его, пользователя область ответственности.
Что касается сжатия данных, то на фоне того, что у тебя это могут быть миллионы мелких файлов ключи к которым ты хранишь в памяти, то она не вызывает у меня большого оптимизма. В болте, если бы мне понадобилось сохранить место на диске, я бы просто держал это на хранилище, что поддерживает компрессию/декомпрессию на лету.
Я не против того, чтоб люди писали свои бодрые велосипеды, но для тех кто вдруг захочет этим воспользоваться — утверждение о том, что «из всех NoSql решений это могла делать mongoDB» неверно совсем, и мотивация использовать этот продукт должна быть как минимум подвергнута сомнению. А автору стоит посмотреть что именно и как делает bolt – это будет полезно.
yroman
01.07.2016 23:28+5>>с гарантированным сохранением данных
Уточните, пожалуйста, насколько гарантированным. Судя по коду, запись не гарантируется в случае крэша или убийства программы.
mantyr
02.07.2016 00:16+4Список того что хочется отметить:
библиотека вызывает panic() если произошла ошибка в компрессии/декомпрессии
это json-based база данных, если её вообще базой данных можно назвать
логи в приложении — хорошо, логи в библиотеке — плохо
github.com/v-grabko/Fly/blob/master/compres.go#L13
github.com/v-grabko/Fly/blob/master/compres.go#L26
github.com/v-grabko/Fly/blob/master/api.go#L82
func (dr *Db) Set(keyS, v interface{}) error {
…
kk, err := json.Marshal(v)
github.com/v-grabko/Fly/blob/master/api.go#L88
Выходит вы никак не защищаете файл от конкурентного доступа на чтение/запись?
mikkab
02.07.2016 12:14riak где то так и работает, только еще куча плюшек типа автоматического распределения данных по серверам уже реализовано. работает не сказать что очень быстро, и места занимает порядочно. а в целом смотрите leveldb.
Lol4t0
03.07.2016 00:43Riak — это решение другого уровня, которое предоставляет большую абстракцию. Решением схожего уровня является backend Riak-а, один из которых все тот же leveldb
chEbba
02.07.2016 13:19+1Про leveldb уже написали — наверное, самое популярное решение. Как вариант ещё WiredTiger, движок, который сейчас внутри монги используется. Эти решения дадут на порядки больше скорости и надежности.
Stalker_RED
11.11.2016 00:06Раз уж он из будущего будет идти, пусть захватит с собой копию чертежей машины времени и биржевую сводку.
vadimr
02.07.2016 19:29+2Слова «гарантированное сохранение данных» обычно всё же подразумевают нечто большее, чем просто их запись в файловую систему на диске.
Kolonist
А Go разве не умеет работать с leveldb?
VGrabko
Умеет https://github.com/syndtr/goleveldb
Насколько я понял там нету гарантированной записи на диск.
Но он не умеет сохранять структуры в автоматическом режиме. И api ужасное
Lol4t0
Leveldb как раз все умеет, а вы вызываете fsync?
A API обычное, вы можете всегда надстройку сделать