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
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()
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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 "<memory-mapped file>" }
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()

View File

@@ -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()

View File

@@ -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()