2020-09-28 17:18:36 +00:00
|
|
|
package bitmapdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2020-10-19 13:11:01 +00:00
|
|
|
"sort"
|
2020-10-06 06:36:34 +00:00
|
|
|
|
2020-10-02 08:16:21 +00:00
|
|
|
"github.com/RoaringBitmap/roaring"
|
2020-09-28 17:18:36 +00:00
|
|
|
"github.com/c2h5oh/datasize"
|
|
|
|
"github.com/ledgerwatch/turbo-geth/common"
|
|
|
|
"github.com/ledgerwatch/turbo-geth/ethdb"
|
|
|
|
)
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
const ChunkLimit = uint64(1900 * datasize.B) // threshold after which appear LMDB OverflowPages
|
2020-09-28 17:18:36 +00:00
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
func ChunkIterator(bm *roaring.Bitmap, target uint64) func() *roaring.Bitmap {
|
|
|
|
return func() *roaring.Bitmap {
|
|
|
|
return CutLeft(bm, target)
|
2020-09-28 17:18:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
// CutLeft - cut from bitmap `targetSize` bytes from left
|
|
|
|
// removing lft part from `bm`
|
|
|
|
// returns nil on zero cardinality
|
|
|
|
func CutLeft(bm *roaring.Bitmap, targetSize uint64) *roaring.Bitmap {
|
|
|
|
if bm.GetCardinality() == 0 {
|
2020-10-02 03:54:11 +00:00
|
|
|
return nil
|
2020-09-28 17:18:36 +00:00
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
sz := bm.GetSerializedSizeInBytes()
|
|
|
|
if sz <= targetSize {
|
|
|
|
lft := roaring.New()
|
|
|
|
lft.Or(bm)
|
|
|
|
bm.Clear()
|
|
|
|
return lft
|
|
|
|
}
|
|
|
|
|
|
|
|
lft := roaring.New()
|
|
|
|
from := uint64(bm.Minimum())
|
|
|
|
minMax := bm.Maximum() - bm.Minimum() // +1 because AddRange has semantic [from,to)
|
|
|
|
to := sort.Search(int(minMax), func(i int) bool { // can be optimized to avoid "too small steps", but let's leave it for readability
|
|
|
|
lft.Clear()
|
|
|
|
lft.AddRange(from, from+uint64(i)+1)
|
|
|
|
lft.And(bm)
|
|
|
|
return lft.GetSerializedSizeInBytes() > targetSize
|
|
|
|
})
|
|
|
|
|
|
|
|
lft.Clear()
|
|
|
|
lft.AddRange(from, from+uint64(to)+1)
|
|
|
|
lft.And(bm)
|
|
|
|
bm.RemoveRange(from, from+uint64(to)+1)
|
|
|
|
return lft
|
2020-09-28 17:18:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TruncateRange - gets existing bitmap in db and call RemoveRange operator on it.
|
|
|
|
// starts from hot shard, stops when shard not overlap with [from-to)
|
|
|
|
// !Important: [from, to)
|
2020-10-02 03:54:11 +00:00
|
|
|
func TruncateRange(tx ethdb.Tx, bucket string, key []byte, from, to uint64) error {
|
2020-10-19 13:11:01 +00:00
|
|
|
chunkKey := make([]byte, len(key)+4)
|
|
|
|
copy(chunkKey, key)
|
|
|
|
binary.BigEndian.PutUint32(chunkKey[len(chunkKey)-4:], uint32(from))
|
2020-10-02 03:54:11 +00:00
|
|
|
c := tx.Cursor(bucket)
|
|
|
|
defer c.Close()
|
|
|
|
cForDelete := tx.Cursor(bucket) // use dedicated cursor for delete operation, but in near future will change to ETL
|
|
|
|
defer cForDelete.Close()
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
for k, v, err := c.Seek(chunkKey); k != nil; k, v, err = c.Next() {
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.HasPrefix(k, key) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-10-02 08:16:21 +00:00
|
|
|
bm := roaring.New()
|
|
|
|
_, err := bm.FromBuffer(v)
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-19 13:11:01 +00:00
|
|
|
noReasonToCheckNextChunk := (uint64(bm.Minimum()) <= from && uint64(bm.Maximum()) >= to) || binary.BigEndian.Uint32(k[len(k)-4:]) == ^uint32(0)
|
2020-09-28 17:18:36 +00:00
|
|
|
|
|
|
|
bm.RemoveRange(from, to)
|
|
|
|
if bm.GetCardinality() == 0 { // don't store empty bitmaps
|
2020-10-02 03:54:11 +00:00
|
|
|
err = cForDelete.Delete(k)
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-19 13:11:01 +00:00
|
|
|
if noReasonToCheckNextChunk {
|
2020-10-02 03:54:11 +00:00
|
|
|
break
|
|
|
|
}
|
2020-09-28 17:18:36 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
bm.RunOptimize()
|
2020-10-02 08:16:21 +00:00
|
|
|
newV := bytes.NewBuffer(make([]byte, 0, bm.GetSerializedSizeInBytes()))
|
|
|
|
_, err = bm.WriteTo(newV)
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-02 08:16:21 +00:00
|
|
|
err = c.Put(common.CopyBytes(k), newV.Bytes())
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
if noReasonToCheckNextChunk {
|
2020-09-28 17:18:36 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
// rename last chunk if it has no finality marker
|
|
|
|
binary.BigEndian.PutUint32(chunkKey[len(chunkKey)-4:], ^uint32(0))
|
|
|
|
k, v, err := c.Seek(chunkKey)
|
2020-10-02 03:54:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-19 13:11:01 +00:00
|
|
|
if k == nil || !bytes.HasPrefix(k, key) {
|
2020-10-02 03:54:11 +00:00
|
|
|
k, v, err = c.Prev()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
// case when all chunks were delted
|
|
|
|
if k == nil || !bytes.HasPrefix(k, key) {
|
|
|
|
return nil
|
|
|
|
}
|
2020-10-02 03:54:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
copyV := common.CopyBytes(v)
|
|
|
|
err = cForDelete.Delete(k)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
err = c.Put(chunkKey, copyV)
|
2020-10-02 03:54:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-28 17:18:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
// Get - reading as much chunks as needed to satisfy [from, to] condition
|
|
|
|
// join all chunks to 1 bitmap by Or operator
|
2020-10-02 08:16:21 +00:00
|
|
|
func Get(c ethdb.Cursor, key []byte, from, to uint32) (*roaring.Bitmap, error) {
|
2020-10-19 13:11:01 +00:00
|
|
|
var chunks []*roaring.Bitmap
|
2020-10-02 03:54:11 +00:00
|
|
|
|
|
|
|
fromKey := make([]byte, len(key)+4)
|
|
|
|
copy(fromKey, key)
|
|
|
|
binary.BigEndian.PutUint32(fromKey[len(fromKey)-4:], from)
|
|
|
|
for k, v, err := c.Seek(fromKey); k != nil; k, v, err = c.Next() {
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.HasPrefix(k, key) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-10-02 08:16:21 +00:00
|
|
|
bm := roaring.New()
|
|
|
|
_, err := bm.FromBuffer(v)
|
2020-09-28 17:18:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-19 13:11:01 +00:00
|
|
|
chunks = append(chunks, bm)
|
2020-10-02 03:54:11 +00:00
|
|
|
|
|
|
|
if binary.BigEndian.Uint32(k[len(k)-4:]) >= to {
|
|
|
|
break
|
|
|
|
}
|
2020-09-28 17:18:36 +00:00
|
|
|
}
|
|
|
|
|
2020-10-19 13:11:01 +00:00
|
|
|
if len(chunks) == 0 {
|
2020-10-02 08:16:21 +00:00
|
|
|
return roaring.New(), nil
|
2020-09-28 17:18:36 +00:00
|
|
|
}
|
2020-10-19 13:11:01 +00:00
|
|
|
return roaring.FastOr(chunks...), nil
|
2020-09-28 17:18:36 +00:00
|
|
|
}
|