Pull to refresh

Comments 39

Смайлики на Хабре запрещены, сами знаете.

Это всё неправда. Я постоянно их использую, и НЛО меня только один раз отправило на специальную страницу, где надо было проклинать где-то 20 или 30 чекбоксов с лейблами "Я больше не буду злоупотреблять смайликами", причём активировались они по очереди, с интервалом около 10 или 30 секунд после прощёлкивания предыдущего.
В общем, интересная штука, рекомендую попробовать!

Я уже натыкался, спасибо, кликал потом. С тех пор стараюсь не использовать. Потому что есть при создании аккаунта каждый хабраюзер подписывается следовать правилам сайта, а в них про смайлы и запрет на них написано. Увы. НЛО работает на поиск смайлов только по большим праздникам (или не знаю еще по какому расписанию), но большой радости еще раз кликать по чекбоксам не вижу.
А если правилам (на которые сам же и подписался), не следовать, то ресурс вразнос пойдет, как ни крути.
А она потокобезопасна? т.е. если из разных потоков одновременно писать в одну таблицу (в один файл) и читать с таблицы — данные не будут корректно обработаны?
Да, потокобезопасна, конечно. В тестах есть множество примеров
А что насчет процессобезопасности?

Оно-то ясно, что несколько процессов не могут читать из одной базой одновременно (ибо все ключи хранятся в памяти, а вычитываются только при открытии файла), но локи/защита от попыток одновременной записи есть?
До тех пор, пока Вы работаете с базой через интерфейс slowpoke — это безопасно. Но нельзя менять что то в файле напрямую, или к примеру запустить другую слоупоке, в другом процессе и натравить на тот же файл. Если я правильно понял задачу — Вам нужен сервер над slowpoke. Например, github.com/recoilme/okdb
Сервер будет обеспечивать доступ к базе из различных процессов, код будет чуть сложнее:
	var conn *grpc.ClientConn
	var err error
	var f = "db/1.db"

	conn, err = grpc.Dial(":7777", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %s", err)
	}
	defer conn.Close()

	c := api.NewOkdbClient(conn)

	// Set
	_, err = c.Set(context.Background(), &api.CmdSet{File: f, Key: []byte("1"), Val: []byte("1")})

	// Get
	b, err := c.Get(context.Background(), &api.CmdGet{File: f, Key: []byte("1")})
}
Чет дважды перечитал, не могу найти, так где же на диске ключи хранятся-то?
Для каждой таблицы значений, создается файл с постфиксом .idx, в который персистятся ключи.
Ну, slowpoke делает sync (fsync) на каждую операцию записи, не отложенный, по таймеру как сейчас стало модно, и не перед закрытием, например, а честный sync на каждый set/sets. Это самый дорогой и надежный способ синхронизации. Но конечно и он не дает 100% гарантии: danluu.com/file-consistency
>а честный sync на каждый set/sets. Это самый дорогой и надежный способ синхронизации.
Это ужасно.
Аналогичный подход исповедует команда проекта Sia, так одно время их продукт приходилось пускать с tmpfs, потому что даже на SSD было медленно, а на шпинделе вообще часы! И это на БД меньше гигабайта! Естественно костыли уровня cp db /tmp; dosmth /tmp/db ; cp /tmp/db ./ надёжности не добавляли.
Наверно эта Sia — sia.tech
Там вроде boltdb внутри — github.com/NebulousLabs/Sia/blob/master/persist/boltdb.go
У болта есть режим DB.NoSync, он, естественно опасный — github.com/boltdb/bolt/issues/612
Бэкапы, реплики.

В slowpoke нет NoSync, но есть пакетная запись, sets — с одним sync на пакет, он быстрее:
//macbook 2017 slowpoke vs boltdb
//The 100 Set took 19.440075ms to run./19.272079ms
//The 100 Sets took 1.139579ms to run./?

Это тесты на ssd — sync занимает около 1 ms. Это оч медленно (примерно 99% времени от set уходит на sync) На «шпинделе» — еще медленней раз в 100 или даже 1000. Секунда запросто уйдет — и с размером базы это не связано никак.
Вобщем можно юзать sets для быстрой вставки, но есть риск потерять весь пакет при креше.
но ведь не на ключи, а на данные? или ключи тоже кидаются на диск после каждого изменения?
Стоп. Так как база значений не содержит ни длин ни чего другого, изменение любого значения требует пересброса и ключей тоже?
то есть 1 маленькая запись для данных + 1 большая для всех ключей одним махом?
Перебрасывается только обновленный ключ (переазписывается) + в конец файла с индексами аппендится запись со спец маркером для удаления (отчего уж не сделать также поверху не ясно). Как я понимаю реального удаления ключеей никогда не происходит (судя по коду compact нету). Все записи в файлы считаются атомарными всегда и без вариантов (ну а че, отчего бы и нет).
UFO just landed and posted this here
Я не стал выливать эту боль в посте. Проблема состоит в том, что недостатки раскрываются с ростом базы данных, в процессе эксплуатации. Остается либо лупоглазить в код либо читать описания/отзывы и прикидывать во что это выльется. Мне же не нужна была скорость, мне нужен был движок который будет предсказуемо и стабильно вести себя по мере роста. А сейчас так не пишут. Всем нужна скорость, возникли проблемы? Так купите SSD, добавьте память.

Коротко, на примере boltdb, через примерно полгода эксплуатации:
— нагрузка на винт под 100% (перестало работать всё остальное)
— деградация производительности в десятки — сотни раз
— необходимость покупки дорогостоящего SSD
— необходимость поднятия кеширующего слоя с redis над болтом
При этом boltdb — не самая плохая база данных, пока целиком помещалась в память — всё было прекрасно.

До этого обжигался на хранении бинарных данных (именно бинарных) в РСУБД + был не самый удачный опыт эксплуатации Касандры.

С другой стороны есть позитивный опыт эксплуатации sophia в хайлоаде, но она на C и замечания тоже есть. С leveldb сталкивался больше как пользователь, субъективно — мне показалась что она вобрала в себя все возможные недостатки) Проверять не стал. Архитектурно понравился badger, но оттолкнула модель хранения данных (Lsm Tree) и некоторые посты от их команды разработки. Банально не хотелось проверять на себе. Ну и задача разработки простенького движка — казалось проще чем оказалось)
Скажие, а на каких объемах данных сейчас используется slowpoke.
К моему стыду внедрена в продакшен она относительно недавно и опыта эксплуатации на миллиардах записей пока банально нет. Максимальный размер базы — несколько гигабайт. Надеюсь в ближайшее время перевести на нее более крупный проект, для которого первоначально и писал, но там нужна не встроенная база, а сразу grpc-сервер, который пока не закончен(
Максимальный размер базы — несколько гигабайт.
Это, как я понимаю, суммарный размер Values.
Меня больше интересует размер и количество Keys.
Как показывает практика — чем более сложна архитектура базы данных, тем с бОльшим количеством проблем предстоит столкнуться с ростом базы. Так как ничего на бывает бесплатно.
Как показывает практика, чем проще архитектура, чем с худшей производительностью предстоит столкнуться с ростом базы… Например, у вас на каждый вызов Keys делается вызов sort.SliceIsSorted, у которого, на минуточку, линейная сложность. Ладно, пофиксите… А после каждого Sot лениво вызываете sort.SortSlice… Даже в лучшем случае это тоже как минимум линейно… Зато архитектура простая!
Это во многом справедливое замечание, спасибо. Метод Keys может быть написан получше. Но тесты показали что перестроение/хранение отсортированного дерева ключей (LLRB BTree) тоже не бесплатно. Версия на деревьях сохранилась в истории если интересно (https://github.com/recoilme/slowpoke/blob/ver.0.2/slowpoke.go) Просто в какой то момент я устал дебажить откуда и почему почему в дереве появился фантом( А если по скорости примерно тоже самое, зачем за него платить?
Метод сортировки в гоу, несмотря на название функции quickSort_func, под капотом содержит и сортировку вставкой, что должно привести к очень быстрой вставке новых ключей в отсортированный массив. И при Set — не вызывается (хотя такая идея была).
сортировку вставкой, что должно привести к очень быстрой вставке новых ключей в отсортированный массив
Да хоть сортировку пупыркой =)
Все равно минимум O(N) сравнений будет (алгоритм то не знает, что только последний элемент не на своем месте). Да и сортировка вставками включается только как последний шаг qsort для оптимизации. Но вообще я придираюсь, это да.
Портировал бэкенд клона медиум на slowpoke
github.com/recoilme/golang-gin-realworld-example-app

Полностью выкинута реалиционная база данных и написан бэкенд. С индексами, many-to-many отношениями и прочим. Просто как демонстрация того, что на slowpoke можно написать что угодно.
Если неконсистентность в том, что потратится единица счетчика, то да
Но вообще я не рекомендую смотреть на примеры серии realworld.io, там очень до фига тупо за рамками здравого смысла. Практически все написано на отвали, чуваком который учит как писать, а не пишет. Начиная с апи. Это примеры. В реальной жизни я не буду запускать приложение на бекенде, делающее несколько запросов к БД на каждом запросе. Они тупо тупые. Если вы хотите боевой клон хабра, который «полетит» в reallife — я сейчас как раз пилю такой, на github.com/recoilme/tgram/tree/tgram
Если неконсистентность в том, что потратится единица счетчика, то да
Неконсистентность в том, что в slowpoke нету транзакций. Например, UserName может записаться, а вот Email уже нет.

Но вообще я не рекомендую смотреть на примеры серии realworld.io, там очень до фига тупо за рамками здравого смысла.
Так а назачем тогда приводить такой пример :D
Приветите хороший пример, где будет нормальная атомарость и все такое.

Не хочу вас обижать, но выглядит как прям хрестоматийный пример Эффект Даннинга — Крюгера.
Я согласен. Это да, может создаться индекс «вникуда», проверки необходимо реализовать на уровне апликейшена, например: github.com/recoilme/golang-gin-realworld-example-app/blob/master/users/models.go#L108

Если от этого «бомбит» — лучше взять БД в которой проверки реализованы на уровне БД. Но придется забыть о скорости и гибкости.

А пример нужен затем — зачем вообще нужны примеры. Дать азы. Реальный медиум — будет слишком сложен для примера новичку, он просто запутается в коде. Поэтому и пишут такие дамми примеры для обучения.
Тут дело не в «бомбит». И дело не в проверках.

Просто есть такая полезная штука как ACID. И заменяю существующую БД (которая этот ACID в той или иной степени поддерживат) на ваш slowpoke придется все это реализовывать ручками… самим делать db-локи, реализовывать mvcc и прочее. Ну или забивать на ACID (он не всегда нужен, это да).

PS: я не могу найти у вас индексы (которые для сортировки, а не для uniqueness check), не подскажите?
Все верно прям на 100%. Полный хардкор. Гораздо проще написать чтоть типа gorm, unique и не париться)

Сортировка есть всегда и для всех ключей, но только бинарная. Допустим, если нужна корректная целочисленная сортировка — необходимо конвертировать в bigendian:
```
for i := 0; i < 40; i++ {
id := make([]byte, 4)
binary.BigEndian.PutUint32(id, uint32(i))
}
```
В примерах и тестах много примеров
При извлечении указывается порядок:
```
//open Db and read tags by prefix 2:* in ascending order
tagsKeys, _ := slowpoke.Keys(tags, []byte(«2:*»), 0, 0, true)
for _, v := range tagsKeys {
fmt.Print(string(v) + ", ")
}
```
Следовательно если текстовое поле, например — можно извлечь все ключи начинающиеся с префикс* — но надо класть приведенными к нижнему регистру, например
Все верно прям на 100%. Полный хардкор. Гораздо проще написать чтоть типа gorm, unique и не париться)
Гораздо проще взять, почитать что такое и зачем нужны транзакции… И попробовать их реализовать поверх slowpoke :) И уже потом говорить «я успешно заменил реляционную БД на свою key-value».
думал об этом, если честно — тупо не получилось транзакции привернуть. Вроде бы не самый сложный функционал, но когда полез в него — все оказалось сложнее
Sign up to leave a comment.

Articles