Documentation
¶
Index ¶
- Constants
- func ComputeTimePerOp(tps float64) string
- func ShardIndex(key []byte) int
- type BFile
- func (b *BFile) Close() (err error)
- func (b *BFile) Flush() (err error)
- func (b *BFile) Offset() (offset uint64, err error)
- func (b *BFile) Open() (err error)
- func (b *BFile) ReadAt(offset uint64, data []byte) (err error)
- func (b *BFile) Write(Data []byte) (update bool, err error)
- func (b *BFile) WriteAt(offset int64, data []byte) (err error)
- type Bloom
- type DBBKey
- type DBBKeyFull
- type FastRandom
- func (f FastRandom) Clone() *FastRandom
- func (f *FastRandom) NextBool() bool
- func (f *FastRandom) NextHash() (hash [32]byte)
- func (f *FastRandom) RandBuff(min uint, max uint) []byte
- func (f *FastRandom) RandChar(min uint, max uint) []byte
- func (f *FastRandom) Reset()
- func (f *FastRandom) Step()
- func (f *FastRandom) Uint64() uint64
- func (f *FastRandom) UintN(N uint) uint
- type Header
- type HistoryFile
- func (hf *HistoryFile) AddKeys(keyList []byte) (err error)
- func (hf *HistoryFile) EOF() uint64
- func (hf *HistoryFile) Get(Key [32]byte) (dbBKey *DBBKey, err error)
- func (hf *HistoryFile) Index(key [32]byte) int
- func (hf *HistoryFile) Marshal() []byte
- func (hf *HistoryFile) OffsetSort()
- func (hf *HistoryFile) Unmarshal(data []byte)
- func (hf *HistoryFile) UpdateKeySet(index int, keyList []byte) (err error)
- type KFile
- func (k *KFile) Close() (err error)
- func (k *KFile) Flush() (err error)
- func (k *KFile) Get(Key [32]byte) (dbBKey *DBBKey, err error)
- func (k *KFile) GetKeyList() (keyValues map[[32]byte]*DBBKey, KeyList [][32]byte, err error)
- func (k *KFile) LoadHeader() (err error)
- func (k *KFile) Open() error
- func (k *KFile) PopulateBloomFilterFromHistory() error
- func (k *KFile) PushHistory() (err error)
- func (k *KFile) Put(Key [32]byte, dbBKey *DBBKey) (err error)
- func (k *KFile) WriteHeader() (err error)
- type KV
- type KV2
- func (k *KV2) Close() error
- func (k *KV2) Compress()
- func (k *KV2) Get(key [32]byte) (value []byte, err error)
- func (k *KV2) GetDyna(key [32]byte) (value []byte, err error)
- func (k *KV2) GetPerm(key [32]byte) (value []byte, err error)
- func (k *KV2) Open() error
- func (k *KV2) Put(key [32]byte, value []byte) (writes int, err error)
- func (k *KV2) PutDyna(key [32]byte, value []byte) (writes int, err error)
- func (k *KV2) PutPerm(key [32]byte, value []byte) (writes int, err error)
- type KVShard
- func (k *KVShard) Close() (err error)
- func (k *KVShard) Compress() (err error)
- func (k *KVShard) Get(key [32]byte) (value []byte, err error)
- func (k *KVShard) GetDyna(key [32]byte) (value []byte, err error)
- func (k *KVShard) GetPerm(key [32]byte) (value []byte, err error)
- func (k *KVShard) Put(key [32]byte, value []byte) (err error)
- func (k *KVShard) PutDyna(key [32]byte, value []byte) (err error)
- func (k *KVShard) PutPerm(key [32]byte, value []byte) (err error)
- func (k *KVShard) ShardDir(index int) string
- type KVView
- func (s *KVView) Close()
- func (s *KVView) Get(key [32]byte) (value []byte, err error)
- func (s *KVView) GetViewIndex(view *View) int
- func (s *KVView) IsViewActive() bool
- func (s *KVView) NewView() *View
- func (s *KVView) Put(key [32]byte, value []byte) error
- func (s *KVView) ViewGet(view *View, key [32]byte) (value []byte, err error)
- type KeySet
- type View
Constants ¶
const BufferSize = 1024 * 32 // Buffer size for values written to the BFile
const DBKeyFullSize = 48
const DynaDirName = "dyna"
const IndexOffsets = 0 // byte index to a 16 bit int used to define keysets in a KFile
const IndexShards = 4 // byte index to a 16 bit int used to define a shard for a key
const (
KeySetSize = 16
)
const NumShards = 512
const PermDirName = "perm"
Variables ¶
This section is empty.
Functions ¶
func ComputeTimePerOp ¶
func ShardIndex ¶
ShardIndex Returns the index of into the header for the given key
Types ¶
type BFile ¶
type BFile struct { File *os.File // The file being buffered. It doesn't have to be kept open Filename string // Fully qualified file name for the BFle Buffer [BufferSize]byte // The current BFBuffer under construction EOB uint64 // End within the buffer EOD uint64 // Current EOD }
Block BFile Holds the buffers and ID stuff needed to build DBBlocks (Database Blocks)
func (*BFile) Open ¶
Open Opens the underlying file and positions the file location to the end of the file.
func (*BFile) ReadAt ¶
ReadAt Seek to the offset from start and read into the given data buffer. Note that to avoid a flush to disk, ReadAt must be smart about what is in the buffer vs what is on disk, and to open the file and read from it only if required.
func (*BFile) Write ¶
Write A Buffered Write given Data into the File. Returns:
update -- true if a actual file update occurs err -- nil on no error, the error if an error occurs
type Bloom ¶
type Bloom struct { SizeOfMap float64 NumBytes uint64 Map []byte K int // Number of hash functions }
func NewBloomFilter ¶
NewBloomFilter Create a Bloom Filter of the given size in MB with k hash functions
type DBBKey ¶
type DBBKeyFull ¶
type FastRandom ¶
type FastRandom struct {
// contains filtered or unexported fields
}
func NewFastRandom ¶
func NewFastRandom(seed []byte) *FastRandom
NewFastRandom() Returns a Fast Random Generator If no seed is provided, the seed will be generated by selecting a random nano second In order to make this seed near impossible to guess, we collect nano seconds in a loop which means the speed of the CPU and the load on the CPU will create unpredictable randomness in the seed.
func (FastRandom) Clone ¶
func (f FastRandom) Clone() *FastRandom
Make a clone of a FastRandom state as it currently exists
func (*FastRandom) NextBool ¶
func (f *FastRandom) NextBool() bool
func (*FastRandom) NextHash ¶
func (f *FastRandom) NextHash() (hash [32]byte)
NextHahs return a random 32 byte array
func (*FastRandom) RandBuff ¶
func (f *FastRandom) RandBuff(min uint, max uint) []byte
RandBuff Always returns a buffer of random bytes If max > 100 MB, max is set to 100 MB If max < 1, max is set to 1 If min > max, min is set to max
func (*FastRandom) RandChar ¶
func (f *FastRandom) RandChar(min uint, max uint) []byte
RandChar Returns a buffer of random hex characters If max > 100 MB, max is set to 100 MB If max < 1, max is set to 1 If min > max, min is set to max
func (*FastRandom) Reset ¶
func (f *FastRandom) Reset()
Reset() Reset the sequence produced by FastRandom to its initial state
func (*FastRandom) Step ¶
func (f *FastRandom) Step()
type Header ¶
type Header struct { OffsetsCnt uint32 // Number of bins in the Offset Table HeaderSize uint32 // Length of the header Offsets []uint64 // List of offsets EndOfList uint64 // Offset marking end of the last Key section }
Offset table for all the indexes in the KFile EndOfList is necessary because when we close the KFile, the list of keys will often be smaller. So the file will have stuff past the key list we need to ignore. This way, we have an offset to the end of valid keys.
func (*Header) OffsetIndex ¶
OffsetIndex Returns the index of into the header for the given key
type HistoryFile ¶
type HistoryFile struct { // Not marshaled Mutex sync.Mutex // Stops access to History during a reorg Directory string // Path to the file Filename string // Computed; directory + filename HeaderSize uint64 // Computed based of IndexCnt File *os.File // Path to the History File KeySetOffset []*KeySet // Offsets around key sets, in file offset order // Marshaled OffsetCnt int32 // Count of offsets to key sets KeySets []*KeySet // Offsets around key sets, in key index order }
func NewHistoryFile ¶
func NewHistoryFile(OffsetCnt uint64, Directory string) (historyFile *HistoryFile, err error)
NewHistoryFile Creates and initializes a HistoryFile. If one already exists, it is replaced with a fresh, new, empty HistoryFile
func (*HistoryFile) AddKeys ¶
func (hf *HistoryFile) AddKeys(keyList []byte) (err error)
AddKeys Take a buffer of Keys, sort them into bins, and add them to the History file. Assumes the keyList is already sorted into bins internally.
func (*HistoryFile) EOF ¶
func (hf *HistoryFile) EOF() uint64
EOF Return the last offset in the HistoryFile
func (*HistoryFile) Get ¶
func (hf *HistoryFile) Get(Key [32]byte) (dbBKey *DBBKey, err error)
Get Get the value for a given DBKeyFull. The value returned is free for the user to use (i.e. not part of a buffer used by the BFile)
func (*HistoryFile) Index ¶
func (hf *HistoryFile) Index(key [32]byte) int
Index Compute the index into the KeySets for this key
func (*HistoryFile) Marshal ¶
func (hf *HistoryFile) Marshal() []byte
Marshal Only marshals the header, which is written to the// Marshal Only marshals the header, which is written to the front of the History File
func (*HistoryFile) OffsetSort ¶
func (hf *HistoryFile) OffsetSort()
OffsetSort Sort the indexes by HistoryFile Offsets; Sort by the end, because empty keySets can have the same Start as one keySet...
func (*HistoryFile) Unmarshal ¶
func (hf *HistoryFile) Unmarshal(data []byte)
Unmarshal Unmarshals the header.
func (*HistoryFile) UpdateKeySet ¶
func (hf *HistoryFile) UpdateKeySet(index int, keyList []byte) (err error)
UpdateKeySet Add the given entries to the KeySet at the given index and update the History File
If the new keys fit where the KeySet is, just add them to the HistoryFile ¶
If a KeySet does not fit where it is in the HistoryFile, Update it's start and end to where it can fit, and update the KeySets offsets, and the HistoryFile. Mem
type KFile ¶
type KFile struct { Header // kFile header (what is pushed to disk) Directory string // Directory of the BFile File *BFile // Key File History *HistoryFile // The History Database (nil if history is disabled) Cache map[[32]byte]*DBBKey // Cache of DBBKey Offsets BlocksCached int // Track blocks cached before rewritten HistoryMutex sync.Mutex // Allow the History to be merged in background KeyCnt uint64 // Number of keys in the current KFile TotalCnt uint64 // Total number of keys processed OffsetCnt uint64 // Number of key sets in the kFile KeyLimit uint64 // How many keys triggers to send keys to History MaxCachedBlocks int // Maximum number of keys cached before flushing to kfile HistoryOffsets int // History offset cnt BloomFilter *Bloom // Bloom filter for quick key existence checks }
Block File Holds the buffers and ID stuff needed to build DBBlocks (Database Blocks)
KFile can operate in two modes based on whether history is enabled or disabled:
1. With History Enabled (used in PermKV):
- Values are immutable - once a key is associated with a value, it cannot be changed
- Attempting to overwrite a key with a different value will result in an error
- Overwriting a key with the same value is allowed (no-op)
- Suitable for content-addressed storage where keys are derived from values (e.g., hash of value)
- Uses a Bloom filter to optimize key lookups and avoid unnecessary disk I/O
2. With History Disabled (used in DynaKV):
- Values are mutable - keys can be freely associated with different values over time
- Overwriting a key with a different value is allowed
- Suitable for state storage where keys have an arbitrary relationship to values
Performance Optimizations: - Uses a Bloom filter to quickly determine if a key definitely doesn't exist - Checks the Bloom filter before any disk I/O operations - In Put operations, uses the Bloom filter to avoid unnecessary disk lookups - Memory usage is optimized by having a single Bloom filter per KFile
func (*KFile) Close ¶
Close Take everything in flight and write it to disk, then close the file. Note that if an error occurs while updating the BFile, the BFile will be trashed.
func (*KFile) Get ¶
Get Get the value for a given DBKeyFull. The value returned is free for the user to use (i.e. not part of a buffer used by the BFile). If the key is not found, the code will look at the history.
func (*KFile) GetKeyList ¶
GetKeyList Returns all the keys and their values, and a list of the keys sorted by the key bins.
func (*KFile) LoadHeader ¶
LoadHeader Load the Header out of the Key File
func (*KFile) Open ¶
Open Make sure the underlying File is open for adding keys. Sets the location in the file for writing to the end of the file.
func (*KFile) PopulateBloomFilterFromHistory ¶
PopulateBloomFilterFromHistory Populates the Bloom filter with all existing keys in the history file This should be called when opening a KFile with history enabled
func (*KFile) PushHistory ¶
PushHistory Creates a new kFile height. Merges the keys of the current kFile into the History. Resets the KFile to accept more keys.
func (*KFile) Put ¶
Put Put a key value pair into the BFile, return the *DBBKeyFull
Behavior depends on whether history is enabled:
- With history enabled (k.History != nil): Values are immutable. If the key already exists with a different value, an error is returned. Overwriting with the same value is a no-op.
- With history disabled (k.History == nil): Values are mutable. Keys can be freely overwritten with different values.
This design supports two use cases:
- Content-addressed storage (history enabled): Where keys are derived from values (e.g., hash) and immutability is required.
- State storage (history disabled): Where keys have an arbitrary relationship to values and need to be updated over time.
func (*KFile) WriteHeader ¶
WriteHeader Write the Header to the Key File
type KV ¶
type KV struct { Directory string HistoryFile *HistoryFile UseHistory bool // contains filtered or unexported fields }
func NewKV ¶
func NewKV(history bool, directory string, offsetsCnt, KeyLimit uint64, MaxCachedBlocks int) (kv *KV, err error)
NewKV Overwrites any existing directory; directories are created for the vFile and kFile
func OpenKV ¶
OpenKV Open an existing Key/Value Database that uses separate BFiles to hold values and keys.
type KV2 ¶
type KV2 struct { Directory string // Directory where the PermKV and DynaKV directories are PermKV *KV // The Perm KV DynaKV *KV // the Dyna KV DWrites int // Number of writes to the DynaKV since the last compress PWrites int // Number of writes to the PermKV since the last compress }
func NewKV2 ¶
func NewKV2(directory string, offsetsCnt, KeyLimit uint64, MaxCachedBlocks int) (kv2 *KV2, err error)
NewKV2 Create a two level KV file with different immutability characteristics:
1. PermKV: Uses KFile with history enabled (immutable values)
- Created with history=true
- Once a key is associated with a value, it cannot be changed
- If a key in PermKV needs to be updated, it's moved to DynaKV
2. DynaKV: Uses KFile with history disabled (mutable values)
- Created with history=false
- Keys can be freely associated with different values over time
This design efficiently separates immutable data (content-addressed storage) from mutable data (state storage) in a blockchain-style database.
func (*KV2) Compress ¶
func (k *KV2) Compress()
Compress Only DynaKV is compressed, since PermKV doesn't change. That does mean one bogus DynaKV key will exist in PermKV.
TODO: Cleanse PermKV of keys in DynaKV
func (*KV2) Put ¶
Put Returns the number of writes since the last compress, and an err if the put failed
type KVShard ¶
func NewKVShard ¶
func NewKVShard(directory string, offsetsCnt, keyLimit uint64, MaxCachedBlocks int) (kvs *KVShard, err error)
NewKVShard Create a new KVShard database. This database creates database shards to reduce the overhead of compressing large database files.
func OpenKVShard ¶
OpenKVShard Open an existing KVShard Database
func (*KVShard) GetDyna ¶
GetDyna Find the right shard, and extract the value from the DynaKV in the shard
func (*KVShard) GetPerm ¶
GetPerm Find the right shard, and extract the value from the PermKV in the shard
func (*KVShard) PutDyna ¶
PutDyna Find the right shard, and put the key/value in the DynaKV in the shard
type KVView ¶
type KVView struct { DB *KVShard // The underlying DB ViewID int // The next ViewID ActiveViews []*View // List of all active Views, newest first Map map[int]View // Fast lookup of a view Timeout time.Duration // How long before views timeout; every access resets timeout OffsetCnt uint64 // KeyOffset number for the key files KeyLimit uint64 // KeyLimit sets when to move keys to History }
KVView A wrapper around a sharded DB that implements Views. Views are created in a stack. More recent views lookup values in their cache, and then in the views that come before. Later views are ignored.
func NewShardDBViews ¶
func OpenShardDBViews ¶
func (*KVView) GetViewIndex ¶
GetViewIndex Returns the view index for a view. Returns 0 if view is closed.
func (*KVView) IsViewActive ¶
Active Views Returns true if a valid active view exists. If old views exist, but none are active, the active views are tossed.
type KeySet ¶
type KeySet struct { OffsetIndex uint64 // Offset Order (enables KeySet Index -> Offset Index) KeySetIndex uint64 // KeySet Order (enables Offset Index -> KeySet Index) Start uint64 // offset to the start of KeySet End uint64 // offset to the first entry after the KeySet }
KeySet The starting offset and ending offset for each KeySet Start points to the next entry in the KeySet. End points to the entry after the last entry in the KeySet.
If Start == End then the KeySet is empty.
type View ¶
type View struct { ID int // ID of a view KeyValues map[[32]byte][]byte // key value pairs LastAccess time.Time // time that the view was created Closed bool // true if the View is closed KVView *KVView // The KV database with views }
View Struct Views allow one to grab a point in time in the database, and query values that were in the DB at that time (writes after creating a view do not impact the view)