JasperFx / marten

.NET Transactional Document DB and Event Store on PostgreSQL
https://martendb.io
MIT License
2.83k stars 448 forks source link

Event Store Internal Improvements for 7.25 #3314

Closed jeremydmiller closed 2 months ago

jeremydmiller commented 3 months ago

Continuing a discussion from Discord, but gathering this all together to track outstanding tasks. The goal here is to reduce the overhead of appending events and Inline projections by reducing network round trips as much as possible. This is also to improve the new Quick Append event workflow and address #3310. This work is driven by scalability and performance concerns from two JasperFx clients concerned about scalability for very large workloads

Overarching changes:

Event Appending Data Fetching

When appending events, there's potentially a series of preparatory data fetching steps that has to take place before the events can be appended and any or all inline projections can be applied. Today this feature is very optimized for appending events to a single stream at a time -- which isn't necessarily how folks are actually using this feature today. To optimize the process -- and I'm making a very large assumption that reducing the network round trips to the database will more than offset extra in process steps -- I think we make the event appending be a multi-step process to create a new intermediate helper to enable data fetching like where the active IEventAppender and each registered Inline projection have a method something like:

public interface IEventAppendingStep
{
    Task ApplyAsync(DocumentSessionBase session, CancellationToken cancellation);
}

// This would be used to register data query needs in a batch, and return a "continuation"
// that applies changes to the session later
public IEventAppendingStep FetchData(IBatchedQuery query);

There's some more detail to this so there's some interplay between Inline projections that get the stream state for you so you don't have to make a second fetch

Tasks

    [Fact]
    public async Task start_and_append_events_to_same_stream()
    {
        await using var session = theStore.LightweightSession(tenant);

        session.Logger = new TestOutputMartenLogger(_testOutputHelper);

        var streamId = Guid.NewGuid().ToString();

        session.Events.StartStream<LoadTestInlineProjection>(streamId,new LoadTestEvent(Guid.NewGuid(), 1),
            new LoadTestEvent(Guid.NewGuid(), 2), new LoadTestEvent(Guid.NewGuid(), 3));
        await session.SaveChangesAsync();

        _testOutputHelper.WriteLine("APPEND STARTS HERE");

        session.Events.Append(streamId, new LoadTestEvent(Guid.NewGuid(), 4), new LoadTestEvent(Guid.NewGuid(), 5));
        await session.SaveChangesAsync();

        var doc = await session.LoadAsync<LoadTestInlineProjection>(streamId);
        doc.Version.ShouldBe(5);

    }