kevo/pkg/replication/engine_applier.go

145 lines
4.1 KiB
Go

package replication
import (
"fmt"
"github.com/KevoDB/kevo/pkg/common/log"
"github.com/KevoDB/kevo/pkg/engine/interfaces"
"github.com/KevoDB/kevo/pkg/wal"
)
// EngineApplier implements the WALEntryApplier interface for applying
// WAL entries to a database engine.
type EngineApplier struct {
engine interfaces.Engine
}
// NewEngineApplier creates a new engine applier
func NewEngineApplier(engine interfaces.Engine) *EngineApplier {
return &EngineApplier{
engine: engine,
}
}
// Apply applies a WAL entry to the engine through its API
// This bypasses the read-only check for replication purposes
func (e *EngineApplier) Apply(entry *wal.Entry) error {
log.Info("Replica applying WAL entry through engine API: seq=%d, type=%d, key=%s",
entry.SequenceNumber, entry.Type, string(entry.Key))
// Check if engine is in read-only mode
isReadOnly := false
if checker, ok := e.engine.(interface{ IsReadOnly() bool }); ok {
isReadOnly = checker.IsReadOnly()
}
// Handle application based on read-only status and operation type
if isReadOnly {
return e.applyInReadOnlyMode(entry)
}
return e.applyInNormalMode(entry)
}
// applyInReadOnlyMode applies a WAL entry in read-only mode
func (e *EngineApplier) applyInReadOnlyMode(entry *wal.Entry) error {
log.Info("Applying entry in read-only mode: seq=%d", entry.SequenceNumber)
switch entry.Type {
case wal.OpTypePut:
// Try internal interface first
if putter, ok := e.engine.(interface{ PutInternal(key, value []byte) error }); ok {
return putter.PutInternal(entry.Key, entry.Value)
}
// Try temporarily disabling read-only mode
if setter, ok := e.engine.(interface{ SetReadOnly(bool) }); ok {
setter.SetReadOnly(false)
err := e.engine.Put(entry.Key, entry.Value)
setter.SetReadOnly(true)
return err
}
// Fall back to normal operation which may fail
return e.engine.Put(entry.Key, entry.Value)
case wal.OpTypeDelete:
// Try internal interface first
if deleter, ok := e.engine.(interface{ DeleteInternal(key []byte) error }); ok {
return deleter.DeleteInternal(entry.Key)
}
// Try temporarily disabling read-only mode
if setter, ok := e.engine.(interface{ SetReadOnly(bool) }); ok {
setter.SetReadOnly(false)
err := e.engine.Delete(entry.Key)
setter.SetReadOnly(true)
return err
}
// Fall back to normal operation which may fail
return e.engine.Delete(entry.Key)
case wal.OpTypeBatch:
// Try internal interface first
if batcher, ok := e.engine.(interface {
ApplyBatchInternal(entries []*wal.Entry) error
}); ok {
return batcher.ApplyBatchInternal([]*wal.Entry{entry})
}
// Try temporarily disabling read-only mode
if setter, ok := e.engine.(interface{ SetReadOnly(bool) }); ok {
setter.SetReadOnly(false)
err := e.engine.ApplyBatch([]*wal.Entry{entry})
setter.SetReadOnly(true)
return err
}
// Fall back to normal operation which may fail
return e.engine.ApplyBatch([]*wal.Entry{entry})
case wal.OpTypeMerge:
// Handle merge as a put operation for compatibility
if setter, ok := e.engine.(interface{ SetReadOnly(bool) }); ok {
setter.SetReadOnly(false)
err := e.engine.Put(entry.Key, entry.Value)
setter.SetReadOnly(true)
return err
}
return e.engine.Put(entry.Key, entry.Value)
default:
return fmt.Errorf("unsupported WAL entry type: %d", entry.Type)
}
}
// applyInNormalMode applies a WAL entry in normal mode
func (e *EngineApplier) applyInNormalMode(entry *wal.Entry) error {
log.Info("Applying entry in normal mode: seq=%d", entry.SequenceNumber)
switch entry.Type {
case wal.OpTypePut:
return e.engine.Put(entry.Key, entry.Value)
case wal.OpTypeDelete:
return e.engine.Delete(entry.Key)
case wal.OpTypeBatch:
return e.engine.ApplyBatch([]*wal.Entry{entry})
case wal.OpTypeMerge:
// Handle merge as a put operation for compatibility
return e.engine.Put(entry.Key, entry.Value)
default:
return fmt.Errorf("unsupported WAL entry type: %d", entry.Type)
}
}
// Sync ensures all applied entries are persisted
func (e *EngineApplier) Sync() error {
// Force a flush of in-memory tables to ensure durability
return e.engine.FlushImMemTables()
}