Skip to content

Latest commit

 

History

History
571 lines (446 loc) · 34.4 KB

README_RU.md

File metadata and controls

571 lines (446 loc) · 34.4 KB

GoDoc Mentioned in Awesome Go Travis CI Go Report Card codecov

Coffer

Простая 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.

Table of Contents

Usage

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))
}

Examples

По указанным путям вы найдёте много примеров использования транзакций.

API

Запущенная БД после выполнения операции возвращает отчёт с результатами:

  • кодом
  • ошибкой (коды ошибок хранятся здесь: github.com/claygod/coffer/reports)
  • данными
  • другими подробностями Ознакомиться со структурами ответов можно здесь: github.com/claygod/coffer/reports

Methods

  • Start
  • Stop
  • StopHard
  • Save
  • Write
  • WriteList
  • WriteListUnsafe
  • Read
  • ReadList
  • ReadListUnsafe
  • Delete
  • DeleteListStrict
  • DeleteListOptional
  • Transaction
  • Count
  • CountUnsafe
  • RecordsList
  • RecordsListUnsafe
  • RecordsListWithPrefix
  • RecordsListWithSuffix

Внимание! Все запросы, имена которых содержат Unsafe обычно можно выполнять как при запущенной, так и при остановленной (не запущеной) БД. Во втором случае нельзя запросы делать параллельно, иначе консистентность БД может оказаться нарушенной, а данные могут быть потеряны.

Start

Run the database. When launched, the Follow interactor is turned on, which monitors the relevance of the current checkpoint.

Stop

Остановить БД. Если вы хотите в своём приложении периодически останавливать и запускать БД, возможно, после остановки вы захотите создать новый клиент.

Write

Записать в БД новую запись, указав ключ и значение. Их длина должна удовлетворять требованиям, указанным в конфигурации.

WriteList

Записать в БД несколько записей, указав в агрументах соответствующую map. Strict mode (strictMode=true): Операция будет выполнена, если нет записей с такими ключами. Возвращается список уже существующих записей. Optional mode (strictMode=false): Операция будет выполнена не зависимо от того, есть записи с такими ключами или нет. Возвращается список уже существующих записей. Важно: этот аргумент ссылочный, его нельзя изменять!

WriteListUnsafe

Записать в БД несколько записей, указам в агрументах соответствующую map. Этот метод существует для того, чтобы перед запуском БД немного быстрее её наполнять. Метод не подразумевает параллельного использования.

Read

Прочитать одну запись из БД. В полученом report будет код результата, и если он положительный, то и значение в соответствующем поле.

ReadList

Прочитать несколько записей. Ограничение на максимальное количество читаемых записей есть в конфигурации. Помимо найденных записей, возвращается список не найденных записей.

ReadListUnsafe

Прочитать несколько записей. Метод может быть вызван при остановленной (или не запущенной) БД. Метод не подразумевает параллельного использования.

Delete

Удалить одну запись.

DeleteList

Strict: Удалить несколько записей, но только если все они есть в БД. Если хотя бы одной записи нет, то ни одна запись удалена не будет.

Optional: Удалить несколько записей. Удалены будут все записи из списка, которые будут найдены в БД.

Transaction

Выполнить транзакцию. Транзакция должна быть занесена в БД на этапе создания и конфигурирования БД. Ответственность за консистентность функционала обработчиков транзакций между разными запусками БД лежит на пользователе БД. Транзакция возвращает новые значения, сохранённые в БД.

Count

Получить количество записей в БД. Запрос можно делать только к запущенной БД

CountUnsafe

Получить количество записей в БД. Запросы к остановленной или не запущеннной БД нельзя делать параллельно!

RecordsList

Получить список всех ключей БД. При большом количестве записей в БД запрос будет медленным, поэтому применяйте его только в случае крайней нужды. Метод работает только при запущенной БД.

RecordsListUnsafe

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

RecordsListWithPrefix

Получить список всех ключей, имеющих указанный в аргументах префикс (начинается с этой строки).

Config

Фактически, достаточно указать путь к директории базы данных, и все параметры конфигурации установятся на дефолтные:

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()

Db

Указываем рабочую директорию, в которой БД будет хранить свои файлы. Для новой базы данных директория должна быть свободной от файлов с расширениями log, check, checkpoint.

BatchSize

Максимальное количество записей, которое БД может добавить за один раз. Уменьшение этого параметра немного улучшает latency (но не слишком сильно). Увеличение этого параметра немного ухудшает latency, но при этом увеличивает пропускную способность throughput.

LimitRecordsPerLogfile

Количество операций, которые будут записаны в один log-файл. Маленькая цифра заставит БД очень часто создавать новые файлы, что отрицательно скажется на скорости работы БД. Большая цифра уменьшает количество пауз на создание файлов, но файлы при этом становятся более крупными.

FollowPause

Размер интервала для запуска Follow интерактора, анализирующего старые логи и периодически создающего новые чекпоинты (точки останова).

LogsByCheckpoint

После скольких заполненных лог-файлов необходимо создавать новый чекпоинт, чем меньше цифра, тем чаще создаём. Для производительности лучше это делать не слишком часто.

AllowStartupErrLoadLogs

Опция разрешает работу БД при загрузке, даже в том случае, если последний файл логов закончен некорректно (типичная ситуация для нештатного завершения работы). По умолчанию опция разрешена.

MaxKeyLength

Максимально допустимая длина ключа.

MaxValueLength

Максимальный размер значения для записи.

MaxRecsPerOperation

Максимальное количество записей, которое может быть задействовано в одной операции.

RemoveUnlessLogs

Опция удаления старых файлов. После того, как Follow создал новый чекпоинт, он с разрешения этой опции удаляет теперь уже не нужные логи операций. Если вам по каким-то причина нужно хранить весь лог операций, вы можете отключить эту опцию, однако будьте готовы к тому, что это увеличит расход дискового пространства.

LimitMemory

Минимальный размер свободной оперативной памяти, при котором БД перестаёт выполнять операции и останавливается во избежание потери данных.

LimitDisk

Минимальный размер свободного места на жёстком диске, при котором БД перестаёт выполнять операции и останавливается во избежание потери данных.

Handler

Добавить хэндлер транзакции. Важно, чтобы при разных запусках одной и той же БД имя хэндлера и результаты его работы были идемпотентны. В противном случае в разное время при разных запусках хэндлеры будут работать по разному, что приведёт к нарушению консистентности данных. Если вы предполагаете со временем вносить изменения в хэндлеры, возможно добавление в ключ номера версии поможет упорядочить такой процесс.

Ограничения:

  • Аргумент, передаваемый в хэндлер, должен быть числом, слайсом байтов.
  • При необходимости передать сложные структуры, их нужно сериализовать в байты.
  • Хидер может оперировать только уже существующими записями.
  • Хидер не может удалять записи.
  • Хидер по окончании работы должен вернуть новые значения всех запрошенных записей.
  • Количество изменяемых хидером записей установлено в конфигурации 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
}

Handlers

Добавить несколько нэндлеров в БД за один раз. Важный момент: хэндлеры с совпадающими ключами перезатираются.

Create

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

Запуск

Старт

При старте последним по номеру должен быть чекпойнт. Если это не так, то значит, остановка была некорректной. Тогда грузится последний имеющийся чекпоинт и все логи после него до тех пор, пока это возможно. На битом логе или последнем логе скачиваем, пока получается, и на этом загрузку заканчиваем. БД создаёт новый чекпоинт, и после этого возможно продолжение исполнение кода.

Follow

После того, как БД будет запущена, она пишет все операции в журнал. В результате лог может сильно разрастись. Если в конце концов при окончании работы приложения БД будет корректно остановлена, то появится новый чекпойнт, и при последующем старте именно из него и будут взяты данные. Однако, остановка может оказаться некорректной, и новый чекпойнт создан не будет.

В этом случае при новом старте БД будет вынуждена загрузить старый чекпоинт, и провести заново все операции, которые были совершены и записаны в журнал. Это может оказаться весьма значительным по времени, и в конечном итоге база будет грузиться гораздо дольше, что не всегда приемлемо для приложений.

Именно поэтому в БД существует механизм фолловера, который методично перебирает логи в процессе работы БД и периодически создаёт чекпойнты, которые значительно ближе по состоянию к текущему моменту. Также за фолловером закреплена функция чистки старых логов и чекпойнтов, чтобы освобождать место на жёстком диске.

Хранение данных

Ваши данные хранятся в виде файлов в том каталоге, который вы указали при создании базы. Файлы с расширением 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 - не выполнено, дальнейшая работа с БД невозможна (охватывает ВСЕ паники)

Benchmark

  • BenchmarkCofferWriteParallel32LowConcurent-4 100000 12933 ns/op
  • BenchmarkCofferTransactionSequence-4 2000 227928 ns/op
  • BenchmarkCofferTransactionPar32NotConcurent-4 100000 4132 ns/op
  • BenchmarkCofferTransactionPar32HalfConcurent-4 100000 4199 ns/op

Dependencies

  • github.com/shirou/gopsutil/disk
  • github.com/shirou/gopsutil/mem
  • github.com/sirupsen/logrus

TODO

  • журнал при старте должен начинать новый лог
  • разобраться с именами чекпоинтов и логов (логика нумерации)
  • запуск и работа фолловера
  • чистка ненужных логов фолловером
  • предусмотреть возможность не удалять старые логи, добавить тест!
  • загрузка с битых файлов, чтобы останавливалась загрузка, но работа продолжалась (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
  • пауза в батчере - проверить её размер, выставить оптимальный
  • указать в описании, что данные при работе хранятся и на диске и в памяти
  • метод получения всех файлов логов и чекпоинтов
  • метод просмотра файла лога
  • метод просмотра файла чекпоинта
  • метод строгой записи в базу (только если запись с таким ключём не существует)
  • добавить метод для просмотра статуса БД

Copyright © 2019-2022 Eduard Sesigin. All rights reserved. Contacts: [email protected]