Простая key-value ACID* база данных. При среднем или даже низком latency
старается обеспечить
большую пропускную способность throughput
, не жертвуя при этом ACID свойствами БД.
БД даёт возможность создавать хидеры записей по своему усмотрению и использовать их в качестве транзакций.
Максимальный размер хранимых данных ограничен размером оперативной памяти компьютера.
*is a set of properties of database transactions intended to guarantee validity even in the event of errors, power failures, etc.
package main
import (
"fmt"
"github.com/claygod/coffer"
)
const curDir = "./"
func main() {
// STEP init
db, err, wrn := coffer.Db(curDir).Create()
switch {
case err != nil:
fmt.Println("Error:", err)
return
case wrn != nil:
fmt.Println("Warning:", err)
return
}
if !db.Start() {
fmt.Println("Error: not start")
return
}
defer db.Stop()
// STEP write
if rep := db.Write("foo", []byte("bar")); rep.IsCodeError() {
fmt.Sprintf("Write error: code `%d` msg `%s`", rep.Code, rep.Error)
return
}
// STEP read
rep := db.Read("foo")
rep.IsCodeError()
if rep.IsCodeError() {
fmt.Sprintf("Read error: code `%v` msg `%v`", rep.Code, rep.Error)
return
}
fmt.Println(string(rep.Data))
}
По указанным путям вы найдёте много примеров использования транзакций.
Quick start
https://github.com/claygod/coffer/tree/master/examples/quick_startFinance
https://github.com/claygod/coffer/tree/master/examples/finance
Запущенная БД после выполнения операции возвращает отчёт с результатами:
- кодом
- ошибкой (коды ошибок хранятся здесь:
github.com/claygod/coffer/reports
) - данными
- другими подробностями
Ознакомиться со структурами ответов можно здесь:
github.com/claygod/coffer/reports
- Start
- Stop
- StopHard
- Save
- Write
- WriteList
- WriteListUnsafe
- Read
- ReadList
- ReadListUnsafe
- Delete
- DeleteListStrict
- DeleteListOptional
- Transaction
- Count
- CountUnsafe
- RecordsList
- RecordsListUnsafe
- RecordsListWithPrefix
- RecordsListWithSuffix
Внимание! Все запросы, имена которых содержат Unsafe
обычно можно выполнять как при запущенной,
так и при остановленной (не запущеной) БД. Во втором случае нельзя запросы делать параллельно,
иначе консистентность БД может оказаться нарушенной, а данные могут быть потеряны.
Run the database. When launched, the Follow
interactor is turned on,
which monitors the relevance of the current checkpoint.
Остановить БД. Если вы хотите в своём приложении периодически останавливать и запускать БД, возможно, после остановки вы захотите создать новый клиент.
Записать в БД новую запись, указав ключ и значение. Их длина должна удовлетворять требованиям, указанным в конфигурации.
Записать в БД несколько записей, указав в агрументах соответствующую map
.
Strict mode (strictMode=true):
Операция будет выполнена, если нет записей с такими ключами.
Возвращается список уже существующих записей.
Optional mode (strictMode=false):
Операция будет выполнена не зависимо от того, есть записи с такими ключами или нет.
Возвращается список уже существующих записей.
Важно: этот аргумент ссылочный, его нельзя изменять!
Записать в БД несколько записей, указам в агрументах соответствующую map
.
Этот метод существует для того, чтобы перед запуском БД немного быстрее её наполнять.
Метод не подразумевает параллельного использования.
Прочитать одну запись из БД. В полученом report
будет код результата, и если он положительный,
то и значение в соответствующем поле.
Прочитать несколько записей. Ограничение на максимальное количество читаемых записей есть в конфигурации. Помимо найденных записей, возвращается список не найденных записей.
Прочитать несколько записей. Метод может быть вызван при остановленной (или не запущенной) БД. Метод не подразумевает параллельного использования.
Удалить одну запись.
Strict: Удалить несколько записей, но только если все они есть в БД. Если хотя бы одной записи нет, то ни одна запись удалена не будет.
Optional: Удалить несколько записей. Удалены будут все записи из списка, которые будут найдены в БД.
Выполнить транзакцию. Транзакция должна быть занесена в БД на этапе создания и конфигурирования БД. Ответственность за консистентность функционала обработчиков транзакций между разными запусками БД лежит на пользователе БД. Транзакция возвращает новые значения, сохранённые в БД.
Получить количество записей в БД. Запрос можно делать только к запущенной БД
Получить количество записей в БД. Запросы к остановленной или не запущеннной БД нельзя делать параллельно!
Получить список всех ключей БД. При большом количестве записей в БД запрос будет медленным, поэтому применяйте его только в случае крайней нужды. Метод работает только при запущенной БД.
Получить список всех ключей БД. При большом количестве записей в БД запрос будет медленным, поэтому применяйте его только в случае крайней нужды. При использовании запроса с остановленной или не запущенной БД параллельность запрещена.
Получить список всех ключей, имеющих указанный в аргументах префикс (начинается с этой строки).
Фактически, достаточно указать путь к директории базы данных, и все параметры конфигурации установятся на дефолтные:
cof, err, wrn := Db(dirPath) . Create()
Дефолтные значения можно увидеть в файле /config.go
.
Однако каждый из параметров можно сконфигурировать:
Db(dirPath).
BatchSize(batchSize).
LimitRecordsPerLogfile(limitRecordsPerLogfile).
FollowPause(100*time.Second).
LogsByCheckpoint(1000).
AllowStartupErrLoadLogs(true).
MaxKeyLength(maxKeyLength).
MaxValueLength(maxValueLength).
MaxRecsPerOperation(1000000).
RemoveUnlessLogs(true).
LimitMemory(100 * 1000000).
LimitDisk(1000 * 1000000).
Handler("handler1", &handler1).
Handler("handler2", &handler2).
Handlers(map[string]*handler).
Create()
Указываем рабочую директорию, в которой БД будет хранить свои файлы. Для новой базы данных директория должна быть свободной от файлов с расширениями log, check, checkpoint.
Максимальное количество записей, которое БД может добавить за один раз. Уменьшение этого параметра
немного улучшает latency
(но не слишком сильно). Увеличение этого параметра немного ухудшает latency
,
но при этом увеличивает пропускную способность throughput
.
Количество операций, которые будут записаны в один log-файл. Маленькая цифра заставит БД очень часто создавать новые файлы, что отрицательно скажется на скорости работы БД. Большая цифра уменьшает количество пауз на создание файлов, но файлы при этом становятся более крупными.
Размер интервала для запуска Follow
интерактора, анализирующего старые логи и периодически создающего
новые чекпоинты (точки останова).
После скольких заполненных лог-файлов необходимо создавать новый чекпоинт, чем меньше цифра, тем чаще создаём. Для производительности лучше это делать не слишком часто.
Опция разрешает работу БД при загрузке, даже в том случае, если последний файл логов закончен некорректно (типичная ситуация для нештатного завершения работы). По умолчанию опция разрешена.
Максимально допустимая длина ключа.
Максимальный размер значения для записи.
Максимальное количество записей, которое может быть задействовано в одной операции.
Опция удаления старых файлов. После того, как Follow
создал новый чекпоинт, он с разрешения этой
опции удаляет теперь уже не нужные логи операций. Если вам по каким-то причина нужно хранить весь лог операций,
вы можете отключить эту опцию, однако будьте готовы к тому, что это увеличит расход дискового пространства.
Минимальный размер свободной оперативной памяти, при котором БД перестаёт выполнять операции и останавливается во избежание потери данных.
Минимальный размер свободного места на жёстком диске, при котором БД перестаёт выполнять операции и останавливается во избежание потери данных.
Добавить хэндлер транзакции. Важно, чтобы при разных запусках одной и той же БД имя хэндлера и результаты его работы были идемпотентны. В противном случае в разное время при разных запусках хэндлеры будут работать по разному, что приведёт к нарушению консистентности данных. Если вы предполагаете со временем вносить изменения в хэндлеры, возможно добавление в ключ номера версии поможет упорядочить такой процесс.
Ограничения:
- Аргумент, передаваемый в хэндлер, должен быть числом, слайсом байтов.
- При необходимости передать сложные структуры, их нужно сериализовать в байты.
- Хидер может оперировать только уже существующими записями.
- Хидер не может удалять записи.
- Хидер по окончании работы должен вернуть новые значения всех запрошенных записей.
- Количество изменяемых хидером записей установлено в конфигурации
MaxRecsPerOperation
func HandlerExchange(arg []byte, recs map[string][]byte) (map[string][]byte, error) {
if arg != nil {
return nil, fmt.Errorf("Args not null.")
} else if len(recs) != 2 {
return nil, fmt.Errorf("Want 2 records, have %d", len(recs))
}
recsKeys := make([]string, 0, 2)
recsValues := make([][]byte, 0, 2)
for k, v := range recs {
recsKeys = append(recsKeys, k)
recsValues = append(recsValues, v)
}
out := make(map[string][]byte, 2)
out[recsKeys[0]] = recsValues[1]
out[recsKeys[1]] = recsValues[0]
return out, nil
}
func HandlerDebit(arg []byte, recs map[string][]byte) (map[string][]byte, error) {
if arg == nil || len(arg) != 8 {
return nil, fmt.Errorf("Invalid Argument: %v.", arg)
} else if len(recs) != 1 {
return nil, fmt.Errorf("Want 1 record, have %d", len(recs))
}
delta := bytesToUint64(arg)
var recKey string
var recValue []byte
for k, v := range recs {
recKey = k
recValue = v
}
if len(recValue) != 8 {
return nil, fmt.Errorf("The length of the value in the record is %d bytes, but 8 bytes are needed", len(recValue))
}
curAmount := bytesToUint64(recValue)
newAmount := curAmount + delta
if curAmount > newAmount {
return nil, fmt.Errorf("Account overflow. There is %d, a debit of %d.", curAmount, delta)
}
return map[string][]byte{recKey: uint64ToBytes(newAmount)}, nil
}
Добавить несколько нэндлеров в БД за один раз. Важный момент: хэндлеры с совпадающими ключами перезатираются.
Обязательная команда (должна быть последней), которая заканчивает конфигурирование и создаёт БД.
При старте последним по номеру должен быть чекпойнт. Если это не так, то значит, остановка была некорректной. Тогда грузится последний имеющийся чекпоинт и все логи после него до тех пор, пока это возможно. На битом логе или последнем логе скачиваем, пока получается, и на этом загрузку заканчиваем. БД создаёт новый чекпоинт, и после этого возможно продолжение исполнение кода.
После того, как БД будет запущена, она пишет все операции в журнал. В результате лог может сильно разрастись. Если в конце концов при окончании работы приложения БД будет корректно остановлена, то появится новый чекпойнт, и при последующем старте именно из него и будут взяты данные. Однако, остановка может оказаться некорректной, и новый чекпойнт создан не будет.
В этом случае при новом старте БД будет вынуждена загрузить старый чекпоинт, и провести заново все операции, которые были совершены и записаны в журнал. Это может оказаться весьма значительным по времени, и в конечном итоге база будет грузиться гораздо дольше, что не всегда приемлемо для приложений.
Именно поэтому в БД существует механизм фолловера, который методично перебирает логи в процессе работы БД и периодически создаёт чекпойнты, которые значительно ближе по состоянию к текущему моменту. Также за фолловером закреплена функция чистки старых логов и чекпойнтов, чтобы освобождать место на жёстком диске.
Ваши данные хранятся в виде файлов в том каталоге, который вы указали при создании базы.
Файлы с расширением log
содержат описание выполненных операций.
Файлы с расширением checkpoint
содержат снимки состояния БД на определённый момент.
Файлы с расширением check
содержат неполный снимок состояния БД.
Используя параметр конфигурации RemoveUnlessLogs
, вы може приказать БД
старые и ненужные файлы удалять, чтобы сберечь дисковое пространство.
Если база данных остановлена в штатном режиме, то последним файлом, записанным на диск,
будет файл checkpoint
, а его номер будет максимальным.
Если база данных остановлена некорректно, то скорей всего максимальный номер будет у файла
с расширением log
или check
.
Внимание! до тех пор, пока БД полностью не остановлена, запрещено с файлами базы данных проводить какие-либо операции.
Если вы хотите скопировать куда-либо базу, то необходимо копировать всё содержимое директории.
Если вы хотите при копировании скопировать минимум файлов, то необходимо скопировать файл
с расширением checkpoint
, имеющий максимальный номер, и все файлы с расширением log
,
которые имеют номер, больший чем у скопированного файла checkpoint
.
Если работа приложения, использующего БД завершена некорректно, то при следующей загрузке БД
постарается найти последний корректный снимок состояния checkpoint
. Найдя этот файл, БД загрузит его,
после чего загрузит все log
файлы с большими номерами. Мы ожидаем, что последний log
файл может
быть не до конца заполненным, так как во время записи работа могла быть прервана. Поэтому загрузка
с испорченного файла будет выполнена до испорченного (недозаписанного) участка, после чего
загрузка БД считается завершенной. По кончании загрузки БД создаёт новый checkpoint
.
Если сбои системы происходят во время старта (загрузки) БД, возможны ошибки и нарушение
консистентности данных.
Коды ошибок хранятся здесь: "github.com/claygod/coffer/reports/codes"
Если получен Ok
код, значит операция выполнена полностью. Если Код содержит Error
, значит операция
не выполнена, выполнена не полностью или выполнена с ошибкой, однако работу с БД можно продолжать.
Если код содержит Panic
, значит состояние БД таково, что работать с ней дальше нельзя.
- Ok - выполнено без замечаний
- Error - не выполнено или выполнено не полностью, но работать дальше можно
- ErrRecordLimitExceeded - превышен лимит записей на одну операцию
- ErrExceedingMaxValueSize - слишком длинное значение
- ErrExceedingMaxKeyLength - слишком длинный ключ
- ErrExceedingZeroKeyLength - слишком короткий ключ
- ErrHandlerNotFound - не найден хэндлер
- ErrParseRequest - не получилось подготовить запрос для логгирования
- ErrResources - не хватает ресурсов
- ErrNotFound - не найдены ключи
- ErrReadRecords - ошибка считывания записей для транзакции (при отсутствии хоть одной записи транзакцию нельзя проводить)
- ErrHandlerReturn - найденный и загруженный хандлер вернул ошибку
- ErrHandlerResponse - хандлер вернул неполные ответы
- Panic - не выполнено, дальнейшая работа с БД невозможна
- PanicStopped - приложение остановлено
- PanicWAL - ошибка работы журнала проведённых операций
Чтобы не экспортировать в приложение, работающее с БД, у отчётов (Report) есть методы:
- IsCodeOk - выполнено без замечаний
- IsCodeError - не выполнено или выполнено не полностью, но работать дальше можно
- IsCodeErrRecordLimitExceeded - превышен лимит записей на одну операцию
- IsCodeErrExceedingMaxValueSize - слишком длинное значение
- IsCodeErrExceedingMaxKeyLength - слишком длинный ключ
- IsCodeErrExceedingZeroKeyLength - слишком короткий ключ
- IsCodeErrHandlerNotFound - не найден хэндлер
- IsCodeErrParseRequest - не получилось подготовить запрос для логгирования
- IsCodeErrResources - не хватает ресурсов
- IsCodeErrNotFound - не найдены ключи
- IsCodeErrReadRecords - ошибка считывания записей для транзакции (при отсутствии хоть одной записи транзакцию нельзя проводить)
- IsCodeErrHandlerReturn - найденный и загруженный хандлер вернул ошибку
- IsCodeErrHandlerResponse - хандлер вернул неполные ответы
- IsCodePanic - не выполнено, дальнейшая работа с БД невозможна
- IsCodePanicStopped - приложение остановлено
- IsCodePanicWAL - ошибка работы журнала проведённых операций
Для проверки полученных кодов не очень удобно делать большие свитчи. Можно ограничиться всего тремя проверками:
- IsCodeOk - выполнено без замечаний
- IsCodeError - не выполнено или выполнено не полностью, но работать дальше можно (охватывает ВСЕ ошибки)
- IsCodePanic - не выполнено, дальнейшая работа с БД невозможна (охватывает ВСЕ паники)
- BenchmarkCofferWriteParallel32LowConcurent-4 100000 12933 ns/op
- BenchmarkCofferTransactionSequence-4 2000 227928 ns/op
- BenchmarkCofferTransactionPar32NotConcurent-4 100000 4132 ns/op
- BenchmarkCofferTransactionPar32HalfConcurent-4 100000 4199 ns/op
- github.com/shirou/gopsutil/disk
- github.com/shirou/gopsutil/mem
- github.com/sirupsen/logrus
- журнал при старте должен начинать новый лог
- разобраться с именами чекпоинтов и логов (логика нумерации)
- запуск и работа фолловера
- чистка ненужных логов фолловером
- предусмотреть возможность не удалять старые логи, добавить тест!
- загрузка с битых файлов, чтобы останавливалась загрузка, но работа продолжалась (AllowStartupErrLoadLogs)
- циклическая загрузка чекпойнтов, пока они не кончатся (при ошибках)
- возврат не ошибок, а отчётов о проделанной работе
- добавить DeleteOptional, в том числе и в Operations
- тест Count
- тест Write
- тест Read
- тест Delete
- тест Transaction
- тест RecordsList
- тест RecordsListUnsafe
- тест RecordsListWithPrefix
- тест RecordsListWithSuffix
- тест ReadListUnsafe
- тест на загрузку с битым логом (последним, остальные в порядке)
- тест на загрузку с битым чекпоинтом
- тест на загрузку с битым логом и идущим за ним ещё одним логом
- тест на использование транзакции
- для удобства тестирования сделать WriteUnsafe
-
для чего нужен WriteUnsafeRecord в Checkpoint ? (для записи при старте?)альтернатива WriteListUnsafe (быстрее) - бенчмарк записи конкурентной и не конкурентной
- бенчмарк чтения конкурентного
- бенчмарк записии и чтения в конкурентном режиме
- бенчмарк конкурентных транзакций в параллельном режиме
- при загрузке - при поломанных файлах возвращаться может wrn, а не err
- разобраться с журналом и батчером, почему при быстрой записи records попадают в следующий лог
- перехват паник в корне приложения и на уровне usecases
-
при транзакциях можно некоторые записи из участвующих удалять (!надобность под вопросом!) - тестирование вспомогательных хэлперов
- при создании БД сразу добавлять список хэндлеров, т.к. и загрузка из логов тоже происходит сразу
- добавить удобный конфигуратор при создании бд
- комментарии перевести на английский язык
- очистить код от старых артефактов
- завести каталог для документации
- завести каталог для примеров
- сделать простой пример с записью, транзакцией и чтением
- сделать пример с финансовыми транзакциями
- пример обработки ошибок
- прогнать линтер и устранить все некорректности в коде
- добавить Usage/Quick start текст в readme
- описание кодов ошибок
- описание конфигурирования
- в описании указать сторонние пакеты (как зависимости)
- репортам добавить методы проверки на все ошибки в духе IsErrBlahBlahBlah
- все импортируемые пакеты перенести в дистрибутив
- перевести использование WriteUnsafeRecord на WriteListUnsafe
- добавитьReadListUnsafe для возможности чтения при остановленной базе
- добавить RecordsListUnsafe, который может работать и при остановленной и при работающей БД
- получение списка ключей с условием по префиксу RecordsListWithPrefix
- получение списка ключей с условием по суффиксу RecordsListWithSuffix
- убрать метод Save
- при транзакции возвращать в отчёте новые значения
- в тестах проверить возвращаемое значение
- начинать нумерацию с больших цифр, допустим с миллиона/миллиарда (удобней для сортировки файлов)
- всем публичным методам дать корректное описание-комментарий
- описать возврат ошибок и варнингов в методе Create
- пауза в батчере - проверить её размер, выставить оптимальный
- указать в описании, что данные при работе хранятся и на диске и в памяти
- метод получения всех файлов логов и чекпоинтов
- метод просмотра файла лога
- метод просмотра файла чекпоинта
- метод строгой записи в базу (только если запись с таким ключём не существует)
- добавить метод для просмотра статуса БД