A new snapshot is always created after new event

This commit is contained in:
2025-03-18 16:04:03 +01:00
parent 12af6604d6
commit b91c45100d

View File

@@ -15,10 +15,20 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
data class SnapshotConfig( data class SnapshotConfig(
/**
* Keep snapshot when is on the head of the queue cache
*/
val maxSnapshotCacheSize: Int = 20, val maxSnapshotCacheSize: Int = 20,
/**
* Keep snapshot when is newer of
*
* snapshot.date > now + maxSnapshotCacheTtl
*/
val maxSnapshotCacheTtl: Duration = 10.minutes, val maxSnapshotCacheTtl: Duration = 10.minutes,
/** /**
* Only create [snapshots][Projection] every [X][modulo] [events][Event] * Keep snapshot when version is this modulo
*
* snapshot.lastVersion % modulo == 1
*/ */
val modulo: Int = 10, val modulo: Int = 10,
) )
@@ -42,14 +52,12 @@ class ProjectionSnapshotRepositoryInMemory<E : Event<ID>, P : Projection<ID>, ID
* 6. remove old one * 6. remove old one
*/ */
fun applyAndPutToCache(event: E) { fun applyAndPutToCache(event: E) {
if ((event.version % snapshotCacheConfig.modulo) == 1) {
getUntil(event) getUntil(event)
.also { .also {
save(it) save(it)
removeOldSnapshot(it.aggregateId) removeOldSnapshot(it.aggregateId)
} }
} }
}
/** /**
* Build the list of all [Projections][Projection] * Build the list of all [Projections][Projection]
@@ -100,46 +108,65 @@ class ProjectionSnapshotRepositoryInMemory<E : Event<ID>, P : Projection<ID>, ID
} }
/** /**
* Remove the oldest snapshot. * Remove the oldest [snapshot][P] of the [queue][projectionsSnapshot].
* *
* The rules are pass in the controller. * The rules are pass in the controller.
*/ */
private fun removeOldSnapshot(aggregateId: ID) { private fun removeOldSnapshot(aggregateId: ID) {
projectionsSnapshot[aggregateId]?.let { queue -> projectionsSnapshot[aggregateId]?.let { queue ->
// never remove the last one
val theLastOne = getLastSnapshot(aggregateId)
removeByDate(queue, theLastOne)
removeBySize(queue, theLastOne)
}
}
private fun removeBySize(
queue: ConcurrentLinkedQueue<Pair<P, Instant>>,
theLastOne: Pair<P, Instant>?,
) {
// Remove if size exceeds the limit
val size = queue.size
if (size > snapshotCacheConfig.maxSnapshotCacheSize) {
val numberToRemove = size - snapshotCacheConfig.maxSnapshotCacheSize
if (numberToRemove > 0) {
queue queue
.sortedBy { it.first.lastEventVersion } .excludeFirstAndLast()
.take(numberToRemove) .excludeTheHeadBySize()
.let { it - theLastOne } .excludeNewerByDate()
.excludeByModulo()
.forEach { queue.remove(it) } .forEach { queue.remove(it) }
} }
} }
/**
* Return a new list without the first and last snapshot.
*
* Exclude from deletion the first and the last.
*/
private fun FilteredList<P>.excludeFirstAndLast(): FilteredList<P> =
sortedBy { it.first.lastEventVersion }
.drop(1)
.dropLast(1)
/**
* Return a new list of event filtered by the version modulo.
*
* Exclude from deletion 1 element out of 10 (if modulo 10 in [config][snapshotCacheConfig]).
*/
private fun FilteredList<P>.excludeByModulo(): FilteredList<P> =
filter { (it.first.lastEventVersion % snapshotCacheConfig.modulo) != 1 }
/**
* Return a new list of event filtered by the maximum size.
*
* Exclude from removal all [snapshot][projectionsSnapshot] that in the head of the queue.
*/
private fun FilteredList<P>.excludeTheHeadBySize(): FilteredList<P> {
// filter if size exceeds the limit
if (size > snapshotCacheConfig.maxSnapshotCacheSize) {
val numberToRemove = size - snapshotCacheConfig.maxSnapshotCacheSize
if (numberToRemove > 0) {
return sortedBy { it.first.lastEventVersion }
.takeLast(numberToRemove)
}
}
return this
} }
private fun removeByDate( /**
queue: ConcurrentLinkedQueue<Pair<P, Instant>>, * Return a new list of event filtered by the maximum date.
theLastOne: Pair<P, Instant>?, *
) { * Exclude from removal all [snapshot][projectionsSnapshot] that newer of the date (in [config][SnapshotConfig]).
// remove the oldest by time */
private fun FilteredList<P>.excludeNewerByDate(): FilteredList<P> {
val now = Clock.System.now() val now = Clock.System.now()
val deadLine = now - snapshotCacheConfig.maxSnapshotCacheTtl val deadLine = now - snapshotCacheConfig.maxSnapshotCacheTtl
val toRemove = queue.filter { deadLine > it.second } return filter { deadLine < it.second }
(toRemove - theLastOne).forEach { queue.remove(it) }
} }
/** /**
@@ -204,3 +231,5 @@ class ProjectionSnapshotRepositoryInMemory<E : Event<ID>, P : Projection<ID>, ID
} }
} }
} }
private typealias FilteredList<P> = Collection<Pair<P, Instant>>