ElasticsearchSnapshotStore

The SnapshotStore of one bounded context on Elasticsearch (documentation/designs/03-log-and-snapshot.md §3).

Each aggregate type has its own index, wow_snapshot__<context>__<aggregate> (ElasticsearchNames), holding one document per aggregate, its id the tenant and the aggregate id (ElasticsearchNames.documentId), laid out as ElasticsearchSnapshotDocuments and ElasticsearchSnapshotIndex say. An aggregate type's index is created the first time this store touches the type, as ddl says.

Every write is a whole document, guarded by the sequence number of the one it replaces (if_seq_no, if_primary_term), or created only when there is none (op_type=create): the store reads the document in real time, decides as the contract says (version, state revision, stale mark), and writes; a write another one beat is refused by Elasticsearch and decided again. Concurrent saves therefore leave the highest version, and no write is a partial update, which would have Elasticsearch rewrite the _source and lose the state's exact numbers.

The claimed state revision of each index is a document of the snapshot table index (ElasticsearchNames.snapshotTables), keyed by the snapshot index, guarded the same way. Writes become visible to searches as refresh says (wait_for by default, so a snapshot is queryable once its write returns).

Properties

Link copied to clipboard
open override val rebuilds: SnapshotRebuildStore

This store rewrites its snapshots in place: it is its own rebuild control plane.

Functions

Link copied to clipboard
open suspend override fun claim(type: AggregateType, revision: Int, lower: Boolean): SnapshotTable
Link copied to clipboard
suspend fun createIndex(aggregate: String)

Makes sure of aggregate's snapshot index, as the store's DDL mode says.

Link copied to clipboard

Nothing to create up front: each aggregate type's index is created on its first use, since the store does not know the context's aggregate types. Kept for the host, which calls it at startup.

Link copied to clipboard
open suspend override fun load(ref: AggregateRef): SnapshotRecord?
Link copied to clipboard
open suspend override fun mark(type: AggregateType, revision: Int, rebuilding: Boolean?, backfilled: Boolean?): SnapshotTable?
Link copied to clipboard
open suspend override fun markStale(ref: AggregateRef): Boolean
Link copied to clipboard
open suspend override fun pending(type: AggregateType, revision: Int, after: AggregateRef?, limit: Int): List<AggregateRef>
Link copied to clipboard
open suspend override fun rewrite(snapshot: SnapshotRecord): Boolean
Link copied to clipboard
open suspend override fun save(snapshot: SnapshotRecord): Boolean
Link copied to clipboard
open suspend override fun saveAll(snapshots: List<SnapshotRecord>): List<SnapshotSaveOutcome>

Saves snapshots: in the rounds SnapshotSaveOutcome.rounds gives, each round's snapshots of one aggregate type are read with one real-time _mget, decided each as save decides, and written with one _bulk request, each write guarded as save guards one (if_seq_no, if_primary_term, or create): two round trips a batch instead of two a snapshot. The writes another one beat (409) are read, decided and written again together. A write Elasticsearch refuses alone is SnapshotSaveOutcome.Failed: refused as a value (400) with an IllegalArgumentException, as a save would throw, and one the cluster cannot take now (429, 503) with a me.ahoo.wow.spi.StorageUnavailableException.

Link copied to clipboard
open suspend override fun table(type: AggregateType): SnapshotTable?