or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

at-least-once-delivery.mddurable-state.mdevent-adapters.mdindex.mdjournal-api.mdpersistent-actors.mdplugin-development.mdsnapshots.md

snapshots.mddocs/

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

}