1
0
mirror of https://github.com/nxshock/zkv.git synced 2024-11-27 11:21:02 +05:00
zkv/zkv.go

289 lines
5.3 KiB
Go
Raw Normal View History

2022-02-16 16:16:29 +05:00
package zkv
2022-02-16 16:08:20 +05:00
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"os"
"sync"
"github.com/klauspost/compress/zstd"
)
2022-12-03 20:59:17 +05:00
type Store struct {
2022-02-16 16:08:20 +05:00
dataOffset map[string]int64
2022-12-03 20:59:17 +05:00
file *os.File
filePath string
offset int64
encoder *zstd.Encoder
buffer *bytes.Buffer
bufferDataOffset map[string]int64
2022-02-16 16:08:20 +05:00
options Options
readOrderChan chan struct{}
2022-12-02 21:37:15 +05:00
mu sync.RWMutex
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
func OpenWithOptions(filePath string, options Options) (*Store, error) {
options.setDefaults()
2022-02-16 16:08:20 +05:00
2022-12-03 20:59:17 +05:00
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
2022-02-16 16:08:20 +05:00
if err != nil {
2022-12-03 20:59:17 +05:00
f.Close()
return nil, fmt.Errorf("ошибка при открытии файла для записи: %v", err)
}
compressor, err := zstd.NewWriter(f)
if err != nil {
f.Close()
return nil, fmt.Errorf("ошибка при инициализации компрессора: %v", err)
}
database := &Store{
dataOffset: make(map[string]int64),
bufferDataOffset: make(map[string]int64),
offset: 0,
file: f,
encoder: compressor,
buffer: new(bytes.Buffer),
filePath: filePath,
options: options,
readOrderChan: make(chan struct{}, int(options.MaxParallelReads))}
// restore file data
readF, err := os.Open(filePath)
if err != nil {
f.Close()
return nil, fmt.Errorf("ошибка при открытии файла для чтения: %v", err)
}
defer readF.Close()
decompressor, err := zstd.NewReader(readF)
if err != nil {
f.Close()
return nil, fmt.Errorf("ошибка при инициализации декомпрессора: %v", err)
}
defer decompressor.Close()
offset := int64(0)
for {
n, record, err := readRecord(decompressor)
if err == io.EOF {
break
}
if err != nil {
f.Close()
return nil, fmt.Errorf("ошибка при чтении записи из файла: %v", err)
}
switch record.Type {
case RecordTypeSet:
database.dataOffset[string(record.KeyHash[:])] = offset
case RecordTypeDelete:
delete(database.dataOffset, string(record.KeyHash[:]))
}
offset += n
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
return database, nil
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
func Open(filePath string) (*Store, error) {
return OpenWithOptions(filePath, defaultOptions)
}
2022-02-16 16:08:20 +05:00
2022-12-03 20:59:17 +05:00
func (s *Store) Set(key, value interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.set(key, value)
}
func (s *Store) Get(key, value interface{}) error {
s.mu.RLock()
defer s.mu.RUnlock()
return s.get(key, value)
}
func (s *Store) Delete(key interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
keyHash, err := hashInterface(key)
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
record := &Record{
Type: RecordTypeDelete,
KeyHash: keyHash,
}
2022-02-16 16:08:20 +05:00
b, err := record.Marshal()
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
delete(s.dataOffset, string(record.KeyHash[:]))
delete(s.bufferDataOffset, string(record.KeyHash[:]))
2022-02-16 16:08:20 +05:00
2022-12-03 20:59:17 +05:00
_, err = s.buffer.Write(b)
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
if s.buffer.Len() > s.options.BufferSize {
err = s.flush()
if err != nil {
return err
}
}
2022-02-16 16:08:20 +05:00
return nil
}
2022-12-03 20:59:17 +05:00
func (s *Store) Flush() error {
s.mu.Lock()
defer s.mu.Unlock()
return s.flush()
}
2022-02-16 16:08:20 +05:00
2022-12-03 20:59:17 +05:00
func (s *Store) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
2022-12-03 20:59:17 +05:00
err := s.flush()
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
err = s.encoder.Close()
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
return s.file.Close()
}
func (s *Store) set(key, value interface{}) error {
record, err := newRecord(RecordTypeSet, key, value)
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
b, err := record.Marshal()
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
s.bufferDataOffset[string(record.KeyHash[:])] = int64(s.buffer.Len())
_, err = s.buffer.Write(b)
2022-02-16 16:08:20 +05:00
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
if s.buffer.Len() > s.options.BufferSize {
err = s.flush()
if err != nil {
return err
}
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
return nil
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
func (s *Store) get(key, value interface{}) error {
s.readOrderChan <- struct{}{}
defer func() { <-s.readOrderChan }()
2022-12-03 12:55:42 +05:00
2022-12-03 20:59:17 +05:00
hashToFind, err := hashInterface(key)
2022-02-16 16:08:20 +05:00
if err != nil {
2022-12-03 20:59:17 +05:00
return err
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
offset, exists := s.bufferDataOffset[string(hashToFind[:])]
if exists {
reader := bytes.NewReader(s.buffer.Bytes())
err = skip(reader, offset)
if err != nil {
return err
}
_, record, err := readRecord(reader)
if err != nil {
return err
}
return decode(record.ValueBytes, value)
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
offset, exists = s.dataOffset[string(hashToFind[:])]
if !exists {
return ErrNotExists
}
2022-02-16 16:08:20 +05:00
2022-12-03 20:59:17 +05:00
readF, err := os.Open(s.filePath)
2022-02-16 16:08:20 +05:00
if err != nil {
2022-12-03 20:59:17 +05:00
return err
2022-02-16 16:08:20 +05:00
}
defer readF.Close()
decompressor, err := zstd.NewReader(readF)
if err != nil {
2022-12-03 20:59:17 +05:00
return err
2022-02-16 16:08:20 +05:00
}
defer decompressor.Close()
2022-12-03 20:59:17 +05:00
err = skip(decompressor, offset)
if err != nil {
return err
}
2022-02-16 16:08:20 +05:00
2022-12-03 20:59:17 +05:00
_, record, err := readRecord(decompressor)
if err != nil {
return err
2022-02-16 16:08:20 +05:00
}
2022-12-03 20:59:17 +05:00
if !bytes.Equal(record.KeyHash[:], hashToFind[:]) {
return fmt.Errorf("wrong hash on this offset: expected %s, got %s", base64.StdEncoding.EncodeToString(hashToFind[:]), base64.StdEncoding.EncodeToString(record.KeyHash[:])) // TODO: заменить на константную ошибку
}
2022-12-03 20:59:17 +05:00
return decode(record.ValueBytes, value)
}
2022-12-03 20:59:17 +05:00
func (s *Store) flush() error {
l := int64(s.buffer.Len())
2022-12-03 20:59:17 +05:00
_, err := s.buffer.WriteTo(s.encoder)
if err != nil {
return err
}
2022-12-03 20:59:17 +05:00
for key, val := range s.bufferDataOffset {
s.dataOffset[key] = val + s.offset
}
2022-12-03 20:59:17 +05:00
s.bufferDataOffset = make(map[string]int64)
2022-12-03 20:59:17 +05:00
s.offset += l
2022-12-03 20:59:17 +05:00
err = s.encoder.Flush()
if err != nil {
return err
}
return nil
}