mirror of
https://github.com/PeernetOfficial/Abstraction.git
synced 2026-07-21 12:57:50 +01:00
added example package
This commit is contained in:
2
vendor/github.com/akrylysov/pogreb/.gitignore
generated
vendored
Normal file
2
vendor/github.com/akrylysov/pogreb/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/test.db
|
||||
/fs/test
|
||||
55
vendor/github.com/akrylysov/pogreb/CHANGELOG.md
generated
vendored
Normal file
55
vendor/github.com/akrylysov/pogreb/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# Changelog
|
||||
|
||||
## [0.10.1] - 2021-05-01
|
||||
### Changed
|
||||
- Improve error reporting.
|
||||
### Fixed
|
||||
- Fix compilation for 32-bit OS.
|
||||
|
||||
## [0.10.0] - 2021-02-09
|
||||
### Added
|
||||
- Memory-mapped file access can now be disabled by setting `Options.FileSystem` to `fs.OS`.
|
||||
### Changed
|
||||
- The default file system implementation is changed to `fs.OSMMap`.
|
||||
|
||||
## [0.9.2] - 2021-01-01
|
||||
### Changed
|
||||
- Write-ahead log doesn't rely on wall-clock time anymore. It prevents potential race conditions during compaction and recovery.
|
||||
### Fixed
|
||||
- Fix recovery writing extra delete records.
|
||||
|
||||
## [0.9.1] - 2020-04-03
|
||||
### Changed
|
||||
- Improve Go 1.14 compatibility (remove "unsafe" usage).
|
||||
|
||||
## [0.9.0] - 2020-03-08
|
||||
### Changed
|
||||
- Replace the unstructured data file for storing key-value pairs with a write-ahead log.
|
||||
### Added
|
||||
- In the event of a crash or a power loss the database is automatically recovered.
|
||||
- Optional background compaction allows reclaiming disk space occupied by overwritten or deleted keys.
|
||||
### Fixed
|
||||
- Fix disk space overhead when storing small keys and values.
|
||||
|
||||
## [0.8.3] - 2019-11-03
|
||||
### Fixed
|
||||
- Fix slice bounds out of range error mapping files on Windows.
|
||||
|
||||
## [0.8.2] - 2019-09-04
|
||||
### Fixed
|
||||
- Race condition could lead to data corruption.
|
||||
|
||||
## [0.8.1] - 2019-06-30
|
||||
### Fixed
|
||||
- Fix panic when accessing closed database.
|
||||
- Return error opening invalid database.
|
||||
|
||||
## [0.8] - 2019-03-30
|
||||
### Changed
|
||||
- ~2x write performance improvement on non-Windows.
|
||||
|
||||
## [0.7] - 2019-03-23
|
||||
### Added
|
||||
- Windows support (@mattn).
|
||||
### Changed
|
||||
- Improve freelist performance.
|
||||
201
vendor/github.com/akrylysov/pogreb/LICENSE
generated
vendored
Normal file
201
vendor/github.com/akrylysov/pogreb/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
102
vendor/github.com/akrylysov/pogreb/README.md
generated
vendored
Normal file
102
vendor/github.com/akrylysov/pogreb/README.md
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
<p align="center"><img src="https://akrylysov.github.io/pogreb/logo.svg" width="300"></p>
|
||||
|
||||
# Pogreb
|
||||
[](https://pkg.go.dev/github.com/akrylysov/pogreb)
|
||||
[](https://github.com/akrylysov/pogreb/actions)
|
||||
[](https://goreportcard.com/report/github.com/akrylysov/pogreb)
|
||||
[](https://codecov.io/gh/akrylysov/pogreb)
|
||||
|
||||
Pogreb is an embedded key-value store for read-heavy workloads written in Go.
|
||||
|
||||
## Key characteristics
|
||||
|
||||
- 100% Go.
|
||||
- Optimized for fast random lookups and infrequent bulk inserts.
|
||||
- Can store larger-than-memory data sets.
|
||||
- Low memory usage.
|
||||
- All DB methods are safe for concurrent use by multiple goroutines.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/akrylysov/pogreb
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Opening a database
|
||||
|
||||
To open or create a new database, use the `pogreb.Open()` function:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/akrylysov/pogreb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := pogreb.Open("pogreb.test", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
}
|
||||
```
|
||||
|
||||
### Writing to a database
|
||||
|
||||
Use the `DB.Put()` function to insert a new key-value pair:
|
||||
|
||||
```go
|
||||
err := db.Put([]byte("testKey"), []byte("testValue"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Reading from a database
|
||||
|
||||
To retrieve the inserted value, use the `DB.Get()` function:
|
||||
|
||||
```go
|
||||
val, err := db.Get([]byte("testKey"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("%s", val)
|
||||
```
|
||||
|
||||
### Iterating over items
|
||||
|
||||
To iterate over items, use `ItemIterator` returned by `DB.Items()`:
|
||||
|
||||
```go
|
||||
it := db.Items()
|
||||
for {
|
||||
key, val, err := it.Next()
|
||||
if err == pogreb.ErrIterationDone {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("%s %s", key, val)
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
The benchmarking code can be found in the [pogreb-bench](https://github.com/akrylysov/pogreb-bench) repository.
|
||||
|
||||
Results of read performance benchmark of pogreb, goleveldb, bolt and badgerdb
|
||||
on DigitalOcean 8 CPUs / 16 GB RAM / 160 GB SSD + Ubuntu 16.04.3 (higher is better):
|
||||
|
||||
<p align="center"><img src="https://akrylysov.github.io/pogreb/read-bench.png" width="609"></p>
|
||||
|
||||
## Internals
|
||||
|
||||
[Design document](/docs/design.md).
|
||||
15
vendor/github.com/akrylysov/pogreb/appveyor.yml
generated
vendored
Normal file
15
vendor/github.com/akrylysov/pogreb/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
version: "{build}"
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\akrylysov/pogreb
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
|
||||
install:
|
||||
- echo %PATH%
|
||||
- echo %GOPATH%
|
||||
- go version
|
||||
- go env
|
||||
|
||||
build_script:
|
||||
- go test -v ./...
|
||||
126
vendor/github.com/akrylysov/pogreb/bucket.go
generated
vendored
Normal file
126
vendor/github.com/akrylysov/pogreb/bucket.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
bucketSize = 512
|
||||
slotsPerBucket = 31 // Maximum number of slots possible to fit in a 512-byte bucket.
|
||||
)
|
||||
|
||||
// slot corresponds to a single item in the hash table.
|
||||
type slot struct {
|
||||
hash uint32
|
||||
segmentID uint16
|
||||
keySize uint16
|
||||
valueSize uint32
|
||||
offset uint32 // Offset of the record in a segment.
|
||||
}
|
||||
|
||||
func (sl slot) kvSize() uint32 {
|
||||
return uint32(sl.keySize) + sl.valueSize
|
||||
}
|
||||
|
||||
// bucket is an array of slots.
|
||||
type bucket struct {
|
||||
slots [slotsPerBucket]slot
|
||||
next int64 // Offset of overflow bucket.
|
||||
}
|
||||
|
||||
// bucketHandle is a bucket, plus its offset and the file it's written to.
|
||||
type bucketHandle struct {
|
||||
bucket
|
||||
file *file
|
||||
offset int64
|
||||
}
|
||||
|
||||
func (b bucket) MarshalBinary() ([]byte, error) {
|
||||
buf := make([]byte, bucketSize)
|
||||
data := buf
|
||||
for i := 0; i < slotsPerBucket; i++ {
|
||||
sl := b.slots[i]
|
||||
binary.LittleEndian.PutUint32(buf[:4], sl.hash)
|
||||
binary.LittleEndian.PutUint16(buf[4:6], sl.segmentID)
|
||||
binary.LittleEndian.PutUint16(buf[6:8], sl.keySize)
|
||||
binary.LittleEndian.PutUint32(buf[8:12], sl.valueSize)
|
||||
binary.LittleEndian.PutUint32(buf[12:16], sl.offset)
|
||||
buf = buf[16:]
|
||||
}
|
||||
binary.LittleEndian.PutUint64(buf[:8], uint64(b.next))
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (b *bucket) UnmarshalBinary(data []byte) error {
|
||||
for i := 0; i < slotsPerBucket; i++ {
|
||||
_ = data[16] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
b.slots[i].hash = binary.LittleEndian.Uint32(data[:4])
|
||||
b.slots[i].segmentID = binary.LittleEndian.Uint16(data[4:6])
|
||||
b.slots[i].keySize = binary.LittleEndian.Uint16(data[6:8])
|
||||
b.slots[i].valueSize = binary.LittleEndian.Uint32(data[8:12])
|
||||
b.slots[i].offset = binary.LittleEndian.Uint32(data[12:16])
|
||||
data = data[16:]
|
||||
}
|
||||
b.next = int64(binary.LittleEndian.Uint64(data[:8]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bucket) del(slotIdx int) {
|
||||
i := slotIdx
|
||||
// Shift slots.
|
||||
for ; i < slotsPerBucket-1; i++ {
|
||||
b.slots[i] = b.slots[i+1]
|
||||
}
|
||||
b.slots[i] = slot{}
|
||||
}
|
||||
|
||||
func (b *bucketHandle) read() error {
|
||||
buf, err := b.file.Slice(b.offset, b.offset+int64(bucketSize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.UnmarshalBinary(buf)
|
||||
}
|
||||
|
||||
func (b *bucketHandle) write() error {
|
||||
buf, err := b.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = b.file.WriteAt(buf, b.offset)
|
||||
return err
|
||||
}
|
||||
|
||||
// slotWriter inserts and writes slots into a bucket.
|
||||
type slotWriter struct {
|
||||
bucket *bucketHandle
|
||||
slotIdx int
|
||||
prevBuckets []*bucketHandle
|
||||
}
|
||||
|
||||
func (sw *slotWriter) insert(sl slot, idx *index) error {
|
||||
if sw.slotIdx == slotsPerBucket {
|
||||
// Bucket is full, create a new overflow bucket.
|
||||
nextBucket, err := idx.createOverflowBucket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sw.bucket.next = nextBucket.offset
|
||||
sw.prevBuckets = append(sw.prevBuckets, sw.bucket)
|
||||
sw.bucket = nextBucket
|
||||
sw.slotIdx = 0
|
||||
}
|
||||
sw.bucket.slots[sw.slotIdx] = sl
|
||||
sw.slotIdx++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sw *slotWriter) write() error {
|
||||
// Write previous buckets first.
|
||||
for i := len(sw.prevBuckets) - 1; i >= 0; i-- {
|
||||
if err := sw.prevBuckets[i].write(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return sw.bucket.write()
|
||||
}
|
||||
160
vendor/github.com/akrylysov/pogreb/compaction.go
generated
vendored
Normal file
160
vendor/github.com/akrylysov/pogreb/compaction.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/akrylysov/pogreb/internal/errors"
|
||||
)
|
||||
|
||||
// promoteRecord writes the record to the current segment if the index still points to the record.
|
||||
// Otherwise it discards the record.
|
||||
func (db *DB) promoteRecord(rec record) (bool, error) {
|
||||
hash := db.hash(rec.key)
|
||||
it := db.index.newBucketIterator(db.index.bucketIndex(hash))
|
||||
for {
|
||||
b, err := it.next()
|
||||
if err == ErrIterationDone {
|
||||
// Exhausted all buckets and the slot wasn't found.
|
||||
// The key was deleted or overwritten. The record is safe to discard.
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for i := 0; i < slotsPerBucket; i++ {
|
||||
sl := b.slots[i]
|
||||
|
||||
// No more slots in the bucket.
|
||||
if sl.offset == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Slot points to a different record.
|
||||
if hash != sl.hash || rec.offset != sl.offset || rec.segmentID != sl.segmentID {
|
||||
continue
|
||||
}
|
||||
|
||||
// The record is in the index, write it to the current segment.
|
||||
segmentID, offset, err := db.datalog.writeRecord(rec.data, rec.rtype) // TODO: batch writes
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Update index.
|
||||
b.slots[i].segmentID = segmentID
|
||||
b.slots[i].offset = offset
|
||||
return false, b.write()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CompactionResult holds the compaction result.
|
||||
type CompactionResult struct {
|
||||
CompactedSegments int
|
||||
ReclaimedRecords int
|
||||
ReclaimedBytes int
|
||||
}
|
||||
|
||||
func (db *DB) compact(sourceSeg *segment) (CompactionResult, error) {
|
||||
cr := CompactionResult{}
|
||||
|
||||
db.mu.Lock()
|
||||
sourceSeg.meta.Full = true // Prevent writes to the compacted file.
|
||||
db.mu.Unlock()
|
||||
|
||||
it, err := newSegmentIterator(sourceSeg)
|
||||
if err != nil {
|
||||
return cr, err
|
||||
}
|
||||
// Copy records from sourceSeg to the current segment.
|
||||
for {
|
||||
err := func() error {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
rec, err := it.next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rec.rtype == recordTypeDelete {
|
||||
cr.ReclaimedRecords++
|
||||
cr.ReclaimedBytes += len(rec.data)
|
||||
return nil
|
||||
}
|
||||
reclaimed, err := db.promoteRecord(rec)
|
||||
if reclaimed {
|
||||
cr.ReclaimedRecords++
|
||||
cr.ReclaimedBytes += len(rec.data)
|
||||
}
|
||||
return err
|
||||
}()
|
||||
if err == ErrIterationDone {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return cr, err
|
||||
}
|
||||
}
|
||||
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
err = db.datalog.removeSegment(sourceSeg)
|
||||
return cr, err
|
||||
}
|
||||
|
||||
// pickForCompaction returns segments eligible for compaction.
|
||||
func (db *DB) pickForCompaction() []*segment {
|
||||
segments := db.datalog.segmentsBySequenceID()
|
||||
var picked []*segment
|
||||
for i := len(segments) - 1; i >= 0; i-- {
|
||||
seg := segments[i]
|
||||
|
||||
if uint32(seg.size) < db.opts.compactionMinSegmentSize {
|
||||
continue
|
||||
}
|
||||
|
||||
fragmentation := float32(seg.meta.DeletedBytes) / float32(seg.size)
|
||||
if fragmentation < db.opts.compactionMinFragmentation {
|
||||
continue
|
||||
}
|
||||
|
||||
if seg.meta.DeleteRecords > 0 {
|
||||
// Delete records can be discarded only when older segments contain no put records
|
||||
// for the corresponding keys.
|
||||
// All segments older than the segment eligible for compaction have to be compacted.
|
||||
return append(segments[:i+1], picked...)
|
||||
}
|
||||
|
||||
picked = append([]*segment{seg}, picked...)
|
||||
}
|
||||
return picked
|
||||
}
|
||||
|
||||
// Compact compacts the DB. Deleted and overwritten items are discarded.
|
||||
// Returns an error if compaction is already in progress.
|
||||
func (db *DB) Compact() (CompactionResult, error) {
|
||||
cr := CompactionResult{}
|
||||
|
||||
// Run only a single compaction at a time.
|
||||
if !atomic.CompareAndSwapInt32(&db.compactionRunning, 0, 1) {
|
||||
return cr, errBusy
|
||||
}
|
||||
defer func() {
|
||||
atomic.StoreInt32(&db.compactionRunning, 0)
|
||||
}()
|
||||
|
||||
db.mu.RLock()
|
||||
segments := db.pickForCompaction()
|
||||
db.mu.RUnlock()
|
||||
|
||||
for _, seg := range segments {
|
||||
segcr, err := db.compact(seg)
|
||||
if err != nil {
|
||||
return cr, errors.Wrapf(err, "compacting segment %s", seg.name)
|
||||
}
|
||||
cr.CompactedSegments++
|
||||
cr.ReclaimedRecords += segcr.ReclaimedRecords
|
||||
cr.ReclaimedBytes += segcr.ReclaimedBytes
|
||||
}
|
||||
|
||||
return cr, nil
|
||||
}
|
||||
261
vendor/github.com/akrylysov/pogreb/datalog.go
generated
vendored
Normal file
261
vendor/github.com/akrylysov/pogreb/datalog.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/akrylysov/pogreb/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
maxSegments = math.MaxInt16
|
||||
)
|
||||
|
||||
// datalog is a write-ahead log.
|
||||
type datalog struct {
|
||||
opts *Options
|
||||
curSeg *segment
|
||||
segments [maxSegments]*segment
|
||||
maxSequenceID uint64
|
||||
}
|
||||
|
||||
func openDatalog(opts *Options) (*datalog, error) {
|
||||
files, err := opts.FileSystem.ReadDir(".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dl := &datalog{
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
// Open existing segments.
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
ext := filepath.Ext(name)
|
||||
if ext != segmentExt {
|
||||
continue
|
||||
}
|
||||
id, seqID, err := parseSegmentName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seg, err := dl.openSegment(name, id, seqID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "opening segment %s", name)
|
||||
}
|
||||
if seg.sequenceID > dl.maxSequenceID {
|
||||
dl.maxSequenceID = seg.sequenceID
|
||||
}
|
||||
dl.segments[seg.id] = seg
|
||||
}
|
||||
|
||||
if err := dl.swapSegment(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dl, nil
|
||||
}
|
||||
|
||||
func parseSegmentName(name string) (uint16, uint64, error) {
|
||||
parts := strings.SplitN(strings.TrimSuffix(name, segmentExt), "-", 2)
|
||||
id, err := strconv.ParseUint(parts[0], 10, 16)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
var seqID uint64
|
||||
if len(parts) == 2 {
|
||||
seqID, err = strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
return uint16(id), seqID, nil
|
||||
}
|
||||
|
||||
func (dl *datalog) openSegment(name string, id uint16, seqID uint64) (*segment, error) {
|
||||
f, err := openFile(dl.opts.FileSystem, name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta := &segmentMeta{}
|
||||
if !f.empty() {
|
||||
metaName := name + metaExt
|
||||
if err := readGobFile(dl.opts.FileSystem, metaName, &meta); err != nil {
|
||||
logger.Printf("error reading segment meta %d: %v", id, err)
|
||||
// TODO: rebuild meta?
|
||||
}
|
||||
}
|
||||
|
||||
seg := &segment{
|
||||
file: f,
|
||||
id: id,
|
||||
sequenceID: seqID,
|
||||
name: name,
|
||||
meta: meta,
|
||||
}
|
||||
|
||||
return seg, nil
|
||||
}
|
||||
|
||||
func (dl *datalog) nextWritableSegmentID() (uint16, uint64, error) {
|
||||
for id, seg := range dl.segments {
|
||||
// Pick empty segment.
|
||||
if seg == nil {
|
||||
dl.maxSequenceID++
|
||||
return uint16(id), dl.maxSequenceID, nil
|
||||
}
|
||||
}
|
||||
return 0, 0, fmt.Errorf("number of segments exceeds %d", maxSegments)
|
||||
}
|
||||
|
||||
func (dl *datalog) swapSegment() error {
|
||||
// Pick unfilled segment.
|
||||
for _, seg := range dl.segments {
|
||||
if seg != nil && !seg.meta.Full {
|
||||
dl.curSeg = seg
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create new segment.
|
||||
id, seqID, err := dl.nextWritableSegmentID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := segmentName(id, seqID)
|
||||
seg, err := dl.openSegment(name, id, seqID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dl.segments[id] = seg
|
||||
dl.curSeg = seg
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dl *datalog) removeSegment(seg *segment) error {
|
||||
dl.segments[seg.id] = nil
|
||||
|
||||
if err := seg.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove segment meta from FS.
|
||||
metaName := seg.name + segmentExt
|
||||
if err := dl.opts.FileSystem.Remove(metaName); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove segment from FS.
|
||||
if err := dl.opts.FileSystem.Remove(seg.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dl *datalog) readKeyValue(sl slot) ([]byte, []byte, error) {
|
||||
off := int64(sl.offset) + 6 // Skip key size and value size.
|
||||
seg := dl.segments[sl.segmentID]
|
||||
keyValue, err := seg.Slice(off, off+int64(sl.kvSize()))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return keyValue[:sl.keySize], keyValue[sl.keySize:], nil
|
||||
}
|
||||
|
||||
func (dl *datalog) readKey(sl slot) ([]byte, error) {
|
||||
off := int64(sl.offset) + 6
|
||||
seg := dl.segments[sl.segmentID]
|
||||
return seg.Slice(off, off+int64(sl.keySize))
|
||||
}
|
||||
|
||||
// trackDel updates segment's metadata for deleted or overwritten items.
|
||||
func (dl *datalog) trackDel(sl slot) {
|
||||
meta := dl.segments[sl.segmentID].meta
|
||||
meta.DeletedKeys++
|
||||
meta.DeletedBytes += encodedRecordSize(sl.kvSize())
|
||||
}
|
||||
|
||||
func (dl *datalog) del(key []byte) error {
|
||||
rec := encodeDeleteRecord(key)
|
||||
_, _, err := dl.writeRecord(rec, recordTypeDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Compaction removes delete records, increment DeletedBytes.
|
||||
dl.curSeg.meta.DeletedBytes += uint32(len(rec))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dl *datalog) writeRecord(data []byte, rt recordType) (uint16, uint32, error) {
|
||||
if dl.curSeg.meta.Full || dl.curSeg.size+int64(len(data)) > int64(dl.opts.maxSegmentSize) {
|
||||
// Current segment is full, create a new one.
|
||||
dl.curSeg.meta.Full = true
|
||||
if err := dl.swapSegment(); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
off, err := dl.curSeg.append(data)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
switch rt {
|
||||
case recordTypePut:
|
||||
dl.curSeg.meta.PutRecords++
|
||||
case recordTypeDelete:
|
||||
dl.curSeg.meta.DeleteRecords++
|
||||
}
|
||||
return dl.curSeg.id, uint32(off), nil
|
||||
}
|
||||
|
||||
func (dl *datalog) put(key []byte, value []byte) (uint16, uint32, error) {
|
||||
return dl.writeRecord(encodePutRecord(key, value), recordTypePut)
|
||||
}
|
||||
|
||||
func (dl *datalog) sync() error {
|
||||
return dl.curSeg.Sync()
|
||||
}
|
||||
|
||||
func (dl *datalog) close() error {
|
||||
for _, seg := range dl.segments {
|
||||
if seg == nil {
|
||||
continue
|
||||
}
|
||||
if err := seg.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
metaName := seg.name + metaExt
|
||||
if err := writeGobFile(dl.opts.FileSystem, metaName, seg.meta); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// segmentsBySequenceID returns segments ordered from oldest to newest.
|
||||
func (dl *datalog) segmentsBySequenceID() []*segment {
|
||||
var segments []*segment
|
||||
|
||||
for _, seg := range dl.segments {
|
||||
if seg == nil {
|
||||
continue
|
||||
}
|
||||
segments = append(segments, seg)
|
||||
}
|
||||
|
||||
sort.SliceStable(segments, func(i, j int) bool {
|
||||
return segments[i].sequenceID < segments[j].sequenceID
|
||||
})
|
||||
|
||||
return segments
|
||||
}
|
||||
403
vendor/github.com/akrylysov/pogreb/db.go
generated
vendored
Normal file
403
vendor/github.com/akrylysov/pogreb/db.go
generated
vendored
Normal file
@@ -0,0 +1,403 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/akrylysov/pogreb/fs"
|
||||
"github.com/akrylysov/pogreb/internal/errors"
|
||||
"github.com/akrylysov/pogreb/internal/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxKeyLength is the maximum size of a key in bytes.
|
||||
MaxKeyLength = math.MaxUint16
|
||||
|
||||
// MaxValueLength is the maximum size of a value in bytes.
|
||||
MaxValueLength = 512 << 20 // 512 MiB
|
||||
|
||||
// MaxKeys is the maximum numbers of keys in the DB.
|
||||
MaxKeys = math.MaxUint32
|
||||
|
||||
metaExt = ".pmt"
|
||||
dbMetaName = "db" + metaExt
|
||||
)
|
||||
|
||||
// DB represents the key-value storage.
|
||||
// All DB methods are safe for concurrent use by multiple goroutines.
|
||||
type DB struct {
|
||||
mu sync.RWMutex // Allows multiple database readers or a single writer.
|
||||
opts *Options
|
||||
index *index
|
||||
datalog *datalog
|
||||
lock fs.LockFile // Prevents opening multiple instances of the same database.
|
||||
hashSeed uint32
|
||||
metrics *Metrics
|
||||
syncWrites bool
|
||||
cancelBgWorker context.CancelFunc
|
||||
closeWg sync.WaitGroup
|
||||
compactionRunning int32 // Prevents running compactions concurrently.
|
||||
}
|
||||
|
||||
type dbMeta struct {
|
||||
HashSeed uint32
|
||||
}
|
||||
|
||||
// Open opens or creates a new DB.
|
||||
// The DB must be closed after use, by calling Close method.
|
||||
func Open(path string, opts *Options) (*DB, error) {
|
||||
opts = opts.copyWithDefaults(path)
|
||||
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try to acquire a file lock.
|
||||
lock, acquiredExistingLock, err := createLockFile(opts)
|
||||
if err != nil {
|
||||
if err == os.ErrExist {
|
||||
err = errLocked
|
||||
}
|
||||
return nil, errors.Wrap(err, "creating lock file")
|
||||
}
|
||||
clean := lock.Unlock
|
||||
defer func() {
|
||||
if clean != nil {
|
||||
_ = clean()
|
||||
}
|
||||
}()
|
||||
|
||||
if acquiredExistingLock {
|
||||
// Lock file already existed, but the process managed to acquire it.
|
||||
// It means the database wasn't closed properly.
|
||||
// Start recovery process.
|
||||
if err := backupNonsegmentFiles(opts.FileSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
index, err := openIndex(opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "opening index")
|
||||
}
|
||||
|
||||
datalog, err := openDatalog(opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "opening datalog")
|
||||
}
|
||||
|
||||
db := &DB{
|
||||
opts: opts,
|
||||
index: index,
|
||||
datalog: datalog,
|
||||
lock: lock,
|
||||
metrics: &Metrics{},
|
||||
syncWrites: opts.BackgroundSyncInterval == -1,
|
||||
}
|
||||
if index.count() == 0 {
|
||||
// The index is empty, make a new hash seed.
|
||||
seed, err := hash.RandSeed()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.hashSeed = seed
|
||||
} else {
|
||||
if err := db.readMeta(); err != nil {
|
||||
return nil, errors.Wrap(err, "reading db meta")
|
||||
}
|
||||
}
|
||||
|
||||
if acquiredExistingLock {
|
||||
if err := db.recover(); err != nil {
|
||||
return nil, errors.Wrap(err, "recovering")
|
||||
}
|
||||
}
|
||||
|
||||
if db.opts.BackgroundSyncInterval > 0 || db.opts.BackgroundCompactionInterval > 0 {
|
||||
db.startBackgroundWorker()
|
||||
}
|
||||
|
||||
clean = nil
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func cloneBytes(src []byte) []byte {
|
||||
dst := make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (db *DB) writeMeta() error {
|
||||
m := dbMeta{
|
||||
HashSeed: db.hashSeed,
|
||||
}
|
||||
return writeGobFile(db.opts.FileSystem, dbMetaName, m)
|
||||
}
|
||||
|
||||
func (db *DB) readMeta() error {
|
||||
m := dbMeta{}
|
||||
if err := readGobFile(db.opts.FileSystem, dbMetaName, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
db.hashSeed = m.HashSeed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) hash(data []byte) uint32 {
|
||||
return hash.Sum32WithSeed(data, db.hashSeed)
|
||||
}
|
||||
|
||||
// newNullableTicker is a wrapper around time.NewTicker that allows creating a nil ticker.
|
||||
// A nil ticker never ticks.
|
||||
func newNullableTicker(d time.Duration) (<-chan time.Time, func()) {
|
||||
if d > 0 {
|
||||
t := time.NewTicker(d)
|
||||
return t.C, t.Stop
|
||||
}
|
||||
return nil, func() {}
|
||||
}
|
||||
|
||||
func (db *DB) startBackgroundWorker() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
db.cancelBgWorker = cancel
|
||||
db.closeWg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer db.closeWg.Done()
|
||||
|
||||
syncC, syncStop := newNullableTicker(db.opts.BackgroundSyncInterval)
|
||||
defer syncStop()
|
||||
|
||||
compactC, compactStop := newNullableTicker(db.opts.BackgroundCompactionInterval)
|
||||
defer compactStop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-syncC:
|
||||
if err := db.Sync(); err != nil {
|
||||
logger.Printf("error synchronizing database: %v", err)
|
||||
}
|
||||
case <-compactC:
|
||||
if cr, err := db.Compact(); err != nil {
|
||||
logger.Printf("error compacting database: %v", err)
|
||||
} else if cr.CompactedSegments > 0 {
|
||||
logger.Printf("compacted database: %+v", cr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Get returns the value for the given key stored in the DB or nil if the key doesn't exist.
|
||||
func (db *DB) Get(key []byte) ([]byte, error) {
|
||||
h := db.hash(key)
|
||||
db.metrics.Gets.Add(1)
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
var retValue []byte
|
||||
err := db.index.get(h, func(sl slot) (bool, error) {
|
||||
if uint16(len(key)) != sl.keySize {
|
||||
return false, nil
|
||||
}
|
||||
slKey, value, err := db.datalog.readKeyValue(sl)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if bytes.Equal(key, slKey) {
|
||||
retValue = cloneBytes(value)
|
||||
return true, nil
|
||||
}
|
||||
db.metrics.HashCollisions.Add(1)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retValue, nil
|
||||
}
|
||||
|
||||
// Has returns true if the DB contains the given key.
|
||||
func (db *DB) Has(key []byte) (bool, error) {
|
||||
h := db.hash(key)
|
||||
found := false
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
err := db.index.get(h, func(sl slot) (bool, error) {
|
||||
if uint16(len(key)) != sl.keySize {
|
||||
return false, nil
|
||||
}
|
||||
slKey, err := db.datalog.readKey(sl)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if bytes.Equal(key, slKey) {
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return found, nil
|
||||
}
|
||||
|
||||
func (db *DB) put(sl slot, key []byte) error {
|
||||
return db.index.put(sl, func(cursl slot) (bool, error) {
|
||||
if uint16(len(key)) != cursl.keySize {
|
||||
return false, nil
|
||||
}
|
||||
slKey, err := db.datalog.readKey(cursl)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if bytes.Equal(key, slKey) {
|
||||
db.datalog.trackDel(cursl) // Overwriting existing key.
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Put sets the value for the given key. It updates the value for the existing key.
|
||||
func (db *DB) Put(key []byte, value []byte) error {
|
||||
if len(key) > MaxKeyLength {
|
||||
return errKeyTooLarge
|
||||
}
|
||||
if len(value) > MaxValueLength {
|
||||
return errValueTooLarge
|
||||
}
|
||||
h := db.hash(key)
|
||||
db.metrics.Puts.Add(1)
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
segID, offset, err := db.datalog.put(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sl := slot{
|
||||
hash: h,
|
||||
segmentID: segID,
|
||||
keySize: uint16(len(key)),
|
||||
valueSize: uint32(len(value)),
|
||||
offset: offset,
|
||||
}
|
||||
|
||||
if err := db.put(sl, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if db.syncWrites {
|
||||
return db.sync()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) del(h uint32, key []byte, writeWAL bool) error {
|
||||
err := db.index.delete(h, func(sl slot) (b bool, e error) {
|
||||
if uint16(len(key)) != sl.keySize {
|
||||
return false, nil
|
||||
}
|
||||
slKey, err := db.datalog.readKey(sl)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if bytes.Equal(key, slKey) {
|
||||
db.datalog.trackDel(sl)
|
||||
var err error
|
||||
if writeWAL {
|
||||
err = db.datalog.del(key)
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the DB.
|
||||
func (db *DB) Delete(key []byte) error {
|
||||
h := db.hash(key)
|
||||
db.metrics.Dels.Add(1)
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
if err := db.del(h, key, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if db.syncWrites {
|
||||
return db.sync()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the DB.
|
||||
func (db *DB) Close() error {
|
||||
if db.cancelBgWorker != nil {
|
||||
db.cancelBgWorker()
|
||||
}
|
||||
db.closeWg.Wait()
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
if err := db.writeMeta(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.datalog.close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.index.close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.lock.Unlock(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) sync() error {
|
||||
return db.datalog.sync()
|
||||
}
|
||||
|
||||
// Items returns a new ItemIterator.
|
||||
func (db *DB) Items() *ItemIterator {
|
||||
return &ItemIterator{db: db}
|
||||
}
|
||||
|
||||
// Sync commits the contents of the database to the backing FileSystem.
|
||||
func (db *DB) Sync() error {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
return db.sync()
|
||||
}
|
||||
|
||||
// Count returns the number of keys in the DB.
|
||||
func (db *DB) Count() uint32 {
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
return db.index.count()
|
||||
}
|
||||
|
||||
// Metrics returns the DB metrics.
|
||||
func (db *DB) Metrics() *Metrics {
|
||||
return db.metrics
|
||||
}
|
||||
|
||||
// FileSize returns the total size of the disk storage used by the DB.
|
||||
func (db *DB) FileSize() (int64, error) {
|
||||
var size int64
|
||||
files, err := db.opts.FileSystem.ReadDir(".")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, file := range files {
|
||||
size += file.Size()
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
4
vendor/github.com/akrylysov/pogreb/doc.go
generated
vendored
Normal file
4
vendor/github.com/akrylysov/pogreb/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package pogreb implements an embedded key-value store for read-heavy workloads.
|
||||
*/
|
||||
package pogreb
|
||||
14
vendor/github.com/akrylysov/pogreb/errors.go
generated
vendored
Normal file
14
vendor/github.com/akrylysov/pogreb/errors.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"github.com/akrylysov/pogreb/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errKeyTooLarge = errors.New("key is too large")
|
||||
errValueTooLarge = errors.New("value is too large")
|
||||
errFull = errors.New("database is full")
|
||||
errCorrupted = errors.New("database is corrupted")
|
||||
errLocked = errors.New("database is locked")
|
||||
errBusy = errors.New("database is busy")
|
||||
)
|
||||
97
vendor/github.com/akrylysov/pogreb/file.go
generated
vendored
Normal file
97
vendor/github.com/akrylysov/pogreb/file.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/akrylysov/pogreb/fs"
|
||||
)
|
||||
|
||||
// file is a database file.
|
||||
// When stored in a file system, the file starts with a header.
|
||||
type file struct {
|
||||
fs.File
|
||||
size int64
|
||||
}
|
||||
|
||||
func openFile(fsyst fs.FileSystem, name string, truncate bool) (*file, error) {
|
||||
flag := os.O_CREATE | os.O_RDWR
|
||||
if truncate {
|
||||
flag |= os.O_TRUNC
|
||||
}
|
||||
fi, err := fsyst.OpenFile(name, flag, os.FileMode(0640))
|
||||
f := &file{}
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
clean := fi.Close
|
||||
defer func() {
|
||||
if clean != nil {
|
||||
_ = clean()
|
||||
}
|
||||
}()
|
||||
f.File = fi
|
||||
stat, err := fi.Stat()
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
f.size = stat.Size()
|
||||
if f.size == 0 {
|
||||
// It's a new file - write header.
|
||||
if err := f.writeHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := f.readHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err := f.Seek(int64(headerSize), io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clean = nil
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *file) writeHeader() error {
|
||||
h := newHeader()
|
||||
data, err := h.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = f.append(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *file) readHeader() error {
|
||||
h := &header{}
|
||||
buf := make([]byte, headerSize)
|
||||
if _, err := io.ReadFull(f, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return h.UnmarshalBinary(buf)
|
||||
}
|
||||
|
||||
func (f *file) empty() bool {
|
||||
return f.size == int64(headerSize)
|
||||
}
|
||||
|
||||
func (f *file) extend(size uint32) (int64, error) {
|
||||
off := f.size
|
||||
if err := f.Truncate(off + int64(size)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.size += int64(size)
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (f *file) append(data []byte) (int64, error) {
|
||||
off := f.size
|
||||
if _, err := f.WriteAt(data, off); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.size += int64(len(data))
|
||||
return off, nil
|
||||
}
|
||||
63
vendor/github.com/akrylysov/pogreb/fs/fs.go
generated
vendored
Normal file
63
vendor/github.com/akrylysov/pogreb/fs/fs.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Package fs provides a file system interface.
|
||||
*/
|
||||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
errAppendModeNotSupported = errors.New("append mode is not supported")
|
||||
)
|
||||
|
||||
// File is the interface compatible with os.File.
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.ReaderAt
|
||||
io.Seeker
|
||||
io.Writer
|
||||
io.WriterAt
|
||||
|
||||
// Stat returns os.FileInfo describing the file.
|
||||
Stat() (os.FileInfo, error)
|
||||
|
||||
// Sync commits the current contents of the file.
|
||||
Sync() error
|
||||
|
||||
// Truncate changes the size of the file.
|
||||
Truncate(size int64) error
|
||||
|
||||
// Slice reads and returns the contents of file from offset start to offset end.
|
||||
Slice(start int64, end int64) ([]byte, error)
|
||||
}
|
||||
|
||||
// LockFile represents a lock file.
|
||||
type LockFile interface {
|
||||
// Unlock and removes the lock file.
|
||||
Unlock() error
|
||||
}
|
||||
|
||||
// FileSystem represents a file system.
|
||||
type FileSystem interface {
|
||||
// OpenFile opens the file with specified flag.
|
||||
OpenFile(name string, flag int, perm os.FileMode) (File, error)
|
||||
|
||||
// Stat returns os.FileInfo describing the file.
|
||||
Stat(name string) (os.FileInfo, error)
|
||||
|
||||
// Remove removes the file.
|
||||
Remove(name string) error
|
||||
|
||||
// Rename renames oldpath to newpath.
|
||||
Rename(oldpath, newpath string) error
|
||||
|
||||
// ReadDir reads the directory and returns a list of directory entries.
|
||||
ReadDir(name string) ([]os.FileInfo, error)
|
||||
|
||||
// CreateLockFile creates a lock file.
|
||||
CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error)
|
||||
}
|
||||
234
vendor/github.com/akrylysov/pogreb/fs/mem.go
generated
vendored
Normal file
234
vendor/github.com/akrylysov/pogreb/fs/mem.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type memFS struct {
|
||||
files map[string]*memFile
|
||||
}
|
||||
|
||||
// Mem is a file system backed by memory.
|
||||
var Mem FileSystem = &memFS{files: map[string]*memFile{}}
|
||||
|
||||
func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
if flag&os.O_APPEND != 0 {
|
||||
// memFS doesn't support opening files in append-only mode.
|
||||
// The database doesn't currently use O_APPEND.
|
||||
return nil, errAppendModeNotSupported
|
||||
}
|
||||
f := fs.files[name]
|
||||
if f == nil || (flag&os.O_TRUNC) != 0 {
|
||||
f = &memFile{
|
||||
name: name,
|
||||
perm: perm, // Perm is saved to return it in Mode, but don't do anything else with it yet.
|
||||
}
|
||||
fs.files[name] = f
|
||||
} else if !f.closed {
|
||||
return nil, os.ErrExist
|
||||
} else {
|
||||
f.offset = 0
|
||||
f.closed = false
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (fs *memFS) CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error) {
|
||||
_, exists := fs.files[name]
|
||||
_, err := fs.OpenFile(name, 0, perm)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return fs.files[name], exists, nil
|
||||
}
|
||||
|
||||
func (fs *memFS) Stat(name string) (os.FileInfo, error) {
|
||||
if f, ok := fs.files[name]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
func (fs *memFS) Remove(name string) error {
|
||||
if _, ok := fs.files[name]; ok {
|
||||
delete(fs.files, name)
|
||||
return nil
|
||||
}
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
func (fs *memFS) Rename(oldpath, newpath string) error {
|
||||
if f, ok := fs.files[oldpath]; ok {
|
||||
delete(fs.files, oldpath)
|
||||
fs.files[newpath] = f
|
||||
f.name = newpath
|
||||
return nil
|
||||
}
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
func (fs *memFS) ReadDir(dir string) ([]os.FileInfo, error) {
|
||||
dir = filepath.Clean(dir)
|
||||
var fis []os.FileInfo
|
||||
for name, f := range fs.files {
|
||||
if filepath.Dir(name) == dir {
|
||||
fis = append(fis, f)
|
||||
}
|
||||
}
|
||||
return fis, nil
|
||||
}
|
||||
|
||||
type memFile struct {
|
||||
name string
|
||||
perm os.FileMode
|
||||
buf []byte
|
||||
size int64
|
||||
offset int64
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (f *memFile) Close() error {
|
||||
if f.closed {
|
||||
return os.ErrClosed
|
||||
}
|
||||
f.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *memFile) Unlock() error {
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return Mem.Remove(f.name)
|
||||
}
|
||||
|
||||
func (f *memFile) ReadAt(p []byte, off int64) (int, error) {
|
||||
if f.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
if off >= f.size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := int64(len(p))
|
||||
if n > f.size-off {
|
||||
copy(p, f.buf[off:])
|
||||
return int(f.size - off), nil
|
||||
}
|
||||
copy(p, f.buf[off:off+n])
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func (f *memFile) Read(p []byte) (int, error) {
|
||||
n, err := f.ReadAt(p, f.offset)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
f.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (f *memFile) WriteAt(p []byte, off int64) (int, error) {
|
||||
if f.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
n := int64(len(p))
|
||||
if off+n > f.size {
|
||||
f.truncate(off + n)
|
||||
}
|
||||
copy(f.buf[off:off+n], p)
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func (f *memFile) Write(p []byte) (int, error) {
|
||||
n, err := f.WriteAt(p, f.offset)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
f.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (f *memFile) Seek(offset int64, whence int) (int64, error) {
|
||||
if f.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
switch whence {
|
||||
case io.SeekEnd:
|
||||
f.offset = f.size + offset
|
||||
case io.SeekStart:
|
||||
f.offset = offset
|
||||
case io.SeekCurrent:
|
||||
f.offset += offset
|
||||
}
|
||||
return f.offset, nil
|
||||
}
|
||||
|
||||
func (f *memFile) Stat() (os.FileInfo, error) {
|
||||
if f.closed {
|
||||
return f, os.ErrClosed
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *memFile) Sync() error {
|
||||
if f.closed {
|
||||
return os.ErrClosed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *memFile) truncate(size int64) {
|
||||
if size > f.size {
|
||||
diff := int(size - f.size)
|
||||
f.buf = append(f.buf, make([]byte, diff)...)
|
||||
} else {
|
||||
f.buf = f.buf[:size]
|
||||
}
|
||||
f.size = size
|
||||
}
|
||||
|
||||
func (f *memFile) Truncate(size int64) error {
|
||||
if f.closed {
|
||||
return os.ErrClosed
|
||||
}
|
||||
f.truncate(size)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *memFile) Name() string {
|
||||
_, name := filepath.Split(f.name)
|
||||
return name
|
||||
}
|
||||
|
||||
func (f *memFile) Size() int64 {
|
||||
return f.size
|
||||
}
|
||||
|
||||
func (f *memFile) Mode() os.FileMode {
|
||||
return f.perm
|
||||
}
|
||||
|
||||
func (f *memFile) ModTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (f *memFile) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *memFile) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *memFile) Slice(start int64, end int64) ([]byte, error) {
|
||||
if f.closed {
|
||||
return nil, os.ErrClosed
|
||||
}
|
||||
if end > f.size {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return f.buf[start:end], nil
|
||||
}
|
||||
64
vendor/github.com/akrylysov/pogreb/fs/os.go
generated
vendored
Normal file
64
vendor/github.com/akrylysov/pogreb/fs/os.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type osFS struct{}
|
||||
|
||||
// OS is a file system backed by the os package.
|
||||
var OS FileSystem = &osFS{}
|
||||
|
||||
func (fs *osFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
f, err := os.OpenFile(name, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &osFile{File: f}, nil
|
||||
}
|
||||
|
||||
func (fs *osFS) CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error) {
|
||||
return createLockFile(name, perm)
|
||||
}
|
||||
|
||||
func (fs *osFS) Stat(name string) (os.FileInfo, error) {
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
func (fs *osFS) Remove(name string) error {
|
||||
return os.Remove(name)
|
||||
}
|
||||
|
||||
func (fs *osFS) Rename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
func (fs *osFS) ReadDir(name string) ([]os.FileInfo, error) {
|
||||
return ioutil.ReadDir(name)
|
||||
}
|
||||
|
||||
type osFile struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (f *osFile) Slice(start int64, end int64) ([]byte, error) {
|
||||
buf := make([]byte, end-start)
|
||||
_, err := f.ReadAt(buf, start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
type osLockFile struct {
|
||||
*os.File
|
||||
path string
|
||||
}
|
||||
|
||||
func (f *osLockFile) Unlock() error {
|
||||
if err := os.Remove(f.path); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
163
vendor/github.com/akrylysov/pogreb/fs/os_mmap.go
generated
vendored
Normal file
163
vendor/github.com/akrylysov/pogreb/fs/os_mmap.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
initialMmapSize = 1024 << 20 // 1 GiB
|
||||
)
|
||||
|
||||
type osMMapFS struct {
|
||||
osFS
|
||||
}
|
||||
|
||||
// OSMMap is a file system backed by the os package and memory-mapped files.
|
||||
var OSMMap FileSystem = &osMMapFS{}
|
||||
|
||||
func (fs *osMMapFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
if flag&os.O_APPEND != 0 {
|
||||
// osMMapFS doesn't support opening files in append-only mode.
|
||||
// The database doesn't currently use O_APPEND.
|
||||
return nil, errAppendModeNotSupported
|
||||
}
|
||||
f, err := os.OpenFile(name, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mf := &osMMapFile{
|
||||
File: f,
|
||||
size: stat.Size(),
|
||||
}
|
||||
if err := mf.mremap(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mf, nil
|
||||
}
|
||||
|
||||
type osMMapFile struct {
|
||||
*os.File
|
||||
data []byte
|
||||
offset int64
|
||||
size int64
|
||||
mmapSize int64
|
||||
}
|
||||
|
||||
func (f *osMMapFile) WriteAt(p []byte, off int64) (int, error) {
|
||||
n, err := f.File.WriteAt(p, off)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writeOff := off + int64(n)
|
||||
if writeOff > f.size {
|
||||
f.size = writeOff
|
||||
}
|
||||
return n, f.mremap()
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Write(p []byte) (int, error) {
|
||||
n, err := f.File.Write(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.offset += int64(n)
|
||||
if f.offset > f.size {
|
||||
f.size = f.offset
|
||||
}
|
||||
return n, f.mremap()
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Seek(offset int64, whence int) (int64, error) {
|
||||
off, err := f.File.Seek(offset, whence)
|
||||
f.offset = off
|
||||
return off, err
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Read(p []byte) (int, error) {
|
||||
n, err := f.File.Read(p)
|
||||
f.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Slice(start int64, end int64) ([]byte, error) {
|
||||
if end > f.size {
|
||||
return nil, io.EOF
|
||||
}
|
||||
if f.data == nil {
|
||||
return nil, os.ErrClosed
|
||||
}
|
||||
return f.data[start:end], nil
|
||||
}
|
||||
|
||||
func (f *osMMapFile) munmap() error {
|
||||
if f.data == nil {
|
||||
return nil
|
||||
}
|
||||
if err := munmap(f.data); err != nil {
|
||||
return err
|
||||
}
|
||||
f.data = nil
|
||||
f.mmapSize = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *osMMapFile) mmap(fileSize int64, mappingSize int64) error {
|
||||
if f.data != nil {
|
||||
if err := munmap(f.data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := mmap(f.File, fileSize, mappingSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = madviceRandom(data)
|
||||
|
||||
f.data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *osMMapFile) mremap() error {
|
||||
mmapSize := f.mmapSize
|
||||
|
||||
if mmapSize >= f.size {
|
||||
return nil
|
||||
}
|
||||
|
||||
if mmapSize == 0 {
|
||||
mmapSize = initialMmapSize
|
||||
if mmapSize < f.size {
|
||||
mmapSize = f.size
|
||||
}
|
||||
} else {
|
||||
if err := f.munmap(); err != nil {
|
||||
return err
|
||||
}
|
||||
mmapSize *= 2
|
||||
}
|
||||
|
||||
if err := f.mmap(f.size, mmapSize); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// On Windows mmap may memory-map less than the requested size.
|
||||
f.mmapSize = int64(len(f.data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Close() error {
|
||||
if err := f.munmap(); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.File.Close()
|
||||
}
|
||||
34
vendor/github.com/akrylysov/pogreb/fs/os_mmap_unix.go
generated
vendored
Normal file
34
vendor/github.com/akrylysov/pogreb/fs/os_mmap_unix.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func mmap(f *os.File, fileSize int64, mappingSize int64) ([]byte, error) {
|
||||
p, err := syscall.Mmap(int(f.Fd()), 0, int(mappingSize), syscall.PROT_READ, syscall.MAP_SHARED)
|
||||
return p, err
|
||||
}
|
||||
|
||||
func munmap(data []byte) error {
|
||||
return syscall.Munmap(data)
|
||||
}
|
||||
|
||||
func madviceRandom(data []byte) error {
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&data[0])), uintptr(len(data)), uintptr(syscall.MADV_RANDOM))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Truncate(size int64) error {
|
||||
if err := f.File.Truncate(size); err != nil {
|
||||
return err
|
||||
}
|
||||
f.size = size
|
||||
return f.mremap()
|
||||
}
|
||||
45
vendor/github.com/akrylysov/pogreb/fs/os_mmap_windows.go
generated
vendored
Normal file
45
vendor/github.com/akrylysov/pogreb/fs/os_mmap_windows.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func mmap(f *os.File, fileSize int64, mappingSize int64) ([]byte, error) {
|
||||
size := fileSize
|
||||
low, high := uint32(size), uint32(size>>32)
|
||||
fmap, err := syscall.CreateFileMapping(syscall.Handle(f.Fd()), nil, syscall.PAGE_READONLY, high, low, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer syscall.CloseHandle(fmap)
|
||||
ptr, err := syscall.MapViewOfFile(fmap, syscall.FILE_MAP_READ, 0, 0, uintptr(size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := (*[maxMmapSize]byte)(unsafe.Pointer(ptr))[:size]
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func munmap(data []byte) error {
|
||||
return syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&data[0])))
|
||||
}
|
||||
|
||||
func madviceRandom(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *osMMapFile) Truncate(size int64) error {
|
||||
// Truncating a memory-mapped file fails on Windows. Unmap it first.
|
||||
if err := f.munmap(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.File.Truncate(size); err != nil {
|
||||
return err
|
||||
}
|
||||
f.size = size
|
||||
return f.mremap()
|
||||
}
|
||||
7
vendor/github.com/akrylysov/pogreb/fs/os_mmap_windows_386.go
generated
vendored
Normal file
7
vendor/github.com/akrylysov/pogreb/fs/os_mmap_windows_386.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const maxMmapSize = math.MaxInt32
|
||||
3
vendor/github.com/akrylysov/pogreb/fs/os_mmap_windows_amd64.go
generated
vendored
Normal file
3
vendor/github.com/akrylysov/pogreb/fs/os_mmap_windows_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package fs
|
||||
|
||||
const maxMmapSize = 1 << 48
|
||||
26
vendor/github.com/akrylysov/pogreb/fs/os_unix.go
generated
vendored
Normal file
26
vendor/github.com/akrylysov/pogreb/fs/os_unix.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func createLockFile(name string, perm os.FileMode) (LockFile, bool, error) {
|
||||
acquiredExisting := false
|
||||
if _, err := os.Stat(name); err == nil {
|
||||
acquiredExisting = true
|
||||
}
|
||||
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, perm)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
|
||||
if err == syscall.EWOULDBLOCK {
|
||||
err = os.ErrExist
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return &osLockFile{f, name}, acquiredExisting, nil
|
||||
}
|
||||
60
vendor/github.com/akrylysov/pogreb/fs/os_windows.go
generated
vendored
Normal file
60
vendor/github.com/akrylysov/pogreb/fs/os_windows.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procLockFileEx = modkernel32.NewProc("LockFileEx")
|
||||
)
|
||||
|
||||
const (
|
||||
errorLockViolation = 0x21
|
||||
)
|
||||
|
||||
func lockfile(f *os.File) error {
|
||||
var ol syscall.Overlapped
|
||||
|
||||
r1, _, err := syscall.Syscall6(
|
||||
procLockFileEx.Addr(),
|
||||
6,
|
||||
uintptr(f.Fd()), // handle
|
||||
uintptr(0x0003),
|
||||
uintptr(0), // reserved
|
||||
uintptr(1), // locklow
|
||||
uintptr(0), // lockhigh
|
||||
uintptr(unsafe.Pointer(&ol)),
|
||||
)
|
||||
if r1 == 0 && (err == syscall.ERROR_FILE_EXISTS || err == errorLockViolation) {
|
||||
return os.ErrExist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createLockFile(name string, perm os.FileMode) (LockFile, bool, error) {
|
||||
acquiredExisting := false
|
||||
if _, err := os.Stat(name); err == nil {
|
||||
acquiredExisting = true
|
||||
}
|
||||
fd, err := syscall.CreateFile(&(syscall.StringToUTF16(name)[0]),
|
||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||
nil,
|
||||
syscall.CREATE_ALWAYS,
|
||||
syscall.FILE_ATTRIBUTE_NORMAL,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, false, os.ErrExist
|
||||
}
|
||||
f := os.NewFile(uintptr(fd), name)
|
||||
if err := lockfile(f); err != nil {
|
||||
f.Close()
|
||||
return nil, false, err
|
||||
}
|
||||
return &osLockFile{f, name}, acquiredExisting, nil
|
||||
}
|
||||
52
vendor/github.com/akrylysov/pogreb/fs/sub.go
generated
vendored
Normal file
52
vendor/github.com/akrylysov/pogreb/fs/sub.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Sub returns a new file system rooted at dir.
|
||||
func Sub(fsys FileSystem, dir string) FileSystem {
|
||||
return &subFS{
|
||||
fsys: fsys,
|
||||
root: dir,
|
||||
}
|
||||
}
|
||||
|
||||
type subFS struct {
|
||||
fsys FileSystem
|
||||
root string
|
||||
}
|
||||
|
||||
func (fs *subFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
subName := filepath.Join(fs.root, name)
|
||||
return fs.fsys.OpenFile(subName, flag, perm)
|
||||
}
|
||||
|
||||
func (fs *subFS) Stat(name string) (os.FileInfo, error) {
|
||||
subName := filepath.Join(fs.root, name)
|
||||
return fs.fsys.Stat(subName)
|
||||
}
|
||||
|
||||
func (fs *subFS) Remove(name string) error {
|
||||
subName := filepath.Join(fs.root, name)
|
||||
return fs.fsys.Remove(subName)
|
||||
}
|
||||
|
||||
func (fs *subFS) Rename(oldpath, newpath string) error {
|
||||
subOldpath := filepath.Join(fs.root, oldpath)
|
||||
subNewpath := filepath.Join(fs.root, newpath)
|
||||
return fs.fsys.Rename(subOldpath, subNewpath)
|
||||
}
|
||||
|
||||
func (fs *subFS) ReadDir(name string) ([]os.FileInfo, error) {
|
||||
subName := filepath.Join(fs.root, name)
|
||||
return fs.fsys.ReadDir(subName)
|
||||
}
|
||||
|
||||
func (fs *subFS) CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error) {
|
||||
subName := filepath.Join(fs.root, name)
|
||||
return fs.fsys.CreateLockFile(subName, perm)
|
||||
}
|
||||
|
||||
var _ FileSystem = &subFS{}
|
||||
27
vendor/github.com/akrylysov/pogreb/gobfile.go
generated
vendored
Normal file
27
vendor/github.com/akrylysov/pogreb/gobfile.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/akrylysov/pogreb/fs"
|
||||
)
|
||||
|
||||
func readGobFile(fsys fs.FileSystem, name string, v interface{}) error {
|
||||
f, err := openFile(fsys, name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
dec := gob.NewDecoder(f)
|
||||
return dec.Decode(v)
|
||||
}
|
||||
|
||||
func writeGobFile(fsys fs.FileSystem, name string, v interface{}) error {
|
||||
f, err := openFile(fsys, name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
enc := gob.NewEncoder(f)
|
||||
return enc.Encode(v)
|
||||
}
|
||||
43
vendor/github.com/akrylysov/pogreb/header.go
generated
vendored
Normal file
43
vendor/github.com/akrylysov/pogreb/header.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
formatVersion = 2 // File format version.
|
||||
headerSize = 512
|
||||
)
|
||||
|
||||
var (
|
||||
signature = [8]byte{'p', 'o', 'g', 'r', 'e', 'b', '\x0e', '\xfd'}
|
||||
)
|
||||
|
||||
type header struct {
|
||||
signature [8]byte
|
||||
formatVersion uint32
|
||||
}
|
||||
|
||||
func newHeader() *header {
|
||||
return &header{
|
||||
signature: signature,
|
||||
formatVersion: formatVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (h header) MarshalBinary() ([]byte, error) {
|
||||
buf := make([]byte, headerSize)
|
||||
copy(buf[:8], h.signature[:])
|
||||
binary.LittleEndian.PutUint32(buf[8:12], h.formatVersion)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (h *header) UnmarshalBinary(data []byte) error {
|
||||
if !bytes.Equal(data[:8], signature[:]) {
|
||||
return errCorrupted
|
||||
}
|
||||
copy(h.signature[:], data[:8])
|
||||
h.formatVersion = binary.LittleEndian.Uint32(data[8:12])
|
||||
return nil
|
||||
}
|
||||
363
vendor/github.com/akrylysov/pogreb/index.go
generated
vendored
Normal file
363
vendor/github.com/akrylysov/pogreb/index.go
generated
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"github.com/akrylysov/pogreb/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
indexExt = ".pix"
|
||||
indexMainName = "main" + indexExt
|
||||
indexOverflowName = "overflow" + indexExt
|
||||
indexMetaName = "index" + metaExt
|
||||
loadFactor = 0.7
|
||||
)
|
||||
|
||||
// index is an on-disk linear hashing hash table.
|
||||
// It uses two files to store the hash table on disk - "main" and "overflow" index files.
|
||||
// Each index file holds an array of buckets.
|
||||
type index struct {
|
||||
opts *Options
|
||||
main *file // Main index file.
|
||||
overflow *file // Overflow index file.
|
||||
freeBucketOffs []int64 // Offsets of freed buckets.
|
||||
level uint8 // Maximum number of buckets on a logarithmic scale.
|
||||
numKeys uint32 // Number of keys.
|
||||
numBuckets uint32 // Number of buckets.
|
||||
splitBucketIdx uint32 // Index of the bucket to split on next split.
|
||||
}
|
||||
|
||||
type indexMeta struct {
|
||||
Level uint8
|
||||
NumKeys uint32
|
||||
NumBuckets uint32
|
||||
SplitBucketIndex uint32
|
||||
FreeOverflowBuckets []int64
|
||||
}
|
||||
|
||||
// matchKeyFunc returns whether the slot matches the key sought.
|
||||
type matchKeyFunc func(slot) (bool, error)
|
||||
|
||||
func openIndex(opts *Options) (*index, error) {
|
||||
main, err := openFile(opts.FileSystem, indexMainName, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "opening main index")
|
||||
}
|
||||
overflow, err := openFile(opts.FileSystem, indexOverflowName, false)
|
||||
if err != nil {
|
||||
_ = main.Close()
|
||||
return nil, errors.Wrap(err, "opening overflow index")
|
||||
}
|
||||
idx := &index{
|
||||
opts: opts,
|
||||
main: main,
|
||||
overflow: overflow,
|
||||
numBuckets: 1,
|
||||
}
|
||||
if main.empty() {
|
||||
// Add an empty bucket.
|
||||
if _, err = idx.main.extend(bucketSize); err != nil {
|
||||
_ = main.Close()
|
||||
_ = overflow.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else if err := idx.readMeta(); err != nil {
|
||||
_ = main.Close()
|
||||
_ = overflow.Close()
|
||||
return nil, errors.Wrap(err, "opening index meta")
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func (idx *index) writeMeta() error {
|
||||
m := indexMeta{
|
||||
Level: idx.level,
|
||||
NumKeys: idx.numKeys,
|
||||
NumBuckets: idx.numBuckets,
|
||||
SplitBucketIndex: idx.splitBucketIdx,
|
||||
FreeOverflowBuckets: idx.freeBucketOffs,
|
||||
}
|
||||
return writeGobFile(idx.opts.FileSystem, indexMetaName, m)
|
||||
}
|
||||
|
||||
func (idx *index) readMeta() error {
|
||||
m := indexMeta{}
|
||||
if err := readGobFile(idx.opts.FileSystem, indexMetaName, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
idx.level = m.Level
|
||||
idx.numKeys = m.NumKeys
|
||||
idx.numBuckets = m.NumBuckets
|
||||
idx.splitBucketIdx = m.SplitBucketIndex
|
||||
idx.freeBucketOffs = m.FreeOverflowBuckets
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *index) bucketIndex(hash uint32) uint32 {
|
||||
bidx := hash & ((1 << idx.level) - 1)
|
||||
if bidx < idx.splitBucketIdx {
|
||||
return hash & ((1 << (idx.level + 1)) - 1)
|
||||
}
|
||||
return bidx
|
||||
}
|
||||
|
||||
type bucketIterator struct {
|
||||
off int64 // Offset of the next bucket.
|
||||
f *file // Current index file.
|
||||
overflow *file // Overflow index file.
|
||||
}
|
||||
|
||||
// bucketOffset returns on-disk bucket offset by the bucket index.
|
||||
func bucketOffset(idx uint32) int64 {
|
||||
return int64(headerSize) + (int64(bucketSize) * int64(idx))
|
||||
}
|
||||
|
||||
func (idx *index) newBucketIterator(startBucketIdx uint32) *bucketIterator {
|
||||
return &bucketIterator{
|
||||
off: bucketOffset(startBucketIdx),
|
||||
f: idx.main,
|
||||
overflow: idx.overflow,
|
||||
}
|
||||
}
|
||||
|
||||
func (it *bucketIterator) next() (bucketHandle, error) {
|
||||
if it.off == 0 {
|
||||
return bucketHandle{}, ErrIterationDone
|
||||
}
|
||||
b := bucketHandle{file: it.f, offset: it.off}
|
||||
if err := b.read(); err != nil {
|
||||
return bucketHandle{}, err
|
||||
}
|
||||
it.f = it.overflow
|
||||
it.off = b.next
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (idx *index) get(hash uint32, matchKey matchKeyFunc) error {
|
||||
it := idx.newBucketIterator(idx.bucketIndex(hash))
|
||||
for {
|
||||
b, err := it.next()
|
||||
if err == ErrIterationDone {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < slotsPerBucket; i++ {
|
||||
sl := b.slots[i]
|
||||
// No more slots in the bucket.
|
||||
if sl.offset == 0 {
|
||||
break
|
||||
}
|
||||
if hash != sl.hash {
|
||||
continue
|
||||
}
|
||||
if match, err := matchKey(sl); match || err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (idx *index) findInsertionBucket(newSlot slot, matchKey matchKeyFunc) (*slotWriter, bool, error) {
|
||||
sw := &slotWriter{}
|
||||
it := idx.newBucketIterator(idx.bucketIndex(newSlot.hash))
|
||||
for {
|
||||
b, err := it.next()
|
||||
if err == ErrIterationDone {
|
||||
return nil, false, errors.New("failed to insert a new slot")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
sw.bucket = &b
|
||||
var i int
|
||||
for i = 0; i < slotsPerBucket; i++ {
|
||||
sl := b.slots[i]
|
||||
if sl.offset == 0 {
|
||||
// Found an empty slot.
|
||||
sw.slotIdx = i
|
||||
return sw, false, nil
|
||||
}
|
||||
if newSlot.hash != sl.hash {
|
||||
continue
|
||||
}
|
||||
match, err := matchKey(sl)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if match {
|
||||
// Key already in the index.
|
||||
// The slot writer will overwrite the existing slot.
|
||||
sw.slotIdx = i
|
||||
return sw, true, nil
|
||||
}
|
||||
}
|
||||
if b.next == 0 {
|
||||
// No more buckets in the chain.
|
||||
sw.slotIdx = i
|
||||
return sw, false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (idx *index) put(newSlot slot, matchKey matchKeyFunc) error {
|
||||
if idx.numKeys == MaxKeys {
|
||||
return errFull
|
||||
}
|
||||
sw, overwritingExisting, err := idx.findInsertionBucket(newSlot, matchKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sw.insert(newSlot, idx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sw.write(); err != nil {
|
||||
return err
|
||||
}
|
||||
if overwritingExisting {
|
||||
return nil
|
||||
}
|
||||
idx.numKeys++
|
||||
if float64(idx.numKeys)/float64(idx.numBuckets*slotsPerBucket) > loadFactor {
|
||||
if err := idx.split(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *index) delete(hash uint32, matchKey matchKeyFunc) error {
|
||||
it := idx.newBucketIterator(idx.bucketIndex(hash))
|
||||
for {
|
||||
b, err := it.next()
|
||||
if err == ErrIterationDone {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < slotsPerBucket; i++ {
|
||||
sl := b.slots[i]
|
||||
if sl.offset == 0 {
|
||||
break
|
||||
}
|
||||
if hash != sl.hash {
|
||||
continue
|
||||
}
|
||||
match, err := matchKey(sl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
b.del(i)
|
||||
if err := b.write(); err != nil {
|
||||
return err
|
||||
}
|
||||
idx.numKeys--
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (idx *index) createOverflowBucket() (*bucketHandle, error) {
|
||||
var off int64
|
||||
if len(idx.freeBucketOffs) > 0 {
|
||||
off = idx.freeBucketOffs[0]
|
||||
idx.freeBucketOffs = idx.freeBucketOffs[1:]
|
||||
} else {
|
||||
var err error
|
||||
off, err = idx.overflow.extend(bucketSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &bucketHandle{file: idx.overflow, offset: off}, nil
|
||||
}
|
||||
|
||||
func (idx *index) freeOverflowBucket(offsets ...int64) {
|
||||
idx.freeBucketOffs = append(idx.freeBucketOffs, offsets...)
|
||||
}
|
||||
|
||||
func (idx *index) split() error {
|
||||
updatedBucketIdx := idx.splitBucketIdx
|
||||
updatedBucketOff := bucketOffset(updatedBucketIdx)
|
||||
updatedBucket := slotWriter{
|
||||
bucket: &bucketHandle{file: idx.main, offset: updatedBucketOff},
|
||||
}
|
||||
|
||||
newBucketOff, err := idx.main.extend(bucketSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sw := slotWriter{
|
||||
bucket: &bucketHandle{file: idx.main, offset: newBucketOff},
|
||||
}
|
||||
|
||||
idx.splitBucketIdx++
|
||||
if idx.splitBucketIdx == 1<<idx.level {
|
||||
idx.level++
|
||||
idx.splitBucketIdx = 0
|
||||
}
|
||||
|
||||
var overflowBuckets []int64
|
||||
it := idx.newBucketIterator(updatedBucketIdx)
|
||||
for {
|
||||
b, err := it.next()
|
||||
if err == ErrIterationDone {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for j := 0; j < slotsPerBucket; j++ {
|
||||
sl := b.slots[j]
|
||||
if sl.offset == 0 {
|
||||
break
|
||||
}
|
||||
if idx.bucketIndex(sl.hash) == updatedBucketIdx {
|
||||
if err := updatedBucket.insert(sl, idx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := sw.insert(sl, idx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.next != 0 {
|
||||
overflowBuckets = append(overflowBuckets, b.next)
|
||||
}
|
||||
}
|
||||
|
||||
idx.freeOverflowBucket(overflowBuckets...)
|
||||
|
||||
if err := sw.write(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updatedBucket.write(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idx.numBuckets++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *index) close() error {
|
||||
if err := idx.writeMeta(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idx.main.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idx.overflow.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *index) count() uint32 {
|
||||
return idx.numKeys
|
||||
}
|
||||
42
vendor/github.com/akrylysov/pogreb/internal/errors/errors.go
generated
vendored
Normal file
42
vendor/github.com/akrylysov/pogreb/internal/errors/errors.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type wrappedError struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (we wrappedError) Error() string {
|
||||
return we.msg + ": " + we.cause.Error()
|
||||
}
|
||||
|
||||
func (we wrappedError) Unwrap() error {
|
||||
return we.cause
|
||||
}
|
||||
|
||||
// New returns an error that formats as the given text.
|
||||
func New(text string) error {
|
||||
return errors.New(text)
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with an additional message.
|
||||
// Compatible with Go 1.13 error chains.
|
||||
func Wrap(cause error, message string) error {
|
||||
return wrappedError{
|
||||
cause: cause,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with an additional formatted message.
|
||||
// Compatible with Go 1.13 error chains.
|
||||
func Wrapf(cause error, format string, a ...interface{}) error {
|
||||
return wrappedError{
|
||||
cause: cause,
|
||||
msg: fmt.Sprintf(format, a...),
|
||||
}
|
||||
}
|
||||
55
vendor/github.com/akrylysov/pogreb/internal/hash/murmurhash32.go
generated
vendored
Normal file
55
vendor/github.com/akrylysov/pogreb/internal/hash/murmurhash32.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package hash
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
const (
|
||||
c1 uint32 = 0xcc9e2d51
|
||||
c2 uint32 = 0x1b873593
|
||||
)
|
||||
|
||||
// Sum32WithSeed is a port of MurmurHash3_x86_32 function.
|
||||
func Sum32WithSeed(data []byte, seed uint32) uint32 {
|
||||
h1 := seed
|
||||
dlen := len(data)
|
||||
|
||||
for len(data) >= 4 {
|
||||
k1 := uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24
|
||||
data = data[4:]
|
||||
|
||||
k1 *= c1
|
||||
k1 = bits.RotateLeft32(k1, 15)
|
||||
k1 *= c2
|
||||
|
||||
h1 ^= k1
|
||||
h1 = bits.RotateLeft32(h1, 13)
|
||||
h1 = h1*5 + 0xe6546b64
|
||||
}
|
||||
|
||||
var k1 uint32
|
||||
switch len(data) {
|
||||
case 3:
|
||||
k1 ^= uint32(data[2]) << 16
|
||||
fallthrough
|
||||
case 2:
|
||||
k1 ^= uint32(data[1]) << 8
|
||||
fallthrough
|
||||
case 1:
|
||||
k1 ^= uint32(data[0])
|
||||
k1 *= c1
|
||||
k1 = bits.RotateLeft32(k1, 15)
|
||||
k1 *= c2
|
||||
h1 ^= k1
|
||||
}
|
||||
|
||||
h1 ^= uint32(dlen)
|
||||
|
||||
h1 ^= h1 >> 16
|
||||
h1 *= 0x85ebca6b
|
||||
h1 ^= h1 >> 13
|
||||
h1 *= 0xc2b2ae35
|
||||
h1 ^= h1 >> 16
|
||||
|
||||
return h1
|
||||
}
|
||||
15
vendor/github.com/akrylysov/pogreb/internal/hash/seed.go
generated
vendored
Normal file
15
vendor/github.com/akrylysov/pogreb/internal/hash/seed.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package hash
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// RandSeed generates a random hash seed.
|
||||
func RandSeed() (uint32, error) {
|
||||
b := make([]byte, 4)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint32(b), nil
|
||||
}
|
||||
75
vendor/github.com/akrylysov/pogreb/iterator.go
generated
vendored
Normal file
75
vendor/github.com/akrylysov/pogreb/iterator.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrIterationDone is returned by ItemIterator.Next calls when there are no more items to return.
|
||||
var ErrIterationDone = errors.New("no more items in iterator")
|
||||
|
||||
type item struct {
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
// ItemIterator is an iterator over DB key-value pairs. It iterates the items in an unspecified order.
|
||||
type ItemIterator struct {
|
||||
db *DB
|
||||
nextBucketIdx uint32
|
||||
queue []item
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// fetchItems adds items to the iterator queue from a bucket located at nextBucketIdx.
|
||||
func (it *ItemIterator) fetchItems(nextBucketIdx uint32) error {
|
||||
bit := it.db.index.newBucketIterator(nextBucketIdx)
|
||||
for {
|
||||
b, err := bit.next()
|
||||
if err == ErrIterationDone {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < slotsPerBucket; i++ {
|
||||
sl := b.slots[i]
|
||||
if sl.offset == 0 {
|
||||
// No more items in the bucket.
|
||||
break
|
||||
}
|
||||
key, value, err := it.db.datalog.readKeyValue(sl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key = cloneBytes(key)
|
||||
value = cloneBytes(value)
|
||||
it.queue = append(it.queue, item{key: key, value: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next key-value pair if available, otherwise it returns ErrIterationDone error.
|
||||
func (it *ItemIterator) Next() ([]byte, []byte, error) {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
|
||||
it.db.mu.RLock()
|
||||
defer it.db.mu.RUnlock()
|
||||
|
||||
// The iterator queue is empty and we have more buckets to check.
|
||||
for len(it.queue) == 0 && it.nextBucketIdx < it.db.index.numBuckets {
|
||||
if err := it.fetchItems(it.nextBucketIdx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
it.nextBucketIdx++
|
||||
}
|
||||
|
||||
if len(it.queue) > 0 {
|
||||
item := it.queue[0]
|
||||
it.queue = it.queue[1:]
|
||||
return item.key, item.value, nil
|
||||
}
|
||||
|
||||
return nil, nil, ErrIterationDone
|
||||
}
|
||||
15
vendor/github.com/akrylysov/pogreb/lock.go
generated
vendored
Normal file
15
vendor/github.com/akrylysov/pogreb/lock.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/akrylysov/pogreb/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
lockName = "lock"
|
||||
)
|
||||
|
||||
func createLockFile(opts *Options) (fs.LockFile, bool, error) {
|
||||
return opts.FileSystem.CreateLockFile(lockName, os.FileMode(0644))
|
||||
}
|
||||
15
vendor/github.com/akrylysov/pogreb/logger.go
generated
vendored
Normal file
15
vendor/github.com/akrylysov/pogreb/logger.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stderr, "pogreb: ", 0)
|
||||
|
||||
// SetLogger sets the global logger.
|
||||
func SetLogger(l *log.Logger) {
|
||||
if l != nil {
|
||||
logger = l
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/akrylysov/pogreb/metrics.go
generated
vendored
Normal file
11
vendor/github.com/akrylysov/pogreb/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package pogreb
|
||||
|
||||
import "expvar"
|
||||
|
||||
// Metrics holds the DB metrics.
|
||||
type Metrics struct {
|
||||
Puts expvar.Int
|
||||
Dels expvar.Int
|
||||
Gets expvar.Int
|
||||
HashCollisions expvar.Int
|
||||
}
|
||||
52
vendor/github.com/akrylysov/pogreb/options.go
generated
vendored
Normal file
52
vendor/github.com/akrylysov/pogreb/options.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/akrylysov/pogreb/fs"
|
||||
)
|
||||
|
||||
// Options holds the optional DB parameters.
|
||||
type Options struct {
|
||||
// BackgroundSyncInterval sets the amount of time between background Sync() calls.
|
||||
//
|
||||
// Setting the value to 0 disables the automatic background synchronization.
|
||||
// Setting the value to -1 makes the DB call Sync() after every write operation.
|
||||
BackgroundSyncInterval time.Duration
|
||||
|
||||
// BackgroundCompactionInterval sets the amount of time between background Compact() calls.
|
||||
//
|
||||
// Setting the value to 0 disables the automatic background compaction.
|
||||
BackgroundCompactionInterval time.Duration
|
||||
|
||||
// FileSystem sets the file system implementation.
|
||||
//
|
||||
// Default: fs.OSMMap.
|
||||
FileSystem fs.FileSystem
|
||||
|
||||
maxSegmentSize uint32
|
||||
compactionMinSegmentSize uint32
|
||||
compactionMinFragmentation float32
|
||||
}
|
||||
|
||||
func (src *Options) copyWithDefaults(path string) *Options {
|
||||
opts := Options{}
|
||||
if src != nil {
|
||||
opts = *src
|
||||
}
|
||||
if opts.FileSystem == nil {
|
||||
opts.FileSystem = fs.OSMMap
|
||||
}
|
||||
opts.FileSystem = fs.Sub(opts.FileSystem, path)
|
||||
if opts.maxSegmentSize == 0 {
|
||||
opts.maxSegmentSize = math.MaxUint32
|
||||
}
|
||||
if opts.compactionMinSegmentSize == 0 {
|
||||
opts.compactionMinSegmentSize = 32 << 20
|
||||
}
|
||||
if opts.compactionMinFragmentation == 0 {
|
||||
opts.compactionMinFragmentation = 0.5
|
||||
}
|
||||
return &opts
|
||||
}
|
||||
161
vendor/github.com/akrylysov/pogreb/recovery.go
generated
vendored
Normal file
161
vendor/github.com/akrylysov/pogreb/recovery.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akrylysov/pogreb/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
recoveryBackupExt = ".bac"
|
||||
)
|
||||
|
||||
func backupNonsegmentFiles(fsys fs.FileSystem) error {
|
||||
logger.Println("moving non-segment files...")
|
||||
|
||||
files, err := fsys.ReadDir(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
ext := filepath.Ext(name)
|
||||
if ext == segmentExt || name == lockName {
|
||||
continue
|
||||
}
|
||||
dst := name + recoveryBackupExt
|
||||
if err := fsys.Rename(name, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Printf("moved %s to %s", name, dst)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeRecoveryBackupFiles(fsys fs.FileSystem) error {
|
||||
logger.Println("removing recovery backup files...")
|
||||
|
||||
files, err := fsys.ReadDir(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
ext := filepath.Ext(name)
|
||||
if ext != recoveryBackupExt {
|
||||
continue
|
||||
}
|
||||
if err := fsys.Remove(name); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Printf("removed %s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// recoveryIterator iterates over records of all datalog segments in insertion order.
|
||||
// Corrupted segments are truncated to the last valid record.
|
||||
type recoveryIterator struct {
|
||||
segments []*segment
|
||||
segit *segmentIterator
|
||||
}
|
||||
|
||||
func newRecoveryIterator(segments []*segment) *recoveryIterator {
|
||||
return &recoveryIterator{
|
||||
segments: segments,
|
||||
}
|
||||
}
|
||||
|
||||
func (it *recoveryIterator) next() (record, error) {
|
||||
for {
|
||||
if it.segit == nil {
|
||||
if len(it.segments) == 0 {
|
||||
return record{}, ErrIterationDone
|
||||
}
|
||||
var err error
|
||||
it.segit, err = newSegmentIterator(it.segments[0])
|
||||
if err != nil {
|
||||
return record{}, err
|
||||
}
|
||||
it.segments = it.segments[1:]
|
||||
}
|
||||
rec, err := it.segit.next()
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF || err == errCorrupted {
|
||||
// Truncate file to the last valid offset.
|
||||
if err := it.segit.f.Truncate(int64(it.segit.offset)); err != nil {
|
||||
return record{}, err
|
||||
}
|
||||
fi, fierr := it.segit.f.Stat()
|
||||
if fierr != nil {
|
||||
return record{}, fierr
|
||||
}
|
||||
logger.Printf("truncated segment %s to offset %d", fi.Name(), it.segit.offset)
|
||||
err = ErrIterationDone
|
||||
}
|
||||
if err == ErrIterationDone {
|
||||
it.segit = nil
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return record{}, err
|
||||
}
|
||||
return rec, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) recover() error {
|
||||
logger.Println("started recovery")
|
||||
logger.Println("rebuilding index...")
|
||||
|
||||
segments := db.datalog.segmentsBySequenceID()
|
||||
it := newRecoveryIterator(segments)
|
||||
for {
|
||||
rec, err := it.next()
|
||||
if err == ErrIterationDone {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := db.hash(rec.key)
|
||||
meta := db.datalog.segments[rec.segmentID].meta
|
||||
if rec.rtype == recordTypePut {
|
||||
sl := slot{
|
||||
hash: h,
|
||||
segmentID: rec.segmentID,
|
||||
keySize: uint16(len(rec.key)),
|
||||
valueSize: uint32(len(rec.value)),
|
||||
offset: rec.offset,
|
||||
}
|
||||
if err := db.put(sl, rec.key); err != nil {
|
||||
return err
|
||||
}
|
||||
meta.PutRecords++
|
||||
} else {
|
||||
if err := db.del(h, rec.key, false); err != nil {
|
||||
return err
|
||||
}
|
||||
meta.DeleteRecords++
|
||||
meta.DeletedBytes += uint32(len(rec.data))
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all segments except the newest as full.
|
||||
for i := 0; i < len(segments)-1; i++ {
|
||||
segments[i].meta.Full = true
|
||||
}
|
||||
|
||||
if err := removeRecoveryBackupFiles(db.opts.FileSystem); err != nil {
|
||||
logger.Printf("error removing recovery backups files: %v", err)
|
||||
}
|
||||
|
||||
logger.Println("successfully recovered database")
|
||||
|
||||
return nil
|
||||
}
|
||||
156
vendor/github.com/akrylysov/pogreb/segment.go
generated
vendored
Normal file
156
vendor/github.com/akrylysov/pogreb/segment.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
)
|
||||
|
||||
type recordType int
|
||||
|
||||
const (
|
||||
recordTypePut recordType = iota
|
||||
recordTypeDelete
|
||||
|
||||
segmentExt = ".psg"
|
||||
)
|
||||
|
||||
// segment is a write-ahead log segment.
|
||||
// It consists of a sequence of binary-encoded variable length records.
|
||||
type segment struct {
|
||||
*file
|
||||
id uint16 // Physical segment identifier.
|
||||
sequenceID uint64 // Logical monotonically increasing segment identifier.
|
||||
name string
|
||||
meta *segmentMeta
|
||||
}
|
||||
|
||||
func segmentName(id uint16, sequenceID uint64) string {
|
||||
return fmt.Sprintf("%05d-%d%s", id, sequenceID, segmentExt)
|
||||
}
|
||||
|
||||
type segmentMeta struct {
|
||||
Full bool
|
||||
PutRecords uint32
|
||||
DeleteRecords uint32
|
||||
DeletedKeys uint32
|
||||
DeletedBytes uint32
|
||||
}
|
||||
|
||||
func segmentMetaName(id uint16, sequenceID uint64) string {
|
||||
return segmentName(id, sequenceID) + metaExt
|
||||
}
|
||||
|
||||
// Binary representation of a segment record:
|
||||
// +---------------+------------------+------------------+-...-+--...--+----------+
|
||||
// | Key Size (2B) | Record Type (1b) | Value Size (31b) | Key | Value | CRC (4B) |
|
||||
// +---------------+------------------+------------------+-...-+--...--+----------+
|
||||
type record struct {
|
||||
rtype recordType
|
||||
segmentID uint16
|
||||
offset uint32
|
||||
data []byte
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
func encodedRecordSize(kvSize uint32) uint32 {
|
||||
// key size, value size, key, value, crc32
|
||||
return 2 + 4 + kvSize + 4
|
||||
}
|
||||
|
||||
func encodeRecord(key []byte, value []byte, rt recordType) []byte {
|
||||
size := encodedRecordSize(uint32(len(key) + len(value)))
|
||||
data := make([]byte, size)
|
||||
binary.LittleEndian.PutUint16(data[:2], uint16(len(key)))
|
||||
|
||||
valLen := uint32(len(value))
|
||||
if rt == recordTypeDelete { // Set delete bit.
|
||||
valLen |= 1 << 31
|
||||
}
|
||||
binary.LittleEndian.PutUint32(data[2:], valLen)
|
||||
|
||||
copy(data[6:], key)
|
||||
copy(data[6+len(key):], value)
|
||||
checksum := crc32.ChecksumIEEE(data[:6+len(key)+len(value)])
|
||||
binary.LittleEndian.PutUint32(data[size-4:size], checksum)
|
||||
return data
|
||||
}
|
||||
|
||||
func encodePutRecord(key []byte, value []byte) []byte {
|
||||
return encodeRecord(key, value, recordTypePut)
|
||||
}
|
||||
|
||||
func encodeDeleteRecord(key []byte) []byte {
|
||||
return encodeRecord(key, nil, recordTypeDelete)
|
||||
}
|
||||
|
||||
// segmentIterator iterates over segment records.
|
||||
type segmentIterator struct {
|
||||
f *segment
|
||||
offset uint32
|
||||
r *bufio.Reader
|
||||
buf []byte // kv size and crc32 reusable buffer.
|
||||
}
|
||||
|
||||
func newSegmentIterator(f *segment) (*segmentIterator, error) {
|
||||
if _, err := f.Seek(int64(headerSize), io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &segmentIterator{
|
||||
f: f,
|
||||
offset: headerSize,
|
||||
r: bufio.NewReader(f),
|
||||
buf: make([]byte, 6),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (it *segmentIterator) next() (record, error) {
|
||||
// Read key and value size.
|
||||
kvSizeBuf := it.buf
|
||||
if _, err := io.ReadFull(it.r, kvSizeBuf); err != nil {
|
||||
if err == io.EOF {
|
||||
return record{}, ErrIterationDone
|
||||
}
|
||||
return record{}, err
|
||||
}
|
||||
|
||||
// Decode key size.
|
||||
keySize := uint32(binary.LittleEndian.Uint16(kvSizeBuf[:2]))
|
||||
|
||||
// Decode value size and record type.
|
||||
rt := recordTypePut
|
||||
valueSize := binary.LittleEndian.Uint32(kvSizeBuf[2:])
|
||||
if valueSize&(1<<31) != 0 {
|
||||
rt = recordTypeDelete
|
||||
valueSize &^= 1 << 31
|
||||
}
|
||||
|
||||
// Read key, value and checksum.
|
||||
recordSize := encodedRecordSize(keySize + valueSize)
|
||||
data := make([]byte, recordSize)
|
||||
copy(data, kvSizeBuf)
|
||||
if _, err := io.ReadFull(it.r, data[6:]); err != nil {
|
||||
return record{}, err
|
||||
}
|
||||
|
||||
// Verify checksum.
|
||||
checksum := binary.LittleEndian.Uint32(data[len(data)-4:])
|
||||
if checksum != crc32.ChecksumIEEE(data[:len(data)-4]) {
|
||||
return record{}, errCorrupted
|
||||
}
|
||||
|
||||
offset := it.offset
|
||||
it.offset += recordSize
|
||||
rec := record{
|
||||
rtype: rt,
|
||||
segmentID: it.f.id,
|
||||
offset: offset,
|
||||
data: data,
|
||||
key: data[6 : 6+keySize],
|
||||
value: data[6+keySize : 6+keySize+valueSize],
|
||||
}
|
||||
return rec, nil
|
||||
}
|
||||
Reference in New Issue
Block a user