
swaydb.compression.CompressorInternal.scala Maven / Gradle / Ivy
/*
* 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.compression
import java.nio.ByteBuffer
import com.typesafe.scalalogging.LazyLogging
import net.jpountz.lz4.{LZ4Compressor, LZ4Factory}
import org.xerial.snappy
import swaydb.data.compression.LZ4Compressor.{Fast, High}
import swaydb.data.compression.LZ4Instance
import swaydb.data.compression.LZ4Instance._
import swaydb.data.slice.Slice
private[swaydb] sealed trait CompressorInternal {
val minCompressionPercentage: Double
def compress(slice: Slice[Byte]): Option[Slice[Byte]]
def compress(emptyHeadSpace: Int, slice: Slice[Byte]): Option[Slice[Byte]]
}
private[swaydb] object CompressorInternal extends LazyLogging {
def apply(instance: swaydb.data.compression.LZ4Instance,
compressor: swaydb.data.compression.LZ4Compressor): CompressorInternal.LZ4 =
lz4Compressor(
compressor = compressor,
factory = lz4Factory(instance)
)
private def lz4Factory(instance: LZ4Instance): LZ4Factory =
instance match {
//@formatter:off
case Fastest => LZ4Factory.fastestInstance()
case FastestJava => LZ4Factory.fastestJavaInstance()
case Native => LZ4Factory.nativeInstance()
case Safe => LZ4Factory.safeInstance()
case Unsafe => LZ4Factory.unsafeInstance()
//@formatter:on
}
private def lz4Compressor(compressor: swaydb.data.compression.LZ4Compressor,
factory: LZ4Factory): CompressorInternal.LZ4 =
compressor match {
case Fast(minCompressionPercentage) =>
CompressorInternal.LZ4(minCompressionPercentage, factory.fastCompressor())
case High(minCompressionPercentage, compressionLevel) =>
compressionLevel match {
case Some(compressionLevel) =>
CompressorInternal.LZ4(minCompressionPercentage, factory.highCompressor(compressionLevel))
case None =>
CompressorInternal.LZ4(minCompressionPercentage, factory.highCompressor())
}
}
/**
* @return true if the compression satisfies the minimum compression requirement else false.
*/
def isCompressionSatisfied(minCompressionPercentage: Double,
compressedLength: Int,
originalLength: Int,
compressionName: String): Boolean = {
val compressionSavedPercentage = (1D - (compressedLength.toDouble / originalLength.toDouble)) * 100
if (compressionSavedPercentage < minCompressionPercentage) {
logger.debug(s"Uncompressed! $compressionName - $originalLength.bytes compressed to $compressedLength.bytes. Compression savings = $compressionSavedPercentage%. Required minimum $minCompressionPercentage%.")
false
} else {
logger.debug(s"Compressed! $compressionName - $originalLength.bytes compressed to $compressedLength.bytes. Compression savings = $compressionSavedPercentage%. Required minimum $minCompressionPercentage%.")
true
}
}
private[swaydb] case class LZ4(minCompressionPercentage: Double,
compressor: LZ4Compressor) extends CompressorInternal {
final val compressionName = this.getClass.getSimpleName
override def compress(slice: Slice[Byte]): Option[Slice[Byte]] =
compress(
emptyHeadSpace = 0,
slice = slice
)
def compress(emptyHeadSpace: Int, slice: Slice[Byte]): Option[Slice[Byte]] = {
val maxCompressLength = compressor.maxCompressedLength(slice.size)
val compressedBuffer = ByteBuffer.allocate(maxCompressLength + emptyHeadSpace)
val compressedBytes = compressor.compress(slice.toByteBufferWrap, slice.fromOffset, slice.size, compressedBuffer, emptyHeadSpace, maxCompressLength)
if (isCompressionSatisfied(minCompressionPercentage, compressedBytes, slice.size, compressionName))
Some(
Slice.from(
byteBuffer = compressedBuffer,
from = 0,
to = emptyHeadSpace + compressedBytes - 1
)
)
else
None
}
}
private[swaydb] case object UnCompressed extends CompressorInternal {
final val compressionName = this.getClass.getSimpleName.dropRight(1)
override final val minCompressionPercentage: Double = Double.MinValue
override def compress(emptyHeadSpace: Int, slice: Slice[Byte]): Option[Slice[Byte]] =
Some(Slice.fill[Byte](emptyHeadSpace)(0) ++ slice)
override def compress(slice: Slice[Byte]): Option[Slice[Byte]] = {
logger.debug(s"Uncompressed {}.bytes with {}", slice.size, compressionName)
Some(slice)
}
}
private[swaydb] case class Snappy(minCompressionPercentage: Double) extends CompressorInternal {
final val compressionName = this.getClass.getSimpleName
override def compress(slice: Slice[Byte]): Option[Slice[Byte]] =
compress(emptyHeadSpace = 0, slice = slice)
override def compress(emptyHeadSpace: Int, slice: Slice[Byte]): Option[Slice[Byte]] = {
val compressedArray = new Array[Byte](snappy.Snappy.maxCompressedLength(slice.size) + emptyHeadSpace)
val (bytes, fromOffset, written) = slice.underlyingWrittenArrayUnsafe
val compressedSize = snappy.Snappy.compress(bytes, fromOffset, written, compressedArray, emptyHeadSpace)
if (isCompressionSatisfied(minCompressionPercentage, compressedSize, slice.size, this.getClass.getSimpleName))
Some(Slice(compressedArray).slice(0, emptyHeadSpace + compressedSize - 1))
else
None
}
}
def randomLZ4(minCompressionSavingsPercent: Double = Double.MinValue): CompressorInternal.LZ4 =
CompressorInternal(
instance = LZ4Instance.random(),
compressor = swaydb.data.compression.LZ4Compressor.random(minCompressionSavingsPercent = minCompressionSavingsPercent)
)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy