d445ba9919
one is unsorted and fast and we only care about it with picking a new free range. the other is sorted and used when merging a new freed range with existing free ranges. both are computed from the events id index at beginning, then tracked manually on each addition or deletion. this change uncovered some errors so we fixed them and added some more fuzz test invariant checking. code is simplified a little bit. there was another thing I forgot.
205 lines
4.8 KiB
Go
205 lines
4.8 KiB
Go
package mmm
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"slices"
|
|
|
|
"fiatjaf.com/nostr"
|
|
"github.com/PowerDNS/lmdb-go/lmdb"
|
|
)
|
|
|
|
func (b *MultiMmapManager) Rescan() error {
|
|
b.writeMutex.Lock()
|
|
defer b.writeMutex.Unlock()
|
|
|
|
return b.lmdbEnv.Update(func(mmmtxn *lmdb.Txn) error {
|
|
cursor, err := mmmtxn.OpenCursor(b.indexId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cursor.Close()
|
|
|
|
var toPurge [][]byte // a list of idPrefix entries
|
|
for key, val, err := cursor.Get(nil, nil, lmdb.First); err == nil; key, val, err = cursor.Get(key, val, lmdb.Next) {
|
|
pos := positionFromBytes(val[0:12])
|
|
|
|
// for every event in this index
|
|
var borked bool
|
|
|
|
// we try to load it
|
|
var evt nostr.Event
|
|
if err := b.loadEvent(pos, &evt); err == nil && bytes.Equal(evt.ID[0:8], key) {
|
|
// all good
|
|
borked = false
|
|
} else {
|
|
// it's borked
|
|
borked = true
|
|
}
|
|
|
|
var layersToRemove []uint16
|
|
|
|
// then for every layer referenced in there we check
|
|
for s := 12; s < len(val); s += 2 {
|
|
layerId := binary.BigEndian.Uint16(val[s : s+2])
|
|
layer := b.layers.ByID(layerId)
|
|
if layer == nil {
|
|
continue
|
|
}
|
|
|
|
if err := layer.lmdbEnv.Update(func(txn *lmdb.Txn) error {
|
|
txn.RawRead = true
|
|
|
|
if borked {
|
|
// for borked events we have to do a bruteforce check
|
|
if layer.hasAtPosition(txn, pos) {
|
|
// expected -- delete anyway since it's borked
|
|
if err := layer.bruteDeleteIndexes(txn, pos); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// this stuff is doubly borked -- let's do nothing
|
|
return nil
|
|
}
|
|
} else {
|
|
// otherwise we do a more reasonable check
|
|
if layer.hasAtTimestampAndPosition(txn, evt.CreatedAt, pos) {
|
|
// expected, all good
|
|
} else {
|
|
// can't find it in this layer, so update source reference to remove this
|
|
// and clear it from this layer (if any traces remain)
|
|
if err := layer.deleteIndexes(txn, evt, val[0:12]); err != nil {
|
|
return err
|
|
}
|
|
|
|
// we'll remove references to this later
|
|
// (no need to do anything in the borked case as everything will be deleted)
|
|
layersToRemove = append(layersToRemove, layerId)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if borked {
|
|
toPurge = append(toPurge, key)
|
|
} else if len(layersToRemove) > 0 {
|
|
for s := 12; s < len(val); {
|
|
if slices.Contains(layersToRemove, binary.BigEndian.Uint16(val[s:s+2])) {
|
|
// swap-delete
|
|
copy(val[s:s+2], val[len(val)-2:])
|
|
val = val[0 : len(val)-2]
|
|
} else {
|
|
s += 2
|
|
}
|
|
}
|
|
|
|
if len(val) > 12 {
|
|
if err := mmmtxn.Put(b.indexId, key, val, 0); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
toPurge = append(toPurge, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, idPrefix := range toPurge {
|
|
// just delete from the ids index,
|
|
// no need to deal with the freeranges list as it will be recalculated afterwards.
|
|
// this also ensures any brokenly overlapping overwritten events don't have to be sacrificed.
|
|
if err := mmmtxn.Del(b.indexId, idPrefix, nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := b.gatherFreeRanges(mmmtxn); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (il *IndexingLayer) hasAtTimestampAndPosition(iltxn *lmdb.Txn, ts nostr.Timestamp, pos position) (exists bool) {
|
|
cursor, err := iltxn.OpenCursor(il.indexCreatedAt)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer cursor.Close()
|
|
|
|
key := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(key[0:4], uint32(ts))
|
|
|
|
if _, val, err := cursor.Get(key, nil, lmdb.SetKey); err == nil {
|
|
if positionFromBytes(val[0:12]) == pos {
|
|
exists = true
|
|
}
|
|
}
|
|
|
|
return exists
|
|
}
|
|
|
|
func (il *IndexingLayer) hasAtPosition(iltxn *lmdb.Txn, pos position) (exists bool) {
|
|
cursor, err := iltxn.OpenCursor(il.indexCreatedAt)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer cursor.Close()
|
|
|
|
for key, val, err := cursor.Get(nil, nil, lmdb.First); err == nil; key, val, err = cursor.Get(key, val, lmdb.Next) {
|
|
if positionFromBytes(val[0:12]) == pos {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
return exists
|
|
}
|
|
|
|
func (il *IndexingLayer) bruteDeleteIndexes(iltxn *lmdb.Txn, pos position) error {
|
|
type entry struct {
|
|
key []byte
|
|
val []byte
|
|
}
|
|
|
|
toDelete := make([]entry, 0, 8)
|
|
|
|
for _, index := range []lmdb.DBI{
|
|
il.indexCreatedAt,
|
|
il.indexKind,
|
|
il.indexPubkey,
|
|
il.indexPubkeyKind,
|
|
il.indexPTagKind,
|
|
il.indexTag,
|
|
il.indexTag32,
|
|
il.indexTagAddr,
|
|
} {
|
|
cursor, err := iltxn.OpenCursor(index)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for key, val, err := cursor.Get(nil, nil, lmdb.First); err == nil; key, val, err = cursor.Get(key, val, lmdb.Next) {
|
|
if positionFromBytes(val[0:12]) == pos {
|
|
toDelete = append(toDelete, entry{key, val})
|
|
}
|
|
}
|
|
|
|
cursor.Close()
|
|
|
|
for _, entry := range toDelete {
|
|
if err := iltxn.Del(index, entry.key, entry.val); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
toDelete = toDelete[:0]
|
|
}
|
|
|
|
return nil
|
|
}
|