kopia lustrzana https://github.com/twitter/the-algorithm
141 wiersze
5.0 KiB
Scala
141 wiersze
5.0 KiB
Scala
package com.twitter.tweetypie.caching
|
|
|
|
import com.twitter.io.Buf
|
|
import com.twitter.scrooge.CompactThriftSerializer
|
|
import com.twitter.scrooge.ThriftStruct
|
|
import com.twitter.scrooge.ThriftStructCodec
|
|
import com.twitter.servo.cache.thriftscala.CachedValue
|
|
import com.twitter.servo.cache.thriftscala.CachedValueStatus
|
|
import com.twitter.stitch.NotFound
|
|
import com.twitter.util.Return
|
|
import com.twitter.util.Throw
|
|
import com.twitter.util.Time
|
|
import com.twitter.util.Try
|
|
import java.nio.ByteBuffer
|
|
|
|
object ServoCachedValueSerializer {
|
|
|
|
/**
|
|
* Thrown when the fields of the servo CachedValue struct do not
|
|
* satisfy the invariants expected by this serialization code.
|
|
*/
|
|
case class UnexpectedCachedValueState(cachedValue: CachedValue) extends Exception {
|
|
def message: String = s"Unexpected state for CachedValue. Value was: $cachedValue"
|
|
}
|
|
|
|
val CachedValueThriftSerializer: CompactThriftSerializer[CachedValue] = CompactThriftSerializer(
|
|
CachedValue)
|
|
}
|
|
|
|
/**
|
|
* A [[ValueSerializer]] that is compatible with the use of
|
|
* Servo's [[CachedValue]] struct by tweetypie:
|
|
*
|
|
* - The only [[CachedValueStatus]] values that are cacheable are
|
|
* [[CachedValueStatus.Found]] and [[CachedValueStatus.NotFound]].
|
|
*
|
|
* - We only track the `cachedAtMsec` field, because tweetypie's cache
|
|
* interaction does not use the other fields, and the values that
|
|
* are cached this way are never updated, so storing readThroughAt
|
|
* or writtenThroughAt would not add any information.
|
|
*
|
|
* - When values are present, they are serialized using
|
|
* [[org.apache.thrift.protocol.TCompactProtocol]].
|
|
*
|
|
* - The CachedValue struct itself is also serialized using TCompactProtocol.
|
|
*
|
|
* The serializer operates on [[Try]] values and will cache [[Return]]
|
|
* and `Throw(NotFound)` values.
|
|
*/
|
|
case class ServoCachedValueSerializer[V <: ThriftStruct](
|
|
codec: ThriftStructCodec[V],
|
|
expiry: Try[V] => Time,
|
|
softTtl: SoftTtl[Try[V]])
|
|
extends ValueSerializer[Try[V]] {
|
|
import ServoCachedValueSerializer.UnexpectedCachedValueState
|
|
import ServoCachedValueSerializer.CachedValueThriftSerializer
|
|
|
|
private[this] val ValueThriftSerializer = CompactThriftSerializer(codec)
|
|
|
|
/**
|
|
* Return an expiry based on the value and a
|
|
* TCompactProtocol-encoded servo CachedValue struct with the
|
|
* following fields defined:
|
|
*
|
|
* - `value`: [[None]]
|
|
* for {{{Throw(NotFound)}}, {{{Some(encodedStruct)}}} for
|
|
* [[Return]], where {{{encodedStruct}}} is a
|
|
* TCompactProtocol-encoding of the value inside of the Return.
|
|
*
|
|
* - `status`: [[CachedValueStatus.Found]] if the value is Return,
|
|
* and [[CachedValueStatus.NotFound]] if it is Throw(NotFound)
|
|
*
|
|
* - `cachedAtMsec`: The current time, accoring to [[Time.now]]
|
|
*
|
|
* No other fields will be defined.
|
|
*
|
|
* @throws IllegalArgumentException if called with a value that
|
|
* should not be cached.
|
|
*/
|
|
override def serialize(value: Try[V]): Option[(Time, Buf)] = {
|
|
def serializeCachedValue(payload: Option[ByteBuffer]) = {
|
|
val cachedValue = CachedValue(
|
|
value = payload,
|
|
status = if (payload.isDefined) CachedValueStatus.Found else CachedValueStatus.NotFound,
|
|
cachedAtMsec = Time.now.inMilliseconds)
|
|
|
|
val serialized = Buf.ByteArray.Owned(CachedValueThriftSerializer.toBytes(cachedValue))
|
|
|
|
(expiry(value), serialized)
|
|
}
|
|
|
|
value match {
|
|
case Throw(NotFound) =>
|
|
Some(serializeCachedValue(None))
|
|
case Return(struct) =>
|
|
val payload = Some(ByteBuffer.wrap(ValueThriftSerializer.toBytes(struct)))
|
|
Some(serializeCachedValue(payload))
|
|
case _ =>
|
|
None
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deserializes values serialized by [[serializeValue]]. The
|
|
* value will be [[CacheResult.Fresh]] or [[CacheResult.Stale]]
|
|
* depending on the result of {{{softTtl.isFresh}}}.
|
|
*
|
|
* @throws UnexpectedCachedValueState if the state of the
|
|
* [[CachedValue]] could not be produced by [[serialize]]
|
|
*/
|
|
override def deserialize(buf: Buf): CacheResult[Try[V]] = {
|
|
val cachedValue = CachedValueThriftSerializer.fromBytes(Buf.ByteArray.Owned.extract(buf))
|
|
val hasValue = cachedValue.value.isDefined
|
|
val isValid =
|
|
(hasValue && cachedValue.status == CachedValueStatus.Found) ||
|
|
(!hasValue && cachedValue.status == CachedValueStatus.NotFound)
|
|
|
|
if (!isValid) {
|
|
// Exceptions thrown by deserialization are recorded and treated
|
|
// as a cache miss by CacheOperations, so throwing this
|
|
// exception will cause the value in cache to be
|
|
// overwritten. There will be stats recorded whenever this
|
|
// happens.
|
|
throw UnexpectedCachedValueState(cachedValue)
|
|
}
|
|
|
|
val value =
|
|
cachedValue.value match {
|
|
case Some(valueBuffer) =>
|
|
val valueBytes = new Array[Byte](valueBuffer.remaining)
|
|
valueBuffer.duplicate.get(valueBytes)
|
|
Return(ValueThriftSerializer.fromBytes(valueBytes))
|
|
|
|
case None =>
|
|
Throw(NotFound)
|
|
}
|
|
|
|
softTtl.toCacheResult(value, Time.fromMilliseconds(cachedValue.cachedAtMsec))
|
|
}
|
|
}
|