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

swaydb.core.segment.PersistentSegmentMany.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.segment

import java.nio.file.Path

import com.typesafe.scalalogging.LazyLogging
import swaydb.Error.Segment.ExceptionHandler
import swaydb.core.actor.ByteBufferSweeper.ByteBufferSweeperActor
import swaydb.core.actor.FileSweeper.FileSweeperActor
import swaydb.core.actor.{FileSweeper, MemorySweeper}
import swaydb.core.data._
import swaydb.core.function.FunctionStore
import swaydb.core.io.file.{BlockCache, DBFile, Effect, ForceSaveApplier}
import swaydb.core.level.PathsDistributor
import swaydb.core.segment.format.a.block.binarysearch.BinarySearchIndexBlock
import swaydb.core.segment.format.a.block.bloomfilter.BloomFilterBlock
import swaydb.core.segment.format.a.block.hashindex.HashIndexBlock
import swaydb.core.segment.format.a.block.reader.BlockRefReader
import swaydb.core.segment.format.a.block.segment.SegmentBlock
import swaydb.core.segment.format.a.block.segment.data.{TransientSegment, TransientSegmentSerialiser}
import swaydb.core.segment.format.a.block.sortedindex.SortedIndexBlock
import swaydb.core.segment.format.a.block.values.ValuesBlock
import swaydb.core.util._
import swaydb.core.util.skiplist.{SkipList, SkipListTreeMap}
import swaydb.data.cache.{Cache, CacheNoIO}
import swaydb.data.config.{Dir, IOAction}
import swaydb.data.order.{KeyOrder, TimeOrder}
import swaydb.data.slice.{Slice, SliceOption}
import swaydb.data.{MaxKey, Reserve}
import swaydb.{Error, IO}

import scala.collection.mutable.ListBuffer
import scala.concurrent.duration.Deadline
import scala.jdk.CollectionConverters._

protected case object PersistentSegmentMany {

  val formatId: Byte = 127
  val formatIdSlice: Slice[Byte] = Slice(formatId)

  def apply(file: DBFile,
            createdInLevel: Int,
            segment: TransientSegment.Many)(implicit keyOrder: KeyOrder[Slice[Byte]],
                                            timeOrder: TimeOrder[Slice[Byte]],
                                            functionStore: FunctionStore,
                                            keyValueMemorySweeper: Option[MemorySweeper.KeyValue],
                                            blockCache: Option[BlockCache.State],
                                            fileSweeper: FileSweeperActor,
                                            bufferCleaner: ByteBufferSweeperActor,
                                            segmentIO: SegmentIO,
                                            forceSaveApplier: ForceSaveApplier): PersistentSegmentMany = {
    val initial =
      if (segment.segments.isEmpty) {
        None
      } else {
        val skipList = SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef](Slice.Null, SegmentRef.Null)
        implicit val blockMemorySweeper: Option[MemorySweeper.Block] = blockCache.map(_.sweeper)

        val firstSegmentOffset =
          segment.headerSize +
            segment.segments.head.segmentSize

        //drop head ignoring the list block.
        segment
          .segments
          .dropHead()
          .foldLeft(firstSegmentOffset) {
            case (offset, one) =>
              val thisSegmentSize = one.segmentSize

              val blockRef =
                BlockRefReader(
                  file = file,
                  start = offset,
                  fileSize = thisSegmentSize
                )

              val ref =
                SegmentRef(
                  path = file.path.resolve(s".ref.$offset"),
                  minKey = one.minKey,
                  maxKey = one.maxKey,
                  blockRef = blockRef,
                  segmentIO = segmentIO,
                  valuesReaderCacheable = one.valuesUnblockedReader,
                  sortedIndexReaderCacheable = one.sortedIndexUnblockedReader,
                  hashIndexReaderCacheable = one.hashIndexUnblockedReader,
                  binarySearchIndexReaderCacheable = one.binarySearchUnblockedReader,
                  bloomFilterReaderCacheable = one.bloomFilterUnblockedReader,
                  footerCacheable = one.footerUnblocked
                )

              skipList.put(one.minKey, ref)

              offset + thisSegmentSize
          }

        Some(skipList)
      }

    PersistentSegmentMany(
      file = file,
      createdInLevel = createdInLevel,
      minKey = segment.minKey,
      maxKey = segment.maxKey,
      minMaxFunctionId = segment.minMaxFunctionId,
      segmentSize = segment.segmentSize,
      nearestExpiryDeadline = segment.nearestPutDeadline,
      initial = initial
    )
  }

  def apply(file: DBFile,
            segmentSize: Int,
            createdInLevel: Int,
            minKey: Slice[Byte],
            maxKey: MaxKey[Slice[Byte]],
            minMaxFunctionId: Option[MinMax[Slice[Byte]]],
            nearestExpiryDeadline: Option[Deadline],
            initial: Option[SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef]])(implicit keyOrder: KeyOrder[Slice[Byte]],
                                                                                                            timeOrder: TimeOrder[Slice[Byte]],
                                                                                                            functionStore: FunctionStore,
                                                                                                            keyValueMemorySweeper: Option[MemorySweeper.KeyValue],
                                                                                                            blockCache: Option[BlockCache.State],
                                                                                                            fileSweeper: FileSweeperActor,
                                                                                                            bufferCleaner: ByteBufferSweeperActor,
                                                                                                            segmentIO: SegmentIO,
                                                                                                            forceSaveApplier: ForceSaveApplier): PersistentSegmentMany = {

    implicit val blockCacheMemorySweeper: Option[MemorySweeper.Block] = blockCache.map(_.sweeper)

    val fileBlockRef: BlockRefReader[SegmentBlock.Offset] =
      BlockRefReader(
        file = file,
        start = 1,
        fileSize = segmentSize - 1
      )

    val segments =
      Cache.deferredIO[swaydb.Error.Segment, swaydb.Error.ReservedResource, Unit, SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef]](
        initial = initial,
        strategy = _ => segmentIO.segmentBlockIO(IOAction.ReadDataOverview).forceCacheOnAccess,
        reserveError = swaydb.Error.ReservedResource(Reserve.free(name = s"${file.path}: ${this.productPrefix}"))
      )() {
        (_, _) =>
          IO {
            parseSkipList(
              file = file,
              minKey = minKey,
              maxKey = maxKey,
              fileBlockRef = fileBlockRef
            )
          }
      }

    new PersistentSegmentMany(
      file = file,
      createdInLevel = createdInLevel,
      minKey = minKey,
      maxKey = maxKey,
      minMaxFunctionId = minMaxFunctionId,
      segmentSize = segmentSize,
      nearestPutDeadline = nearestExpiryDeadline,
      segmentsCache = segments
    )
  }

  /**
   * Used for recovery only - [[swaydb.core.level.tool.AppendixRepairer]] - Not performance optimised.
   *
   * Used when Segment's information is unknown.
   */
  def apply(file: DBFile)(implicit keyOrder: KeyOrder[Slice[Byte]],
                          timeOrder: TimeOrder[Slice[Byte]],
                          functionStore: FunctionStore,
                          keyValueMemorySweeper: Option[MemorySweeper.KeyValue],
                          blockCacheMemorySweeper: Option[MemorySweeper.Block],
                          blockCache: Option[BlockCache.State],
                          fileSweeper: FileSweeperActor,
                          bufferCleaner: ByteBufferSweeperActor,
                          segmentIO: SegmentIO,
                          forceSaveApplier: ForceSaveApplier): PersistentSegmentMany = {

    val fileExtension = Effect.fileExtension(file.path)

    if (fileExtension != Extension.Seg)
      throw new Exception(s"Invalid Segment file extension: $fileExtension")

    val fileBlockRef: BlockRefReader[SegmentBlock.Offset] =
      BlockRefReader(
        file = file,
        start = 1,
        fileSize = file.fileSize.toInt - 1
      )

    val listSegment =
      parseListSegment(
        file = file,
        minKey = null,
        maxKey = null,
        fileBlockRef = fileBlockRef
      )

    val footer = listSegment.getFooter()

    val segmentRefKeyValues =
      listSegment
        .iterator()
        .toList

    val deadlineFunctionId = DeadlineAndFunctionId(segmentRefKeyValues)

    val segmentRefs =
      parseSkipList(
        file = file,
        minKey = null,
        maxKey = null,
        fileBlockRef = fileBlockRef
      )

    val lastSegment =
      segmentRefs.last() match {
        case SegmentRef.Null =>
          throw new Exception("Empty List Segment read. List Segment are non-empty lists.")

        case ref: SegmentRef =>
          ref
      }

    val lastKeyValue =
      lastSegment
        .iterator()
        .foldLeft(Persistent.Null: PersistentOption) {
          case (_, next) =>
            next
        }

    val maxKey =
      lastKeyValue match {
        case fixed: Persistent.Fixed =>
          MaxKey.Fixed(fixed.key.unslice())

        case range: Persistent.Range =>
          MaxKey.Range(range.fromKey.unslice(), range.toKey.unslice())

        case Persistent.Null =>
          throw new Exception("Empty Segment read. Persisted Segments cannot be empty.")
      }

    PersistentSegmentMany(
      file = file,
      segmentSize = file.fileSize.toInt,
      createdInLevel = footer.createdInLevel,
      minKey = segmentRefKeyValues.head.key.unslice(),
      maxKey = maxKey,
      minMaxFunctionId = deadlineFunctionId.minMaxFunctionId.map(_.unslice()),
      nearestExpiryDeadline = deadlineFunctionId.nearestDeadline,
      //above parsed segmentRefs cannot be used here because
      //it's MaxKey.Range's minKey is set to the Segment's minKey
      //instead of the Segment's last range key-values minKey.
      initial = None
    )
  }

  private def parseSkipList(file: DBFile,
                            minKey: Slice[Byte],
                            maxKey: MaxKey[Slice[Byte]],
                            fileBlockRef: BlockRefReader[SegmentBlock.Offset])(implicit keyOrder: KeyOrder[Slice[Byte]],
                                                                               keyValueMemorySweeper: Option[MemorySweeper.KeyValue],
                                                                               blockCacheMemorySweeper: Option[MemorySweeper.Block],
                                                                               segmentIO: SegmentIO): SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef] = {
    val blockedReader: BlockRefReader[SegmentBlock.Offset] = fileBlockRef.copy()
    val listSegmentSize = blockedReader.readUnsignedInt()
    val listSegment = blockedReader.read(listSegmentSize)
    val listSegmentRef = BlockRefReader[SegmentBlock.Offset](listSegment)

    val segmentRef =
      SegmentRef(
        path = file.path,
        minKey = minKey,
        maxKey = maxKey,
        blockRef = listSegmentRef,
        segmentIO = segmentIO,
        valuesReaderCacheable = None,
        sortedIndexReaderCacheable = None,
        hashIndexReaderCacheable = None,
        binarySearchIndexReaderCacheable = None,
        bloomFilterReaderCacheable = None,
        footerCacheable = None
      )

    val skipList = SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef](Slice.Null, SegmentRef.Null)


    //this will also clear all the SegmentRef's
    //            blockCacheMemorySweeper foreach {
    //              cacheMemorySweeper =>
    //                cacheMemorySweeper.add(listSegmentSize, self)
    //            }

    val tailSegmentBytesFromOffset = blockedReader.getPosition
    val tailManySegmentsSize = fileBlockRef.size.toInt - tailSegmentBytesFromOffset

    var previousPath: Path = null
    var previousSegmentRef: SegmentRef = null

    segmentRef.iterator() foreach {
      keyValue =>
        val thisSegmentBlockRef =
          BlockRefReader[SegmentBlock.Offset](
            ref = fileBlockRef.copy(),
            start = tailSegmentBytesFromOffset,
            size = tailManySegmentsSize
          )

        val nextSegmentRef =
          keyValue match {
            case range: Persistent.Range =>
              TransientSegmentSerialiser.toSegmentRef(
                path = file.path,
                reader = thisSegmentBlockRef,
                range = range,
                valuesReaderCacheable = None,
                sortedIndexReaderCacheable = None,
                hashIndexReaderCacheable = None,
                binarySearchIndexReaderCacheable = None,
                bloomFilterReaderCacheable = None,
                footerCacheable = None
              )

            case put: Persistent.Put =>
              TransientSegmentSerialiser.toSegmentRef(
                path = file.path,
                reader = thisSegmentBlockRef,
                put = put,
                valuesReaderCacheable = None,
                sortedIndexReaderCacheable = None,
                hashIndexReaderCacheable = None,
                binarySearchIndexReaderCacheable = None,
                bloomFilterReaderCacheable = None,
                footerCacheable = None
              )

            case _: Persistent.Fixed =>
              throw new Exception("Non put key-value written to List segment")
          }

        val segmentRef =
          if (previousPath == nextSegmentRef.path)
            previousSegmentRef
          else
            nextSegmentRef

        previousPath = segmentRef.path
        previousSegmentRef = segmentRef

        skipList.put(segmentRef.minKey, segmentRef)
    }

    skipList
  }

  private def parseListSegment(file: DBFile,
                               minKey: Slice[Byte],
                               maxKey: MaxKey[Slice[Byte]],
                               fileBlockRef: BlockRefReader[SegmentBlock.Offset])(implicit keyOrder: KeyOrder[Slice[Byte]],
                                                                                  keyValueMemorySweeper: Option[MemorySweeper.KeyValue],
                                                                                  blockCacheMemorySweeper: Option[MemorySweeper.Block],
                                                                                  segmentIO: SegmentIO): SegmentRef = {
    val blockedReader: BlockRefReader[SegmentBlock.Offset] = fileBlockRef.copy()
    val listSegmentSize = blockedReader.readUnsignedInt()
    val listSegment = blockedReader.read(listSegmentSize)
    val listSegmentRef = BlockRefReader[SegmentBlock.Offset](listSegment)

    SegmentRef(
      path = file.path,
      minKey = minKey,
      maxKey = maxKey,
      blockRef = listSegmentRef,
      segmentIO = segmentIO,
      valuesReaderCacheable = None,
      sortedIndexReaderCacheable = None,
      hashIndexReaderCacheable = None,
      binarySearchIndexReaderCacheable = None,
      bloomFilterReaderCacheable = None,
      footerCacheable = None
    )
  }

}

protected case class PersistentSegmentMany(file: DBFile,
                                           createdInLevel: Int,
                                           minKey: Slice[Byte],
                                           maxKey: MaxKey[Slice[Byte]],
                                           minMaxFunctionId: Option[MinMax[Slice[Byte]]],
                                           segmentSize: Int,
                                           nearestPutDeadline: Option[Deadline],
                                           private[segment] val segmentsCache: Cache[Error.Segment, Unit, SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef]])(implicit keyOrder: KeyOrder[Slice[Byte]],
                                                                                                                                                                                          timeOrder: TimeOrder[Slice[Byte]],
                                                                                                                                                                                          functionStore: FunctionStore,
                                                                                                                                                                                          blockCache: Option[BlockCache.State],
                                                                                                                                                                                          fileSweeper: FileSweeperActor,
                                                                                                                                                                                          bufferCleaner: ByteBufferSweeperActor,
                                                                                                                                                                                          keyValueMemorySweeper: Option[MemorySweeper.KeyValue],
                                                                                                                                                                                          segmentIO: SegmentIO,
                                                                                                                                                                                          forceSaveApplier: ForceSaveApplier) extends PersistentSegment with LazyLogging {

  implicit val partialKeyOrder: KeyOrder[Persistent.Partial] = KeyOrder(Ordering.by[Persistent.Partial, Slice[Byte]](_.key)(keyOrder))
  implicit val persistentKeyOrder: KeyOrder[Persistent] = KeyOrder(Ordering.by[Persistent, Slice[Byte]](_.key)(keyOrder))
  implicit val segmentSearcher: SegmentSearcher = SegmentSearcher

  override def formatId: Byte = PersistentSegmentMany.formatId

  private def segments: SkipListTreeMap[SliceOption[Byte], SegmentRefOption, Slice[Byte], SegmentRef] =
    segmentsCache
      .value(())
      .get

  private val segmentRefsCache: CacheNoIO[Unit, Iterable[SegmentRef]] =
    Cache.noIO[Unit, Iterable[SegmentRef]](true, true, None) {
      (_, _) =>
        val uniqueRefs = ListBuffer.empty[SegmentRef]

        var previousPath: Path = null

        segments
          .values()
          .foreach {
            nextSegmentRef =>
              if (nextSegmentRef.path != previousPath) {
                uniqueRefs += nextSegmentRef
                previousPath = nextSegmentRef.path
              }
          }

        uniqueRefs
    }

  def segmentRefs: Iterable[SegmentRef] =
    segmentRefsCache.value(())

  def path = file.path

  override def close: Unit = {
    file.close()
    segmentsCache.clear()
    segmentRefsCache.clear()
  }

  def isOpen: Boolean =
    file.isOpen

  def isFileDefined =
    file.isFileDefined

  def deleteSegmentsEventually =
    fileSweeper send FileSweeper.Command.Delete(this)

  def delete: Unit = {
    logger.trace(s"{}: DELETING FILE", path)
    IO(file.delete()) onLeftSideEffect {
      failure =>
        logger.error(s"{}: Failed to delete Segment file.", path, failure.value.exception)
    } map {
      _ =>
        segmentsCache.clear()
    }
  }

  def copyTo(toPath: Path): Path =
    file copyTo toPath

  /**
   * Default targetPath is set to this [[PersistentSegmentOne]]'s parent directory.
   */
  def put(newKeyValues: Slice[KeyValue],
          removeDeletes: Boolean,
          createdInLevel: Int,
          valuesConfig: ValuesBlock.Config,
          sortedIndexConfig: SortedIndexBlock.Config,
          binarySearchIndexConfig: BinarySearchIndexBlock.Config,
          hashIndexConfig: HashIndexBlock.Config,
          bloomFilterConfig: BloomFilterBlock.Config,
          segmentConfig: SegmentBlock.Config,
          pathsDistributor: PathsDistributor = PathsDistributor(Seq(Dir(path.getParent, 1)), () => Seq()))(implicit idGenerator: IDGenerator): Slice[PersistentSegment] = {

    val transient: Iterable[TransientSegment] =
      SegmentRef.put(
        oldKeyValuesCount = getKeyValueCount(),
        oldKeyValues = iterator(),
        newKeyValues = newKeyValues,
        removeDeletes = removeDeletes,
        createdInLevel = createdInLevel,
        valuesConfig = valuesConfig,
        sortedIndexConfig = sortedIndexConfig,
        binarySearchIndexConfig = binarySearchIndexConfig,
        hashIndexConfig = hashIndexConfig,
        bloomFilterConfig = bloomFilterConfig,
        segmentConfig = segmentConfig
      )

    Segment.persistent(
      pathsDistributor = pathsDistributor,
      mmap = segmentConfig.mmap,
      createdInLevel = createdInLevel,
      segments = transient
    )
  }

  def refresh(removeDeletes: Boolean,
              createdInLevel: Int,
              valuesConfig: ValuesBlock.Config,
              sortedIndexConfig: SortedIndexBlock.Config,
              binarySearchIndexConfig: BinarySearchIndexBlock.Config,
              hashIndexConfig: HashIndexBlock.Config,
              bloomFilterConfig: BloomFilterBlock.Config,
              segmentConfig: SegmentBlock.Config,
              pathsDistributor: PathsDistributor = PathsDistributor(Seq(Dir(path.getParent, 1)), () => Seq()))(implicit idGenerator: IDGenerator): Slice[PersistentSegment] = {

    val transient: Iterable[TransientSegment] =
      SegmentRef.refreshForNewLevel(
        keyValues = iterator(),
        removeDeletes = removeDeletes,
        createdInLevel = createdInLevel,
        valuesConfig = valuesConfig,
        sortedIndexConfig = sortedIndexConfig,
        binarySearchIndexConfig = binarySearchIndexConfig,
        hashIndexConfig = hashIndexConfig,
        bloomFilterConfig = bloomFilterConfig,
        segmentConfig = segmentConfig
      )

    Segment.persistent(
      pathsDistributor = pathsDistributor,
      mmap = segmentConfig.mmap,
      createdInLevel = createdInLevel,
      segments = transient
    )
  }

  def getFromCache(key: Slice[Byte]): PersistentOption =
    segments
      .floor(key)
      .flatMapSomeS(Persistent.Null: PersistentOption)(_.getFromCache(key))

  def mightContainKey(key: Slice[Byte]): Boolean =
    segments
      .floor(key)
      .existsS(_.mightContain(key))

  /**
   * [[PersistentSegmentMany]] is not aware of [[minMaxFunctionId]].
   * It should be deferred to [[SegmentRef]].
   */
  override def mightContainFunction(key: Slice[Byte]): Boolean =
    segmentRefs exists (_.mightContain(key))

  def get(key: Slice[Byte], threadState: ThreadReadState): PersistentOption =
    segments
      .floor(key)
      .flatMapSomeS(Persistent.Null: PersistentOption) {
        implicit ref =>
          SegmentRef.get(
            key = key,
            threadState = threadState
          )
      }

  def lower(key: Slice[Byte], threadState: ThreadReadState): PersistentOption =
    segments
      .lower(key)
      .flatMapSomeS(Persistent.Null: PersistentOption) {
        implicit ref =>
          SegmentRef.lower(
            key = key,
            threadState = threadState
          )
      }

  private def higherFromHigherSegment(key: Slice[Byte],
                                      floorSegment: SegmentRefOption,
                                      threadState: ThreadReadState): PersistentOption =
    segments
      .higher(key)
      .flatMapSomeS(Persistent.Null: PersistentOption) {
        implicit higherSegment =>
          if (floorSegment.existsS(_.path == higherSegment.path))
            Persistent.Null
          else
            SegmentRef.higher(
              key = key,
              threadState = threadState
            )
      }

  def higher(key: Slice[Byte], threadState: ThreadReadState): PersistentOption = {
    val floorSegment = segments.floor(key)

    floorSegment
      .flatMapSomeS(Persistent.Null: PersistentOption) {
        implicit ref =>
          SegmentRef.higher(
            key = key,
            threadState = threadState
          )
      }
      .orElseS {
        higherFromHigherSegment(
          key = key,
          floorSegment = floorSegment,
          threadState = threadState
        )
      }
  }

  override def toSlice(): Slice[Persistent] =
    Segment.getAllKeyValuesRef(segmentRefs)

  override def iterator(): Iterator[Persistent] =
    segmentRefs.foldLeft(List.empty[Persistent].iterator) {
      case (iterator, segment) =>
        iterator ++ segment.iterator()
    }

  override def hasRange: Boolean =
    segmentRefs.exists(_.hasRange)

  override def hasPut: Boolean =
    segmentRefs.exists(_.hasPut)

  def getKeyValueCount(): Int =
    segmentRefs.foldLeft(0)(_ + _.getKeyValueCount())

  override def isFooterDefined: Boolean =
    segmentRefs.exists(_.isFooterDefined)

  def existsOnDisk: Boolean =
    file.existsOnDisk

  def memory: Boolean =
    false

  def persistent: Boolean =
    true

  def notExistsOnDisk: Boolean =
    !file.existsOnDisk

  def hasBloomFilter: Boolean =
    segmentRefs.exists(_.hasBloomFilter)

  def clearCachedKeyValues(): Unit =
    segmentRefs.foreach(_.clearCachedKeyValues())

  def clearAllCaches(): Unit = {
    clearCachedKeyValues()
    segmentsCache.clear()
    segmentRefsCache.clear()
    segmentRefs.foreach(_.clearBlockCache())
  }

  def isInKeyValueCache(key: Slice[Byte]): Boolean =
    segments
      .floor(key)
      .forallS(_.isInKeyValueCache(key))

  def isKeyValueCacheEmpty: Boolean =
    segmentRefs.forall(_.isKeyValueCacheEmpty)

  def areAllCachesEmpty: Boolean =
    segmentRefs.forall(_.areAllCachesEmpty)

  def cachedKeyValueSize: Int =
    segmentRefs.foldLeft(0)(_ + _.cacheSize)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy