Я видел много встраиваемых 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)


  1. Kolonist
    01.07.2016 22:09
    +3

    А Go разве не умеет работать с leveldb?


    1. VGrabko
      01.07.2016 22:11

      Умеет https://github.com/syndtr/goleveldb
      Насколько я понял там нету гарантированной записи на диск.

      Но он не умеет сохранять структуры в автоматическом режиме. И api ужасное

      batch.Put([]byte("foo"), []byte("value"))
      batch.Put([]byte("bar"), []byte("another value"))
      


      1. Lol4t0
        01.07.2016 23:08
        +8

        Leveldb как раз все умеет, а вы вызываете fsync?


        A API обычное, вы можете всегда надстройку сделать


  1. EagleMoor
    01.07.2016 22:17

    Вы в начале сравниваете с mongodb, а можно результаты бенчмарков и сравнений с той же mongodb глянуть?


    1. VGrabko
      01.07.2016 22:21
      -11

      нельзя :) У меня сейчас очень слабенький нет бук и толку от тестов на нём 0


    1. lega
      02.07.2016 00:39
      +8

      Судя по тому что идет насилование ФС, оно должно быть гораздо медленнее чем монга, а с учетом того что не пустой файл (и папка) занимают минимум один кластер, данная БД будет расходовать место на диске как все себя.


  1. meln1k
    01.07.2016 22:20
    +4

    Не очень понятен смысл содержания кеша в RAM. ОС скорее всего сама закеширует те данные что находятся на диске, и в результате у вас будет две копии данных в оперативной памяти.


    1. VGrabko
      01.07.2016 22:22

      Нет. Обращение к файлу только на запись и 1 раз на чтение.


    1. RigelNM
      10.11.2016 17:17

      Но ведь это будет равносильно убийству своего дедушки в прошлом. Вот он и парадокс.


  1. umputun
    01.07.2016 22:39
    +4

    насколько я понимаю, "… все что я находил хранили всё в озу и каждые n секунд отправляли данные на диск" не включает первый по популярности среди подобных проектов https://github.com/boltdb/bolt который вовсе не делает такое, но по каждой транзакции делает fsync.


    1. VGrabko
      01.07.2016 22:55
      -3

      он не сжимает данные (вроде бы) и весь обмазан колбеками (ну и не может автоматом результат запихнуть в структуру)


      1. umputun
        01.07.2016 23:25
        +12

        какими колбеками ин «обмазан», где? это ты так называешь передачу функций внутрь транзакций? Оно не имеет никакого отношения к колбекам. У болта очень лаконичный и мощный API, позволяющий делать сложные вещи просто. Я даже не буду спрашивать, как в твоем хранилище сделать выборку по префиксу ключа, как сделать вложенные бакеты, как сделать запросы по интервалам и прочее. В болте это все есть и оно работает.

        По поводу «не сжимает данные» и «не может автоматом результат запихнуть в структуру» — это хорошо, что он этого не делает, т.к. это делать имеет мало смысла и даже вредно. Это не документное хранилище, но kv, и естественно что в него кладут value. От того, что ты к этим value решил делать json.Marshal/Unmarshal оно не стало чем-то большим, чем kv, просто ты решил что это правильный путь. Автор болта совершенно разумно решил, что это не его уровня сущность, и если пользователь хочет сериализацию в json или protobuf или еще во что — это его, пользователя область ответственности.

        Что касается сжатия данных, то на фоне того, что у тебя это могут быть миллионы мелких файлов ключи к которым ты хранишь в памяти, то она не вызывает у меня большого оптимизма. В болте, если бы мне понадобилось сохранить место на диске, я бы просто держал это на хранилище, что поддерживает компрессию/декомпрессию на лету.

        Я не против того, чтоб люди писали свои бодрые велосипеды, но для тех кто вдруг захочет этим воспользоваться — утверждение о том, что «из всех NoSql решений это могла делать mongoDB» неверно совсем, и мотивация использовать этот продукт должна быть как минимум подвергнута сомнению. А автору стоит посмотреть что именно и как делает bolt – это будет полезно.


  1. yroman
    01.07.2016 23:28
    +5

    >>с гарантированным сохранением данных
    Уточните, пожалуйста, насколько гарантированным. Судя по коду, запись не гарантируется в случае крэша или убийства программы.


    1. umputun
      01.07.2016 23:46
      +8

      судя по коду, запись и чтение в один файл вообще не синхронизировано никак, видимо в надежде, что такое не произойдет.


      1. VGrabko
        02.07.2016 00:22
        -2

        исправлю синхронизацию.


    1. VGrabko
      02.07.2016 00:18
      -2

      исправлю обработку «убийства» с крашем сложнее


      1. RigelNM
        10.11.2016 18:17

        Это понятно, но разве тебе хочется жить в мире где прошлое непоправимо? :)


  1. 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
    Выходит вы никак не защищаете файл от конкурентного доступа на чтение/запись?


    1. VGrabko
      02.07.2016 00:16
      -5

      исправлю на днях.


  1. youROCK
    02.07.2016 07:34

    Говорят, что git так хранил объекты до того, как в нем появились pack-файлы.


  1. mikkab
    02.07.2016 12:14

    riak где то так и работает, только еще куча плюшек типа автоматического распределения данных по серверам уже реализовано. работает не сказать что очень быстро, и места занимает порядочно. а в целом смотрите leveldb.


    1. Lol4t0
      03.07.2016 00:43

      Riak — это решение другого уровня, которое предоставляет большую абстракцию. Решением схожего уровня является backend Riak-а, один из которых все тот же leveldb


  1. chEbba
    02.07.2016 13:19
    +1

    Про leveldb уже написали — наверное, самое популярное решение. Как вариант ещё WiredTiger, движок, который сейчас внутри монги используется. Эти решения дадут на порядки больше скорости и надежности.


    1. Stalker_RED
      11.11.2016 00:06

      Раз уж он из будущего будет идти, пусть захватит с собой копию чертежей машины времени и биржевую сводку.


  1. vadimr
    02.07.2016 19:29
    +2

    Слова «гарантированное сохранение данных» обычно всё же подразумевают нечто большее, чем просто их запись в файловую систему на диске.


  1. roller
    07.07.2016 12:38

    Еще один Tarantool изобрели что-ли?