0
# Snapshot Management
1
2
State snapshot functionality for optimizing recovery performance and managing large event histories.
3
4
## Capabilities
5
6
### Snapshotter Trait
7
8
Core snapshot functionality for persistent actors.
9
10
```scala { .api }
11
/**
12
* Snapshot API for persistent actors
13
*/
14
trait Snapshotter extends Actor {
15
/** Snapshotter identifier */
16
def snapshotterId: String
17
18
/** Current snapshot sequence number */
19
def snapshotSequenceNr: Long
20
21
/** Save a snapshot of current state */
22
def saveSnapshot(snapshot: Any): Unit
23
24
/** Delete specific snapshot by sequence number */
25
def deleteSnapshot(sequenceNr: Long): Unit
26
27
/** Delete snapshots matching criteria */
28
def deleteSnapshots(criteria: SnapshotSelectionCriteria): Unit
29
30
/** Load snapshot for given persistence ID and criteria */
31
def loadSnapshot(
32
persistenceId: String,
33
criteria: SnapshotSelectionCriteria,
34
toSequenceNr: Long
35
): Unit
36
}
37
```
38
39
### Snapshot Metadata
40
41
Metadata associated with snapshots.
42
43
```scala { .api }
44
/**
45
* Snapshot metadata containing identification and timing information
46
*/
47
final class SnapshotMetadata(
48
val persistenceId: String,
49
val sequenceNr: Long,
50
val timestamp: Long,
51
val metadata: Option[Any]
52
) {
53
/** Alternative constructor without metadata */
54
def this(persistenceId: String, sequenceNr: Long, timestamp: Long) =
55
this(persistenceId, sequenceNr, timestamp, None)
56
57
/** Create copy with additional metadata */
58
def withMetadata(metadata: Any): SnapshotMetadata =
59
new SnapshotMetadata(persistenceId, sequenceNr, timestamp, Some(metadata))
60
61
/** Create copy with modified fields (for binary compatibility) */
62
def copy(
63
persistenceId: String = this.persistenceId,
64
sequenceNr: Long = this.sequenceNr,
65
timestamp: Long = this.timestamp
66
): SnapshotMetadata =
67
SnapshotMetadata(persistenceId, sequenceNr, timestamp, metadata)
68
}
69
70
object SnapshotMetadata {
71
/** Factory method */
72
def apply(
73
persistenceId: String,
74
sequenceNr: Long,
75
timestamp: Long,
76
metadata: Option[Any]
77
): SnapshotMetadata = new SnapshotMetadata(persistenceId, sequenceNr, timestamp, metadata)
78
}
79
```
80
81
### Snapshot Selection Criteria
82
83
Criteria for selecting which snapshots to load or delete.
84
85
```scala { .api }
86
/**
87
* Criteria for selecting snapshots during recovery or deletion
88
*/
89
case class SnapshotSelectionCriteria(
90
maxSequenceNr: Long = Long.MaxValue,
91
maxTimestamp: Long = Long.MaxValue,
92
minSequenceNr: Long = 0L,
93
minTimestamp: Long = 0L
94
)
95
96
object SnapshotSelectionCriteria {
97
/** Select the latest available snapshot */
98
val Latest: SnapshotSelectionCriteria = SnapshotSelectionCriteria()
99
100
/** Do not select any snapshot */
101
val None: SnapshotSelectionCriteria = SnapshotSelectionCriteria(0L, 0L)
102
}
103
```
104
105
**Usage Examples:**
106
107
```scala
108
// Select latest snapshot
109
val latestCriteria = SnapshotSelectionCriteria.Latest
110
111
// Select snapshots up to sequence number 1000
112
val upTo1000 = SnapshotSelectionCriteria(maxSequenceNr = 1000L)
113
114
// Select snapshots from specific time range
115
val timeRange = SnapshotSelectionCriteria(
116
maxTimestamp = System.currentTimeMillis(),
117
minTimestamp = System.currentTimeMillis() - 86400000L // 24 hours ago
118
)
119
120
// Select specific sequence number range
121
val seqRange = SnapshotSelectionCriteria(
122
maxSequenceNr = 1000L,
123
minSequenceNr = 500L
124
)
125
```
126
127
### Snapshot Recovery
128
129
Messages used during snapshot recovery process.
130
131
```scala { .api }
132
/**
133
* Snapshot offer sent during recovery
134
*/
135
case class SnapshotOffer(metadata: SnapshotMetadata, snapshot: Any)
136
137
/**
138
* Plugin API selected snapshot representation
139
*/
140
case class SelectedSnapshot(metadata: SnapshotMetadata, snapshot: Any)
141
```
142
143
### Snapshot Response Messages
144
145
#### Success Responses
146
147
```scala { .api }
148
/**
149
* Successful snapshot save confirmation
150
*/
151
case class SaveSnapshotSuccess(metadata: SnapshotMetadata)
152
153
/**
154
* Successful snapshot deletion confirmation
155
*/
156
case class DeleteSnapshotSuccess(metadata: SnapshotMetadata)
157
158
/**
159
* Successful deletion of snapshot range
160
*/
161
case class DeleteSnapshotsSuccess(criteria: SnapshotSelectionCriteria)
162
```
163
164
#### Failure Responses
165
166
```scala { .api }
167
/**
168
* Failed snapshot save notification
169
*/
170
case class SaveSnapshotFailure(metadata: SnapshotMetadata, cause: Throwable)
171
172
/**
173
* Failed snapshot deletion notification
174
*/
175
case class DeleteSnapshotFailure(metadata: SnapshotMetadata, cause: Throwable)
176
177
/**
178
* Failed deletion of snapshot range
179
*/
180
case class DeleteSnapshotsFailure(criteria: SnapshotSelectionCriteria, cause: Throwable)
181
```
182
183
### Snapshot Store Plugin API
184
185
Base class for implementing snapshot store plugins.
186
187
```scala { .api }
188
/**
189
* Abstract snapshot store base class for plugin implementations
190
*/
191
trait SnapshotStore extends Actor with ActorLogging {
192
193
/** Load snapshot matching criteria */
194
protected def loadAsync(
195
persistenceId: String,
196
criteria: SnapshotSelectionCriteria
197
): Future[Option[SelectedSnapshot]]
198
199
/** Save snapshot */
200
protected def saveAsync(
201
metadata: SnapshotMetadata,
202
snapshot: Any
203
): Future[Unit]
204
205
/** Delete snapshot by metadata */
206
protected def deleteAsync(metadata: SnapshotMetadata): Future[Unit]
207
208
/** Delete snapshots matching criteria */
209
protected def deleteAsync(
210
persistenceId: String,
211
criteria: SnapshotSelectionCriteria
212
): Future[Unit]
213
}
214
```
215
216
### Example: Snapshot Usage in Persistent Actor
217
218
```scala
219
import akka.persistence._
220
221
case class CounterState(value: Int, lastUpdate: Long)
222
223
class SnapshotAwareCounter extends PersistentActor {
224
override def persistenceId: String = "snapshot-counter"
225
226
private var state = CounterState(0, System.currentTimeMillis())
227
228
override def receiveRecover: Receive = {
229
case Incremented =>
230
state = state.copy(value = state.value + 1, lastUpdate = System.currentTimeMillis())
231
case Decremented =>
232
state = state.copy(value = state.value - 1, lastUpdate = System.currentTimeMillis())
233
case SnapshotOffer(metadata, snapshot: CounterState) =>
234
println(s"Recovered from snapshot at sequence ${metadata.sequenceNr}")
235
state = snapshot
236
}
237
238
override def receiveCommand: Receive = {
239
case Increment =>
240
persist(Incremented) { _ =>
241
state = state.copy(value = state.value + 1, lastUpdate = System.currentTimeMillis())
242
sender() ! state.value
243
244
// Save snapshot every 100 events
245
if (lastSequenceNr % 100 == 0) {
246
saveSnapshot(state)
247
}
248
}
249
250
case Decrement =>
251
persist(Decremented) { _ =>
252
state = state.copy(value = state.value - 1, lastUpdate = System.currentTimeMillis())
253
sender() ! state.value
254
255
if (lastSequenceNr % 100 == 0) {
256
saveSnapshot(state)
257
}
258
}
259
260
case GetValue => sender() ! state.value
261
262
case "force-snapshot" => saveSnapshot(state)
263
264
case "delete-old-snapshots" =>
265
// Delete snapshots older than current - 200 sequence numbers
266
val criteria = SnapshotSelectionCriteria(maxSequenceNr = lastSequenceNr - 200)
267
deleteSnapshots(criteria)
268
269
case SaveSnapshotSuccess(metadata) =>
270
println(s"Snapshot saved successfully at sequence ${metadata.sequenceNr}")
271
272
case SaveSnapshotFailure(metadata, cause) =>
273
println(s"Snapshot save failed at sequence ${metadata.sequenceNr}: ${cause.getMessage}")
274
275
case DeleteSnapshotsSuccess(criteria) =>
276
println(s"Old snapshots deleted successfully up to ${criteria.maxSequenceNr}")
277
278
case DeleteSnapshotsFailure(criteria, cause) =>
279
println(s"Failed to delete snapshots: ${cause.getMessage}")
280
}
281
}
282
```
283
284
### Advanced Snapshot Patterns
285
286
#### Conditional Snapshotting
287
288
```scala
289
class ConditionalSnapshotActor extends PersistentActor {
290
override def persistenceId: String = "conditional-snapshot"
291
292
private var eventsSinceSnapshot = 0
293
private var state = SomeComplexState()
294
295
override def receiveCommand: Receive = {
296
case SomeEvent =>
297
persist(SomeEvent) { _ =>
298
updateState()
299
eventsSinceSnapshot += 1
300
301
// Snapshot based on multiple conditions
302
val shouldSnapshot = eventsSinceSnapshot >= 50 ||
303
state.size > 1000 ||
304
(System.currentTimeMillis() - lastUpdate) > 3600000 // 1 hour
305
306
if (shouldSnapshot) {
307
saveSnapshot(state)
308
eventsSinceSnapshot = 0
309
}
310
}
311
312
case SaveSnapshotSuccess(_) =>
313
eventsSinceSnapshot = 0
314
}
315
}
316
```
317
318
#### Snapshot Metadata Usage
319
320
```scala
321
class MetadataAwareActor extends PersistentActor {
322
override def persistenceId: String = "metadata-aware"
323
324
override def receiveCommand: Receive = {
325
case "snapshot-with-metadata" =>
326
val metadata = Map("reason" -> "manual", "timestamp" -> System.currentTimeMillis())
327
// Note: metadata is added to SnapshotMetadata after saving
328
saveSnapshot(currentState)
329
330
case SaveSnapshotSuccess(metadata) =>
331
metadata.metadata.foreach { meta =>
332
println(s"Snapshot saved with metadata: $meta")
333
}
334
}
335
336
override def receiveRecover: Receive = {
337
case SnapshotOffer(metadata, snapshot) =>
338
println(s"Recovering from snapshot ${metadata.sequenceNr} at ${metadata.timestamp}")
339
metadata.metadata.foreach { meta =>
340
println(s"Snapshot metadata: $meta")
341
}
342
restoreFromSnapshot(snapshot)
343
}
344
}