-
Notifications
You must be signed in to change notification settings - Fork 51
Add DynamoDB item Time to Live #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Dnomyar
wants to merge
23
commits into
akka:master
Choose a base branch
from
Dnomyar:add-dynamodb-item-ttl
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
af6ae8e
Add config for item expiration: field name and ttl value
5e21bb8
Implement inserting item with the ttl field from configuration
129d6d0
Extract the ttl related configuration stuff into and different file a…
55520d0
add missing assert
914ad08
Add the ttl logic for snapshot tables
f66462b
Refactor: rename maybeTTLConfig to TTLConfig and use pattern matching
aca1aeb
Refactor: rename getItemExpiryTimeSeconds to getItemExpiryTimeEpochSe…
6cc127f
extract serializePersistentRepr
27600aa
reorder statements
b469627
extract function verifyItemSizeDidNotReachThreshold
bd8f36c
Extract createItem function
f7d20d7
move function keyLength to function verifyItemSizeDidNotReachThreshold
1f90792
Extract verifyItemSizeDidNotReachThreshold in another class
293b2e6
Turn keyLength into a value
70e7908
Add the ttl size in the ItemSizeVerifier
80cc33e
Refactor: to fix a smell
624df3f
Add section in README about TTL
c6bf7c3
README typos
7e11085
Address review feedback, move time up
3c6cfc3
README typos
d32d946
Update readme
60d64da
Merge branch 'master' into add-dynamodb-item-ttl
c69866a
format files
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/main/scala/akka/persistence/dynamodb/DynamoDBTTLConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package akka.persistence.dynamodb | ||
|
||
import com.typesafe.config.Config | ||
|
||
import java.time.OffsetDateTime | ||
import scala.concurrent.duration.{ Duration, DurationLong } | ||
import scala.util.Try | ||
|
||
case class DynamoDBTTLConfig(fieldName: String, ttl: DynamoDBTTL) { | ||
|
||
override def toString: String = | ||
s"DynamoDBTTLConfig(fieldName = ${fieldName}, ttl = ${ttl.ttl})" | ||
} | ||
|
||
case class DynamoDBTTL(ttl: Duration) { | ||
|
||
// Important, the value needs to be Unix epoch time format in seconds | ||
// See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/time-to-live-ttl-before-you-start.html#time-to-live-ttl-before-you-start-formatting | ||
def getItemExpiryTimeEpochSeconds(now: OffsetDateTime): Long = | ||
now.plusNanos(ttl.toNanos).toEpochSecond | ||
} | ||
|
||
object DynamoDBTTL { | ||
def fromJavaDuration(duration: java.time.Duration): DynamoDBTTL = | ||
DynamoDBTTL(duration.toNanos.nanos) | ||
} | ||
|
||
object DynamoDBTTLConfigReader { | ||
|
||
val configFieldName: String = "dynamodb-item-ttl-config.field-name" | ||
val configTtlName: String = "dynamodb-item-ttl-config.ttl" | ||
|
||
def readTTLConfig(c: Config): Option[DynamoDBTTLConfig] = { | ||
for { | ||
fieldName <- Try(c.getString(configFieldName)).toOption.map(_.trim) | ||
if fieldName.nonEmpty | ||
ttl <- Try(c.getDuration(configTtlName)).toOption | ||
} yield DynamoDBTTLConfig(fieldName = fieldName, ttl = DynamoDBTTL.fromJavaDuration(ttl)) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,27 +3,30 @@ | |
*/ | ||
package akka.persistence.dynamodb.journal | ||
|
||
import java.nio.ByteBuffer | ||
import java.util.Collections | ||
|
||
import akka.Done | ||
import akka.actor.ExtendedActorSystem | ||
import akka.pattern.after | ||
import akka.persistence.dynamodb._ | ||
import akka.persistence.{ AtomicWrite, PersistentRepr } | ||
import akka.serialization.{ AsyncSerializer, Serialization, Serializer, Serializers } | ||
import com.amazonaws.services.dynamodbv2.model._ | ||
|
||
import java.nio.ByteBuffer | ||
import java.time.OffsetDateTime | ||
import java.util.Collections | ||
import scala.collection.JavaConverters._ | ||
import scala.concurrent.Future | ||
import scala.concurrent.duration._ | ||
import scala.util.{ Failure, Success, Try } | ||
import scala.util.control.NonFatal | ||
import akka.Done | ||
import akka.actor.ExtendedActorSystem | ||
import akka.pattern.after | ||
import akka.persistence.dynamodb._ | ||
import akka.serialization.{ AsyncSerializer, Serialization, Serializers } | ||
import scala.util.{ Failure, Success, Try } | ||
|
||
trait DynamoDBJournalRequests extends DynamoDBRequests { | ||
this: DynamoDBJournal => | ||
|
||
import settings._ | ||
|
||
private lazy val itemSizeVerifier = new ItemSizeCalculator(settings) | ||
|
||
/** | ||
* Write all messages in a sequence of AtomicWrites. Care must be taken to | ||
* not have concurrent writes happening that touch the highest sequence number. | ||
|
@@ -44,6 +47,7 @@ trait DynamoDBJournalRequests extends DynamoDBRequests { | |
writeMessages(write).flatMap(result => rec(remainder, bubbleUpFailures(result) :: acc)) | ||
case Nil => Future.successful(acc.reverse) | ||
} | ||
|
||
rec(writes.toList, Nil) | ||
} | ||
|
||
|
@@ -165,54 +169,66 @@ trait DynamoDBJournalRequests extends DynamoDBRequests { | |
try { | ||
val reprPayload: AnyRef = repr.payload.asInstanceOf[AnyRef] | ||
val serializer = serialization.serializerFor(reprPayload.getClass) | ||
val fut = serializer match { | ||
case aS: AsyncSerializer => | ||
Serialization.withTransportInformation(context.system.asInstanceOf[ExtendedActorSystem]) { () => | ||
aS.toBinaryAsync(reprPayload) | ||
} | ||
case _ => | ||
Future { | ||
ByteBuffer.wrap(serialization.serialize(reprPayload).get).array() | ||
} | ||
} | ||
|
||
fut.map { serialized => | ||
serializePersistentRepr(reprPayload, serializer).map { serialized => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice simplification! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks! |
||
val eventData = B(serialized) | ||
val serializerId = N(serializer.identifier) | ||
|
||
val fieldLength = repr.persistenceId.getBytes.length + repr.sequenceNr.toString.getBytes.length + | ||
repr.writerUuid.getBytes.length + repr.manifest.getBytes.length | ||
|
||
val manifest = Serializers.manifestFor(serializer, reprPayload) | ||
val manifestLength = if (manifest.isEmpty) 0 else manifest.getBytes.length | ||
|
||
val itemSize = keyLength( | ||
repr.persistenceId, | ||
repr.sequenceNr) + eventData.getB.remaining + serializerId.getN.getBytes.length + manifestLength + fieldLength | ||
val manifest = Serializers.manifestFor(serializer, reprPayload) | ||
|
||
val itemSize = itemSizeVerifier.getItemSize(repr, eventData, serializerId, manifest) | ||
if (itemSize > MaxItemSize) { | ||
throw new DynamoDBJournalRejection(s"MaxItemSize exceeded: $itemSize > $MaxItemSize") | ||
} | ||
val item: Item = messageKey(repr.persistenceId, repr.sequenceNr) | ||
|
||
item.put(PersistentId, S(repr.persistenceId)) | ||
item.put(SequenceNr, N(repr.sequenceNr)) | ||
item.put(Event, eventData) | ||
item.put(WriterUuid, S(repr.writerUuid)) | ||
item.put(SerializerId, serializerId) | ||
if (repr.manifest.nonEmpty) { | ||
item.put(Manifest, S(repr.manifest)) | ||
} | ||
if (manifest.nonEmpty) { | ||
item.put(SerializerManifest, S(manifest)) | ||
} | ||
item | ||
|
||
createItem(repr, eventData, serializerId, manifest) | ||
} | ||
} catch { | ||
case NonFatal(e) => Future.failed(e) | ||
} | ||
} | ||
|
||
private def createItem( | ||
repr: PersistentRepr, | ||
eventData: AttributeValue, | ||
serializerId: AttributeValue, | ||
manifest: String) = { | ||
val item: Item = messageKey(repr.persistenceId, repr.sequenceNr) | ||
|
||
item.put(PersistentId, S(repr.persistenceId)) | ||
item.put(SequenceNr, N(repr.sequenceNr)) | ||
item.put(Event, eventData) | ||
item.put(WriterUuid, S(repr.writerUuid)) | ||
item.put(SerializerId, serializerId) | ||
|
||
TTLConfig.foreach { | ||
case DynamoDBTTLConfig(fieldName, ttl) => | ||
val expiresAt = ttl.getItemExpiryTimeEpochSeconds(OffsetDateTime.now) | ||
item.put(fieldName, N(expiresAt)) | ||
} | ||
|
||
if (repr.manifest.nonEmpty) { | ||
item.put(Manifest, S(repr.manifest)) | ||
} | ||
if (manifest.nonEmpty) { | ||
item.put(SerializerManifest, S(manifest)) | ||
} | ||
item | ||
} | ||
|
||
private def serializePersistentRepr(reprPayload: AnyRef, serializer: Serializer) = { | ||
serializer match { | ||
case aS: AsyncSerializer => | ||
Serialization.withTransportInformation(context.system.asInstanceOf[ExtendedActorSystem]) { () => | ||
aS.toBinaryAsync(reprPayload) | ||
} | ||
case _ => | ||
Future { | ||
ByteBuffer.wrap(serialization.serialize(reprPayload).get).array() | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Store the highest sequence number for this persistenceId. | ||
* | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice description of the design considerations required!