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

com.comcast.xfinity.sirius.uberstore.segmented.Segment.scala Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright 2012-2014 Comcast Cable Communications Management, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.comcast.xfinity.sirius.uberstore.segmented

import com.comcast.xfinity.sirius.api.impl.OrderedEvent
import com.comcast.xfinity.sirius.uberstore.seqindex.{DiskOnlySeqIndex, SeqIndex}
import com.comcast.xfinity.sirius.uberstore.data.{UberDataFile, UberDataFileHandleFactory}
import java.io.File

object Segment {

  /**
   * Create an Segment based in baseDir named "name".
   *
   * @param base directory containing the Segment
   * @param name the name of this dir
   *
   * @return an Segment instance, fully repaired and usable
   */
  def apply(base: File, name: String, fileHandleFactory: UberDataFileHandleFactory): Segment = {
    apply(new File(base, name), fileHandleFactory)
  }

  /**
   * Create an Segment at the specified location.
   *
   * @param location location of segment. must be inside a segmented uberstore's base
   *                 to be meaningful.
   *
   * @return an Segment instance, fully repaired and usable
   */
  def apply(location: File, fileHandleFactory: UberDataFileHandleFactory): Segment = {
    location.mkdirs()

    val dataFile = new File(location, "data")
    val indexFile = new File(location, "index")
    val compactionFlagFile = new File(location, "keys-collected")
    val internalCompactionFlagFile = new File(location, "internally-compacted")

    val data = UberDataFile(dataFile.getAbsolutePath, fileHandleFactory)
    val index = DiskOnlySeqIndex(indexFile.getAbsolutePath)
    val compactionFlag = FlagFile(compactionFlagFile.getAbsolutePath)
    val internalCompactionFlag = FlagFile(internalCompactionFlagFile.getAbsolutePath)

    repairIndex(index, data)
    new Segment(location, location.getName, data, index, compactionFlag, internalCompactionFlag)
  }

  /**
   * Recovers missing entries at the end of index from dataFile.
   *
   * Assumes that UberDataFile is proper, that is events are there in order,
   * and there are no dups.
   *
   * Has the side effect of updating index.
   *
   * @param index the SeqIndex to update
   * @param dataFile the UberDataFile to update
   */
  private[segmented] def repairIndex(index: SeqIndex, dataFile: UberDataFile) {
    val (includeFirst, lastOffset) = index.getMaxSeq match {
      case None => (true, 0L)
      case Some(seq) => (false, index.getOffsetFor(seq).get) // has to exist
    }

    dataFile.foldLeftRange(lastOffset, Long.MaxValue)(includeFirst) (
      (shouldInclude, off, evt) => {
        if (shouldInclude) {
          index.put(evt.sequence, off)
        }
        true
      }
    )

  }
}

/**
 * Expectedly high performance sequence number based append only
 * storage directory.  Stores all data in dataFile, and sequence -> data
 * mappings in index.
 *
 * @param dataFile the UberDataFile to store data in
 * @param index the SeqIndex to use
 */
class Segment private[uberstore](val location: File,
                                 val name: String,
                                 dataFile: UberDataFile,
                                 index: SeqIndex,
                                 compactionFlag: FlagFile,
                                 internalCompactionFlag: FlagFile) {

  /**
   * Get the number of entries written to the Segment
   * @return number of entries in the Segment
   */
  def size = index.size

  /**
   * Write OrderedEvent event into this dir. Will fail if closed or sequence
   * is out of order.
   *
   * @param event the {@link OrderedEvent} to write
   */
  def writeEntry(event: OrderedEvent) {
    if (isClosed) {
      throw new IllegalStateException("Attempting to write to closed Segment")
    }
    if (event.sequence < getNextSeq) {
      throw new IllegalArgumentException("Writing events out of order is bad news bears")
    }
    val offset = dataFile.writeEvent(event)
    index.put(event.sequence, offset)
  }

  /**
   * Get all the unique keys in the segment.
   *
   * @return set of keys in this Segment
   */
  def keys: Set[String] =
    foldLeft(Set[String]())(
      (acc, event) => acc + event.request.key
    )

  /**
   * Get the next possible sequence number in this dir.
   */
  def getNextSeq = index.getMaxSeq match {
    case None => 1L
    case Some(seq) => seq + 1
  }

  def foreach[T](fun: OrderedEvent => T) {
    foldLeft(())((_, evt) => fun(evt))
  }

  /**
   * Fold left over all entries.
   *
   * @param acc0 initial accumulator value
   * @param foldFun the fold function
   */
  def foldLeft[T](acc0: T)(foldFun: (T, OrderedEvent) => T): T =
    foldLeftRange(0, Long.MaxValue)(acc0)(foldFun)

  /**
   * Fold left over a range of entries based on sequence number.
   */
  def foldLeftRange[T](startSeq: Long, endSeq: Long)(acc0: T)(foldFun: (T, OrderedEvent) => T): T = {
    val (startOffset, endOffset) = index.getOffsetRange(startSeq, endSeq)
    dataFile.foldLeftRange(startOffset, endOffset)(acc0)(
      (acc, _, evt) => foldFun(acc, evt)
    )
  }

  /**
   * Close underlying file handles or connections.  This Segment should not be used after
   * close is called.
   */
  def close() {
    if (!dataFile.isClosed) {
      dataFile.close()
    }
    if (!index.isClosed) {
      index.close()
    }
  }

  /**
   * Consider this closed if either of its underlying objects are closed, no good writes
   * will be able to go through in that case.
   *
   * @return whether this is "closed," i.e., unable to be written to
   */
  def isClosed = dataFile.isClosed || index.isClosed

  /**
   * Have the keys in this segment been applied via compaction to previous segments?
   * @return true if the keys have been compacted away from previous segments, false otherwise
   */
  def isApplied = compactionFlag.value

  /**
   * Set the keys-applied flag.
   * @param applied the value of the flag
   */
  def setApplied(applied: Boolean) {
    compactionFlag.set(value = applied)
  }

  /**
   * Has this segment been internally compacted?
   *
   * @return true if internal compaction is completed, false otherwise
   */
  def isInternallyCompacted = internalCompactionFlag.value

  /**
   * Set the internally-compacted flag
   *
   * @param compacted the value of the flag
   */
  def setInternallyCompacted(compacted: Boolean) {
    internalCompactionFlag.set(compacted)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy