diff --git a/eventstore/cmd/eventstore/main.go b/eventstore/cmd/eventstore/main.go index 18313b5..adf293f 100644 --- a/eventstore/cmd/eventstore/main.go +++ b/eventstore/cmd/eventstore/main.go @@ -86,7 +86,8 @@ var app = &cli.Command{ end = db.Close case "mmm": var err error - if db, err, end = doMmmInit(path); err != nil { + readonly := c.Args().First() == "query" || c.Args().First() == "count" + if db, err, end = doMmmInit(path, readonly); err != nil { if end != nil { end() } diff --git a/eventstore/cmd/eventstore/main_mmm.go b/eventstore/cmd/eventstore/main_mmm.go index 09e37f9..21dc9bc 100644 --- a/eventstore/cmd/eventstore/main_mmm.go +++ b/eventstore/cmd/eventstore/main_mmm.go @@ -11,13 +11,14 @@ import ( "github.com/rs/zerolog" ) -func doMmmInit(path string) (eventstore.Store, error, func()) { +func doMmmInit(path string, readonly bool) (eventstore.Store, error, func()) { logger := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { w.Out = os.Stderr })) mmmm := mmm.MultiMmapManager{ - Dir: filepath.Dir(path), - Logger: &logger, + Dir: filepath.Dir(path), + Logger: &logger, + ReadOnly: readonly, } if err := mmmm.Init(); err != nil { return nil, err, nil diff --git a/eventstore/mmm/delete.go b/eventstore/mmm/delete.go index 967f416..c015507 100644 --- a/eventstore/mmm/delete.go +++ b/eventstore/mmm/delete.go @@ -11,6 +11,10 @@ import ( ) func (il *IndexingLayer) DeleteEvent(id nostr.ID) error { + if il.mmmm.ReadOnly { + return ReadOnly + } + il.mmmm.writeMutex.Lock() defer il.mmmm.writeMutex.Unlock() diff --git a/eventstore/mmm/mmmm.go b/eventstore/mmm/mmmm.go index 22efab6..417efca 100644 --- a/eventstore/mmm/mmmm.go +++ b/eventstore/mmm/mmmm.go @@ -2,6 +2,7 @@ package mmm import ( "encoding/binary" + "errors" "fmt" "os" "path/filepath" @@ -20,9 +21,12 @@ type mmap []byte func (_ mmap) String() string { return "" } +var ReadOnly = errors.New("mmm in read-only mode") + type MultiMmapManager struct { - Dir string - Logger *zerolog.Logger + Dir string + Logger *zerolog.Logger + ReadOnly bool layers IndexingLayers @@ -62,13 +66,15 @@ func (b *MultiMmapManager) Init() error { return fmt.Errorf("failed to create directory %s: %w", dbpath, err) } - // create lockfile to prevent multiple instances - lockfilePath := filepath.Join(b.Dir, "mmmm.lock") - if _, err := os.OpenFile(lockfilePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644); err != nil { - if os.IsExist(err) { - return fmt.Errorf("database at %s is already in use by another instance", b.Dir) + if !b.ReadOnly { + // create lockfile to prevent multiple instances + lockfilePath := filepath.Join(b.Dir, "mmmm.lock") + if _, err := os.OpenFile(lockfilePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644); err != nil { + if os.IsExist(err) { + return fmt.Errorf("database at %s is already in use by another instance", b.Dir) + } + return fmt.Errorf("failed to create lockfile %s: %w", lockfilePath, err) } - return fmt.Errorf("failed to create lockfile %s: %w", lockfilePath, err) } // open a huge mmapped file @@ -131,19 +137,21 @@ func (b *MultiMmapManager) Init() error { b.indexId = dbi } - // scan index table to calculate free ranges from used positions - b.freeRanges, err = b.gatherFreeRanges(txn) - if err != nil { - return err - } - - logOp := b.Logger.Debug() - for _, pos := range b.freeRanges { - if pos.size > 20 { - logOp = logOp.Uint32(fmt.Sprintf("%d", pos.start), pos.size) + if !b.ReadOnly { + // scan index table to calculate free ranges from used positions + b.freeRanges, err = b.gatherFreeRanges(txn) + if err != nil { + return err } + + logOp := b.Logger.Debug() + for _, pos := range b.freeRanges { + if pos.size > 20 { + logOp = logOp.Uint32(fmt.Sprintf("%d", pos.start), pos.size) + } + } + logOp.Msg("calculated free ranges from index scan") } - logOp.Msg("calculated free ranges from index scan") return nil }); err != nil { @@ -162,34 +170,52 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) { name: name, } - err := b.lmdbEnv.Update(func(txn *lmdb.Txn) error { - txn.RawRead = true + var err error + if b.ReadOnly { + err = b.lmdbEnv.View(func(txn *lmdb.Txn) error { + txn.RawRead = true - nameb := []byte(name) - if idv, err := txn.Get(b.knownLayers, nameb); lmdb.IsNotFound(err) { - if id, err := b.getNextAvailableLayerId(txn); err != nil { - return fmt.Errorf("failed to reserve a layer id for %s: %w", name, err) + nameb := []byte(name) + if idv, err := txn.Get(b.knownLayers, nameb); lmdb.IsNotFound(err) { + if id, err := b.getNextAvailableLayerId(txn); err != nil { + return fmt.Errorf("failed to reserve a layer id for %s: %w", name, err) + } else { + il.id = id + } + + if err := il.Init(); err != nil { + return fmt.Errorf("failed to init new layer %s: %w", name, err) + } + + return txn.Put(b.knownLayers, []byte(name), binary.BigEndian.AppendUint16(nil, il.id), 0) + } else if err == nil { + il.id = binary.BigEndian.Uint16(idv) + + if err := il.Init(); err != nil { + return fmt.Errorf("failed to init old layer %s: %w", name, err) + } + + return nil } else { - il.id = id + return err } + }) + } else { + err = b.lmdbEnv.Update(func(txn *lmdb.Txn) error { + txn.RawRead = true - if err := il.Init(); err != nil { - return fmt.Errorf("failed to init new layer %s: %w", name, err) + nameb := []byte(name) + if idv, err := txn.Get(b.knownLayers, nameb); err == nil { + if err := il.Init(); err != nil { + return fmt.Errorf("failed to init read-only layer %s: %w", name, err) + } + il.id = binary.BigEndian.Uint16(idv) + return nil + } else { + return err } - - return txn.Put(b.knownLayers, []byte(name), binary.BigEndian.AppendUint16(nil, il.id), 0) - } else if err == nil { - il.id = binary.BigEndian.Uint16(idv) - - if err := il.Init(); err != nil { - return fmt.Errorf("failed to init old layer %s: %w", name, err) - } - - return nil - } else { - return err - } - }) + }) + } if err != nil { return nil, err } @@ -199,6 +225,10 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) { } func (b *MultiMmapManager) DropLayer(name string) error { + if b.ReadOnly { + return ReadOnly + } + b.writeMutex.Lock() defer b.writeMutex.Unlock() diff --git a/eventstore/mmm/replace.go b/eventstore/mmm/replace.go index 46cc8a9..57657b3 100644 --- a/eventstore/mmm/replace.go +++ b/eventstore/mmm/replace.go @@ -11,6 +11,10 @@ import ( ) func (il *IndexingLayer) ReplaceEvent(evt nostr.Event) error { + if il.mmmm.ReadOnly { + return ReadOnly + } + il.mmmm.writeMutex.Lock() defer il.mmmm.writeMutex.Unlock() diff --git a/eventstore/mmm/save.go b/eventstore/mmm/save.go index f30df91..9fa55b7 100644 --- a/eventstore/mmm/save.go +++ b/eventstore/mmm/save.go @@ -16,6 +16,10 @@ import ( ) func (il *IndexingLayer) SaveEvent(evt nostr.Event) error { + if il.mmmm.ReadOnly { + return ReadOnly + } + il.mmmm.writeMutex.Lock() defer il.mmmm.writeMutex.Unlock()