We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Comparisons
Documentation
(ReckonDB + evoq) vs Marten (.NET)
Marten is a full-stack event sourcing library for .NET: it provides the event store (PostgreSQL-backed), projections, aggregate lifecycle, and document storage in one package. The right comparison is against the full ReckonDB stack, reckon-db as the store and evoq as the framework, since Marten covers both layers.
This comparison is also interesting because Marten 9.0 (May 2026) shipped DCB support, one of very few event sourcing libraries outside ReckonDB to implement the pattern.
The Short Version
| ReckonDB + evoq | Marten | |
|---|---|---|
| Language / platform | Erlang/Elixir (BEAM) + polyglot via gRPC | .NET only |
| Store backend | Khepri/Ra (BEAM Raft) | PostgreSQL |
| Document store | ❌ | ✅ |
| DCB | ✅ filter-algebra, Raft-atomic | ✅ tag-based, PostgreSQL OCC (9.0+) |
| DCB event-type filter | ✅ | ❌ |
| DCB filter algebra (and/or) | ✅ | 🟡 tag combination only |
| Custom tag indexes | ✅ any value at append time | ✅ HSTORE (9.0+) |
| Arbitrary metadata index |
✅ {meta, Key} |
❌ |
| HA clustering | ✅ BEAM Raft | 🟡 PostgreSQL HA |
| Tamper resistance | ✅ HMAC + hash chain | ❌ |
| Polyglot gRPC access | ✅ | ❌ |
| Multi-node projection distribution | 🟡 (planned) | ✅ AsyncDaemon (7.0+) |
| Stream compaction | ❌ (planned) | ✅ (8.0+) |
| Managed cloud | ❌ | ❌ (any managed PostgreSQL) |
Marten’s DCB: What Is Accurate
Marten 9.0 added DCB via PostgreSQL HSTORE. This is a genuine implementation
of the DCB pattern, not marketing language. Events are tagged with typed
identifiers at append time; FetchForWritingByTags() reads across tagged events
and enforces consistency at write.
How it works:
-
Register tag types upfront (
RegisterTagType<SeatId>("seat")) -
Tag events before appending (
newEvent.WithTag(seatId)) -
Load state across all tagged events and check:
FetchForWritingByTags<T>(seatId) -
Save with an OCC check against the
mt_dcb_tag_versionside table
This is correct DCB behaviour. The consistency boundary is enforced; concurrent writes with conflicting tags are rejected.
Where it differs from ReckonDB’s DCB:
-
PostgreSQL OCC vs Raft log entry. Marten’s boundary is enforced via PostgreSQL optimistic concurrency on the
mt_dcb_tag_versionside table. ReckonDB’s conditional append is a single Ra log entry, the filter check and the write are indivisible at the consensus level, not at the database row level. -
Filter algebra. ReckonDB supports
{and_,[Filter]},{or_,[Filter]},{event_type, binary()}, and tag filters composably. Marten’s DCB is tag- combination-only: you cannot compose a tag filter with an event type filter inside the same atomic consistency boundary. -
Operational overhead. The
mt_dcb_tag_versionside table grows indefinitely; Marten’s documentation flags this as requiring periodic maintenance. ReckonDB’s indexes are maintained inside the Ra log storage.
Where ReckonDB + evoq Leads
Platform
Marten is .NET-only. If your backend is Elixir, Go, Python, or Rust, Marten is not an option. ReckonDB’s gRPC API lets any language access the same store. evoq is the Erlang/Elixir framework layer; teams using other languages can use the store directly via reckon-go or (once released) reckon-py.
Raft-native HA
Marten’s HA story is PostgreSQL HA: Patroni, pgBouncer, or a managed cloud provider manages failover. This is battle-tested infrastructure but the store has no self-managed consensus.
ReckonDB’s Ra cluster is Raft inside the OTP supervision tree. Leader election, log replication, and membership changes are managed by the store. A crashed node is isolated by OTP; no external coordinator is needed.
DCB event-type filter
ReckonDB’s DCB filter algebra includes {event_type, binary()}, composable with
tag filters:
%% Only look at seat_reserved events for show 42, not all tagged events
Filter = {and_, [
{event_type, <<"seat_reserved_v1">>},
{any_of, [<<"seat:42-A3">>]}
]},
{ok, Ctx} = reckon_dcb:read_context(StoreId, Filter),
In Marten, the tag query is not scoped to an event type within the DCB read. You would need to filter by type in application code after the tagged read, which is outside the atomic consistency boundary.
Arbitrary metadata indexes
ReckonDB indexes arbitrary metadata keys at append time:
Events = [Event#{ meta => #{<<"correlation_id">> => CorrId} }],
%% At read time, O(log n)
reckon_streams:read_by_metadata(StoreId, <<"correlation_id">>, CorrId)
Marten has no equivalent for indexing arbitrary metadata keys within the event store. You would add a custom PostgreSQL index to your schema and query it directly outside the Marten API.
Tamper resistance
HMAC + hash chain per store, verifiable at read time. Marten stores events in PostgreSQL with no chain integrity guarantee. For audit log requirements in regulated industries (finance, healthcare, SOC 2), ReckonDB can provide cryptographic proof that no event was altered after write.
Where Marten Leads
Document DB + Event Store in One
Marten is both an event store and a document database on PostgreSQL. Read models can be PostgreSQL documents, queryable via LINQ. The read and write sides live in the same database, with no additional network hop between the event store and the read model store.
ReckonDB’s read models are a separate concern. evoq projections write to wherever you choose (SQLite, PostgreSQL, ETS), but the store and the read model are not co-located by design.
AsyncDaemon: multi-node projection distribution
Marten’s AsyncDaemon spreads projection processing across multiple nodes (since v7.0). This is production-tested at scale. evoq’s projection workers are per-node; the equivalent multi-node distribution is not yet built.
.NET ecosystem depth
Marten integrates with Wolverine for messaging, Npgsql for PostgreSQL access, and the full ASP.NET Core ecosystem. The documentation is extensive and the community is active. If your team is .NET, Marten is the mature choice.
Stream compacting (Marten 8.0+)
Marten can compact old events into a snapshot representation, reducing on-disk storage while retaining current aggregate state. ReckonDB does not yet have a compaction primitive.
Managed PostgreSQL
Marten runs on any managed PostgreSQL: Neon, Supabase, RDS, Azure Database for PostgreSQL, Google Cloud SQL. Zero new infrastructure for teams already running managed PostgreSQL.
Worked Example: DCB Side by Side
Invariant: Prevent double-booking seat A3 on flight 42.
Marten (C#):
var seatId = new SeatId("FL-42", "A3");
// Tag the event at append time
session.Events.Append(streamId, reservedEvent.WithTag(seatId));
// DCB read + conditional write
var (seat, token) = await session.Events
.FetchForWritingByTags<SeatAggregate>(seatId);
if (seat.IsReserved)
throw new SeatAlreadyReservedException();
seat.Reserve(command.BookingId);
await session.SaveChangesAsync(); // OCC on mt_dcb_tag_version
ReckonDB + evoq (Erlang handler):
Filter = {and_, [
{event_type, <<"seat_reserved_v1">>},
{any_of, [<<"seat:FL-42:A3">>]}
]},
{ok, Ctx} = reckon_dcb:read_context(StoreId, Filter),
case seat_already_reserved(Ctx#ctx.events) of
true -> {error, already_reserved};
false ->
reckon_dcb:append_if_no_match(
StoreId, Filter, Ctx#ctx.position,
[#{ event_type => <<"seat_reserved_v1">>,
tags => [<<"seat:FL-42:A3">>],
data => #{ booking_id => BookingId } }]
)
end
Both are correct DCB implementations. The ReckonDB version additionally scopes
the consistency check to seat_reserved_v1 events only, which means the
position cutoff is evaluated against a smaller filtered set, lower read
amplification in stores with many event types per tagged stream.
Who Should Choose Which
Choose ReckonDB + evoq if:
- Your backend is not .NET
- You need composable DCB filter algebra (event type + tag combined in one boundary)
- Tamper resistance is a compliance requirement
- You need polyglot gRPC access from multiple language runtimes
- You want the store embedded in the BEAM cluster, not dependent on PostgreSQL HA
Choose Marten if:
- Your stack is .NET / ASP.NET Core
- You want event store + document read model in one PostgreSQL database
- You’re on managed PostgreSQL and want zero new infrastructure
- You need mature multi-node projection distribution (AsyncDaemon)
- The .NET ecosystem and community matter to your team