All Downloads are FREE. Search and download functionalities are using the official Maven repository.

swaydb.core.CoreInitializer.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2020 Simer JS Plaha ([email protected] - @simerplaha)
 *
 * This file is a part of SwayDB.
 *
 * SwayDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * SwayDB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with SwayDB. If not, see .
 *
 * Additional permission under the GNU Affero GPL version 3 section 7:
 * If you modify this Program or any covered work, only by linking or combining
 * it with separate works, the licensors of this Program grant you additional
 * permission to convey the resulting work.
 */

package swaydb.core

import java.util.function.Supplier

import com.typesafe.scalalogging.LazyLogging
import swaydb.Error.Level.ExceptionHandler
import swaydb.core.actor.ByteBufferSweeper.ByteBufferSweeperActor
import swaydb.core.actor.{ByteBufferSweeper, FileSweeper, MemorySweeper}
import swaydb.core.build.{Build, BuildValidator}
import swaydb.core.function.FunctionStore
import swaydb.core.io.file.Effect._
import swaydb.core.io.file.{BlockCache, ForceSaveApplier}
import swaydb.core.level.compaction._
import swaydb.core.level.compaction.throttle.{ThrottleCompactor, ThrottleState}
import swaydb.core.level.zero.LevelZero
import swaydb.core.level.{Level, LevelCloser, NextLevel, TrashLevel}
import swaydb.core.segment.ThreadReadState
import swaydb.core.segment.format.a.block
import swaydb.core.segment.format.a.block.bloomfilter.BloomFilterBlock
import swaydb.core.segment.format.a.block.segment.SegmentBlock
import swaydb.core.segment.format.a.block.sortedindex.SortedIndexBlock
import swaydb.core.segment.format.a.block.values.ValuesBlock
import swaydb.data.compaction.CompactionExecutionContext
import swaydb.data.config._
import swaydb.data.order.{KeyOrder, TimeOrder}
import swaydb.data.sequencer.Sequencer
import swaydb.data.slice.Slice
import swaydb.data.storage.{AppendixStorage, Level0Storage, LevelStorage}
import swaydb.data.{Atomic, NonEmptyList, OptimiseWrites}
import swaydb.{ActorRef, ActorWire, Bag, Error, Glass, IO}

import scala.sys.ShutdownHookThread

/**
 * Creates all configured Levels via [[ConfigWizard]] instances and starts compaction.
 */
private[core] object CoreInitializer extends LazyLogging {

  /**
   * Based on the configuration returns execution context for the Level.
   */
  def executionContext(levelConfig: LevelConfig): Option[CompactionExecutionContext] =
    levelConfig match {
      case TrashLevelConfig =>
        None

      case config: MemoryLevelConfig =>
        Some(config.compactionExecutionContext)

      case config: PersistentLevelConfig =>
        Some(config.compactionExecutionContext)
    }

  def executionContexts(config: SwayDBConfig): List[CompactionExecutionContext] =
    List(config.level0.compactionExecutionContext) ++
      executionContext(config.level1).toList ++
      config.otherLevels.flatMap(executionContext)

  /**
   * Boots up compaction Actor and start listening to changes in levels.
   */
  def initialiseCompaction(zero: LevelZero,
                           executionContexts: List[CompactionExecutionContext])(implicit compactionStrategy: Compactor[ThrottleState]): IO[Error.Level, NonEmptyList[ActorWire[Compactor[ThrottleState], ThrottleState]]] =
    compactionStrategy.createAndListen(
      zero = zero,
      executionContexts = executionContexts
    )

  def sendInitialWakeUp(compactor: ActorWire[Compactor[ThrottleState], ThrottleState]): Unit =
    compactor send {
      (impl, state, self) =>
        impl.wakeUp(
          state = state,
          forwardCopyOnAllLevels = true,
          self = self
        )
    }

  def addShutdownHook[BAG[_]](core: Core[BAG]): ShutdownHookThread =
    sys.addShutdownHook {
      if (core.state != CoreState.Closed)
        core.closeWithBag[Glass]()
    }

  /**
   * Initialises Core/Levels. To see full documentation for each input parameter see the website - http://swaydb.io/configurations/.
   *
   * @param config           configuration used for initialisations which is created via [[ConfigWizard]]
   * @param enableTimer      if true initialises the timer folder. This is only required if the database has functions enabled.
   * @param cacheKeyValueIds if true, will cache the 3000+ key-values in-memory instead of performing binary search for each search key-value id.
   *                         Set this to true to boost performance and reduce IOps.
   * @param fileCache        Controls when files are closed & deleted. See the configuration documentation - http://swaydb.io/configurations/fileCache/
   * @param threadStateCache Each thread is assigned a small cache to boost read performance. This can be optionally enabled. See
   *                         http://swaydb.io/configurations/threadStateCache/
   * @param memoryCache      Configures how in-memory caches should process read bytes and parsed key-values. See - http://swaydb.io/configurations/memoryCache/
   * @param keyOrder         Defines the sort order for keys. See documentation on website.
   * @param timeOrder        Defines the order in which a single key-value's updates are applied.
   * @param functionStore    Stores all registered functions.
   * @return
   */
  def apply(config: SwayDBConfig,
            enableTimer: Boolean,
            cacheKeyValueIds: Boolean,
            fileCache: FileCache.On,
            threadStateCache: ThreadStateCache,
            memoryCache: MemoryCache)(implicit keyOrder: KeyOrder[Slice[Byte]],
                                      timeOrder: TimeOrder[Slice[Byte]],
                                      functionStore: FunctionStore,
                                      buildValidator: BuildValidator): IO[swaydb.Error.Boot, Core[Glass]] = {
    val validationResult =
      config.level0.storage match {
        case Level0Storage.Memory =>
          IO.unit

        case Level0Storage.Persistent(_, dir, _) =>
          Build.validateOrCreate(dir)
      }

    validationResult match {
      case IO.Right(_) =>

        implicit val fileSweeper: ActorRef[FileSweeper.Command, Unit] =
          FileSweeper(fileCache)

        val memorySweeper: Option[MemorySweeper.On] =
          MemorySweeper(memoryCache)

        implicit val blockCache: Option[BlockCache.State] =
          memorySweeper flatMap BlockCache.init

        implicit val keyValueMemorySweeper: Option[MemorySweeper.KeyValue] =
          memorySweeper flatMap {
            enabled: MemorySweeper.On =>
              enabled match {
                case sweeper: MemorySweeper.All =>
                  Some(sweeper)

                case sweeper: MemorySweeper.KeyValueSweeper =>
                  Some(sweeper)

                case _: MemorySweeper.BlockSweeper =>
                  None
              }
          }

        implicit val compactionStrategy: Compactor[ThrottleState] =
          ThrottleCompactor

        implicit val bufferSweeper: ByteBufferSweeperActor =
          ByteBufferSweeper()(fileSweeper.executionContext)

        val contexts = executionContexts(config)

        lazy val memoryLevelPath = MemoryPathGenerator.next()

        def createLevel(id: Long,
                        nextLevel: Option[NextLevel],
                        config: LevelConfig): IO[swaydb.Error.Level, NextLevel] =
          config match {
            case config: MemoryLevelConfig =>
              implicit val forceSaveApplier: ForceSaveApplier = ForceSaveApplier.Off

              Level(
                bloomFilterConfig = BloomFilterBlock.Config.disabled,
                hashIndexConfig = block.hashindex.HashIndexBlock.Config.disabled,
                binarySearchIndexConfig = block.binarysearch.BinarySearchIndexBlock.Config.disabled,
                sortedIndexConfig = SortedIndexBlock.Config.disabled,
                valuesConfig = ValuesBlock.Config.disabled,
                segmentConfig =
                  SegmentBlock.Config(
                    fileOpenIOStrategy = IOStrategy.AsyncIO(false),
                    blockIOStrategy = _ => IOStrategy.ConcurrentIO(false),
                    cacheBlocksOnCreate = false,
                    minSize = config.minSegmentSize,
                    maxCount = config.maxKeyValuesPerSegment,
                    pushForward = config.copyForward,
                    mmap = MMAP.Off(ForceSave.Off),
                    deleteEventually = config.deleteSegmentsEventually,
                    compressions = _ => Seq.empty
                  ),
                levelStorage = LevelStorage.Memory(dir = memoryLevelPath.resolve(id.toString)),
                appendixStorage = AppendixStorage.Memory,
                nextLevel = nextLevel,
                throttle = config.throttle
              )

            case config: PersistentLevelConfig =>
              implicit val forceSaveApplier: ForceSaveApplier = ForceSaveApplier.On

              Level(
                bloomFilterConfig = BloomFilterBlock.Config(config = config.mightContainIndex),
                hashIndexConfig = block.hashindex.HashIndexBlock.Config(config = config.randomSearchIndex),
                binarySearchIndexConfig = block.binarysearch.BinarySearchIndexBlock.Config(config = config.binarySearchIndex),
                sortedIndexConfig = SortedIndexBlock.Config(config.sortedKeyIndex),
                valuesConfig = ValuesBlock.Config(config.valuesConfig),
                segmentConfig = SegmentBlock.Config(config.segmentConfig),
                levelStorage =
                  LevelStorage.Persistent(
                    dir = config.dir.resolve(id.toString),
                    otherDirs = config.otherDirs.map(dir => dir.copy(path = dir.path.resolve(id.toString)))
                  ),
                appendixStorage = AppendixStorage.Persistent(config.mmapAppendix, config.appendixFlushCheckpointSize),
                nextLevel = nextLevel,
                throttle = config.throttle
              )

            case TrashLevelConfig =>
              IO.Right(TrashLevel)
          }

        def createLevels(levelConfigs: List[LevelConfig],
                         previousLowerLevel: Option[NextLevel]): IO[swaydb.Error.Level, Core[Glass]] =
          levelConfigs match {
            case Nil =>
              implicit val forceSaveApplier: ForceSaveApplier = ForceSaveApplier.On

              createLevel(
                id = 1,
                nextLevel = previousLowerLevel,
                config = config.level1
              ) flatMap {
                level1 =>
                  val coreState = CoreState()

                  implicit val optimiseWrites: OptimiseWrites = config.level0.optimiseWrites
                  implicit val atomic: Atomic = config.level0.atomic

                  LevelZero(
                    mapSize = config.level0.mapSize,
                    appliedFunctionsMapSize = config.level0.appliedFunctionsMapSize,
                    clearAppliedFunctionsOnBoot = config.level0.clearAppliedFunctionsOnBoot,
                    storage = config.level0.storage,
                    enableTimer = enableTimer,
                    cacheKeyValueIds = cacheKeyValueIds,
                    coreState = coreState,
                    nextLevel = Some(level1),
                    acceleration = config.level0.acceleration,
                    throttle = config.level0.throttle
                  ) flatMap {
                    zero: LevelZero =>
                      initialiseCompaction(
                        zero = zero,
                        executionContexts = contexts
                      ) map {
                        implicit compactor =>

                          //trigger initial wakeUp.
                          sendInitialWakeUp(compactor.head)

                          val readStates: ThreadLocal[ThreadReadState] =
                            ThreadLocal.withInitial[ThreadReadState] {
                              new Supplier[ThreadReadState] {
                                override def get(): ThreadReadState =
                                  threadStateCache match {
                                    case ThreadStateCache.Limit(hashMapMaxSize, maxProbe) =>
                                      ThreadReadState.limitHashMap(
                                        maxSize = hashMapMaxSize,
                                        probe = maxProbe
                                      )

                                    case ThreadStateCache.NoLimit =>
                                      ThreadReadState.hashMap()

                                    case ThreadStateCache.Off =>
                                      ThreadReadState.limitHashMap(
                                        maxSize = 0,
                                        probe = 0
                                      )
                                  }
                              }
                            }

                          val core =
                            new Core[Glass](
                              zero = zero,
                              coreState = coreState,
                              threadStateCache = threadStateCache,
                              sequencer = Sequencer.synchronised(Bag.glass),
                              readStates = readStates
                            )

                          addShutdownHook(core = core)

                          core
                      }
                  }
              }

            case lowestLevelConfig :: upperLevelConfigs =>

              val levelNumber: Long =
                previousLowerLevel
                  .flatMap(_.pathDistributor.headOption.map(_.path.folderId - 1))
                  .getOrElse(levelConfigs.size + 1)

              createLevel(levelNumber, previousLowerLevel, lowestLevelConfig) flatMap {
                newLowerLevel =>
                  createLevels(upperLevelConfigs, Some(newLowerLevel))
              }
          }

        logger.info(s"Booting ${config.otherLevels.size + 2} Levels.")

        /**
         * Convert [[swaydb.Error.Level]] to [[swaydb.Error]]
         */
        createLevels(config.otherLevels.reverse, None) match {
          case IO.Right(core) =>
            IO[swaydb.Error.Boot, Core[Glass]](core)

          case IO.Left(createError) =>
            IO(LevelCloser.close[Glass]()) match {
              case IO.Right(_) =>
                IO.failed[swaydb.Error.Boot, Core[Glass]](createError.exception)

              case IO.Left(closeError) =>
                val createException = createError.exception
                logger.error("Failed to create", createException)
                logger.error("Failed to close", closeError.exception)
                IO.failed[swaydb.Error.Boot, Core[Glass]](createException)
            }
        }

      case IO.Left(error) =>
        IO.failed[swaydb.Error.Boot, Core[Glass]](error.exception)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy