mmm: read-only mode.

This commit is contained in:
fiatjaf
2026-01-18 17:56:59 -03:00
parent df64c5b6ec
commit b559828c72
6 changed files with 90 additions and 46 deletions

View File

@@ -86,7 +86,8 @@ var app = &cli.Command{
end = db.Close end = db.Close
case "mmm": case "mmm":
var err error 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 { if end != nil {
end() end()
} }

View File

@@ -11,13 +11,14 @@ import (
"github.com/rs/zerolog" "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) { logger := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.Out = os.Stderr w.Out = os.Stderr
})) }))
mmmm := mmm.MultiMmapManager{ mmmm := mmm.MultiMmapManager{
Dir: filepath.Dir(path), Dir: filepath.Dir(path),
Logger: &logger, Logger: &logger,
ReadOnly: readonly,
} }
if err := mmmm.Init(); err != nil { if err := mmmm.Init(); err != nil {
return nil, err, nil return nil, err, nil

View File

@@ -11,6 +11,10 @@ import (
) )
func (il *IndexingLayer) DeleteEvent(id nostr.ID) error { func (il *IndexingLayer) DeleteEvent(id nostr.ID) error {
if il.mmmm.ReadOnly {
return ReadOnly
}
il.mmmm.writeMutex.Lock() il.mmmm.writeMutex.Lock()
defer il.mmmm.writeMutex.Unlock() defer il.mmmm.writeMutex.Unlock()

View File

@@ -2,6 +2,7 @@ package mmm
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -20,9 +21,12 @@ type mmap []byte
func (_ mmap) String() string { return "<memory-mapped file>" } func (_ mmap) String() string { return "<memory-mapped file>" }
var ReadOnly = errors.New("mmm in read-only mode")
type MultiMmapManager struct { type MultiMmapManager struct {
Dir string Dir string
Logger *zerolog.Logger Logger *zerolog.Logger
ReadOnly bool
layers IndexingLayers layers IndexingLayers
@@ -62,6 +66,7 @@ func (b *MultiMmapManager) Init() error {
return fmt.Errorf("failed to create directory %s: %w", dbpath, err) return fmt.Errorf("failed to create directory %s: %w", dbpath, err)
} }
if !b.ReadOnly {
// create lockfile to prevent multiple instances // create lockfile to prevent multiple instances
lockfilePath := filepath.Join(b.Dir, "mmmm.lock") 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 _, err := os.OpenFile(lockfilePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644); err != nil {
@@ -70,6 +75,7 @@ func (b *MultiMmapManager) Init() error {
} }
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 // open a huge mmapped file
b.mmapfPath = filepath.Join(b.Dir, "events") b.mmapfPath = filepath.Join(b.Dir, "events")
@@ -131,6 +137,7 @@ func (b *MultiMmapManager) Init() error {
b.indexId = dbi b.indexId = dbi
} }
if !b.ReadOnly {
// scan index table to calculate free ranges from used positions // scan index table to calculate free ranges from used positions
b.freeRanges, err = b.gatherFreeRanges(txn) b.freeRanges, err = b.gatherFreeRanges(txn)
if err != nil { if err != nil {
@@ -144,6 +151,7 @@ func (b *MultiMmapManager) Init() error {
} }
} }
logOp.Msg("calculated free ranges from index scan") logOp.Msg("calculated free ranges from index scan")
}
return nil return nil
}); err != nil { }); err != nil {
@@ -162,7 +170,9 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) {
name: name, name: name,
} }
err := b.lmdbEnv.Update(func(txn *lmdb.Txn) error { var err error
if b.ReadOnly {
err = b.lmdbEnv.View(func(txn *lmdb.Txn) error {
txn.RawRead = true txn.RawRead = true
nameb := []byte(name) nameb := []byte(name)
@@ -190,6 +200,22 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) {
return err return err
} }
}) })
} else {
err = b.lmdbEnv.Update(func(txn *lmdb.Txn) error {
txn.RawRead = true
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
}
})
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -199,6 +225,10 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) {
} }
func (b *MultiMmapManager) DropLayer(name string) error { func (b *MultiMmapManager) DropLayer(name string) error {
if b.ReadOnly {
return ReadOnly
}
b.writeMutex.Lock() b.writeMutex.Lock()
defer b.writeMutex.Unlock() defer b.writeMutex.Unlock()

View File

@@ -11,6 +11,10 @@ import (
) )
func (il *IndexingLayer) ReplaceEvent(evt nostr.Event) error { func (il *IndexingLayer) ReplaceEvent(evt nostr.Event) error {
if il.mmmm.ReadOnly {
return ReadOnly
}
il.mmmm.writeMutex.Lock() il.mmmm.writeMutex.Lock()
defer il.mmmm.writeMutex.Unlock() defer il.mmmm.writeMutex.Unlock()

View File

@@ -16,6 +16,10 @@ import (
) )
func (il *IndexingLayer) SaveEvent(evt nostr.Event) error { func (il *IndexingLayer) SaveEvent(evt nostr.Event) error {
if il.mmmm.ReadOnly {
return ReadOnly
}
il.mmmm.writeMutex.Lock() il.mmmm.writeMutex.Lock()
defer il.mmmm.writeMutex.Unlock() defer il.mmmm.writeMutex.Unlock()