Live data from Hacker News

What they don’t tell you about event sourcing

medium.com

51–60 of 81 posts

Re: What they don’t tell you about event sourcing

#51

I've worked with event sourced systems quite a bit in production and at scale, and have written a book on Akka and another one on related topics. While I have been an advocate of the approach, My experiences are guiding me away from implementing Event Sourcing in many use cases (especially where the entities are long lived). While CQRS is more complex, I'm more likely to implement CQRS without event sourcing, where a…

I admittedly don't have your level experience, especially with such huge scale. However, I've worked with smaller systems and have found a "sweet-spot" that worked well in my cases:

1. Keep a traditional RDBM system. Place in here everything that needs consistency (e.g., the bank's accounts, balances and transactions) or has to be stored indefinitely.

2. That part of the system generates events. Those events are used to maintain Queryable stores that are better structured for your required queries. For instance, you could store per-client transaction lists in here. Or you could fill in an OLAP database, or whatever.

3. For each query-able store, implement a process that can populate it from the RDBMS data. This serves several purposes:

a) You can rebuild any such stores at any time. This removes the durability requirement from these stores, so they may be simpler and more efficient.

b) You can compare a rebuilt store against the current one. If there are any differences, there's either a bug in your event tracking code or a bug in the populator.

c) This consistency-check procedure is really useful during development and testing. When you make a change, it is really hard to get both the event-sourced store and the populator wrong but consistent with each other. This only happened to me due to mis-specified or mis-understood specifications, completely fencing out pure programming mistakes.

Of course, this system only works so long as your write volume can be ingested by your RDBMS. Luckily, this is the case on all of my current projects (and I would argue most projects out there). Notice that scaling reads should be much easier!

Finally, this architecture just accepts that read-errors may happen (e.g., a client doesn't see a transaction in their transactions list because some event got lost). This hasn't been a huge problem for us, since the mistake will be repaired on the next "reconciliation" process (along with a warning to get devs to investigate what happened). These reconciliations can be run as frequently or as sparsely as desired, or even on some user-initiated action (e.g.: the support guy has a button to "force reconciliation now" and instructions to press it whenever a customer complains about missing transactions).

Re: What they don’t tell you about event sourcing

#52

Earlier quoted context omitted.

>> can also be synchronous > Then you're giving up several of the benefits of CQRS, and might as well just not bother with the additional complexity. Even without asynchronous read/write, there are still benefits worth the (arguably, small) additional complexity. For instance, the ability to add new functionality without having to migrate existing data is amazing.

You still have migrations, but they exist on the read store, which means they can be done semi-transparently to clients (pause writes and let them queue up, migrate existing read store to new instance, point read calls and indexers to new store, resume writes). CQRS adds a lot of complexity. Sometimes it's absolutely worth it, especially if you've already invested in the expertise and tooling to support it. It drasti…

No need to pause writes or migrate stores. You can have the old and new versions of the read model co-exist and read from the same event stream without disabling writes. Once the new version is deployed and running, you shut down the old version (isn't this already your deployment model?)

It does change the scaling math, but don't automatically assume that every ES+CQRS system is intended for thousand-writes-per-second, terabytes-per-day kind of scales. If the system stays at ten-writes-per-second, megabytes-per-day, a read store (beyond in-memory projections), a queue or an indexer are not necessary.

As for asynchronous error handling, and delays between reads/writes : why would that be a consequence of ES+CQRS ? It would be a consequence of implementing ES+CQRS with asynchronous or eventually consistent behaviour...

Re: What they don’t tell you about event sourcing

#54
post #40

I don't see much discussion of event-sourcing simply using a SQL database (i.e. skipping the CQRS part). This would allow you to keep your CP (strongly-consistent) semantics. While this clearly wouldn't work in high-volume cases (i.e. where you _actually_ need CQRS), it seems like this would be the simplest option for many systems. I see a lot of articles advocating for immediately jumping into CQRS, which seems like…

I’ve implemented this.

TLDR: orm’s + large transactions resulted in a lot of unexpected complexity.

At some point you get really big transactions because one write triggers five process managers which all trigger more writes and so on and so on. Performance was not a problem but I was surprised by the complexity of these big transactions in combination with an orm.

I dont have a concrete example but over the course of two years we have encountered multiple bugs that took days to solve. Theres one of these bugs that we fixed without identifying the root cause until this day.

Re: What they don’t tell you about event sourcing

#55
post #20
post #8

Regarding eventual consistency, a CQRS\ES system can also be synchronous, or partially. You could have listeners for events that need to supply a strongly consistent model and others events that feed parts of the system that don't need strong consistency. "However the events in a event store are immutable and can’t be deleted, to undo an action means sending the command with the opposite action" Well they don't have…

I also share same opinion based on my experience. Events can be modified and deleted but that must be an exceptional situation (GDPR and other compliances, etc.). But even if it's exceptional you have to provide a clear and easy way to do so and that increases complexity of the solution by a lot. Another thing is strongly consistent models, there may be valid requirements in some problem areas to have a strongly cons…

If the decision to abandon strong consistency involved careful analysis of the performance/maintenance trade-offs, then by definition the lack of consistency is less expensive than keeping a consistent but low-performance model, and you're just paying the price of having to solve a Hard Problem.

But if strong consistency was abandoned because someone wrote general statements in favor of eventual consistency...

Re: What they don’t tell you about event sourcing

#56

Good article. I've spent the last year migrating to an event sourced system, so thought I'd share some thoughts. On the eventual consistency point, I've found you can get quite far with having the read model managing the race condition. This probably doesn't work everywhere, but in our system, multiple users can accept an invitation, so we have something like `InvitationAccepted{invitation_id, user_id}`. It's possibl…

This is exactly what we did in our system and it worked wonderfully. It also has the side effect of avoiding locks or contention when doing such mutations.

Re: What they don’t tell you about event sourcing

#57

The last section on Operational Flexibility and the inability to change the event history raises a very good point. Like most of the issues, the solution requires experience to know when you are at the Goldilocks point (Just Right). This specific issue has a lot in common with managing database migrations in django or any other migration system. The ideal situation is to create migrations that can always be rolled ba…

Idk why people are saying that ES data is always immutable. They can be by default sure, but if a facility is useful to change the history, why not?

If you need to change history you can just create new events that accomplish your mutation -- and even mark them as a type 'change history' or some such obvious identifier so when you inspect the event stream you know exactly what you are looking at.

Re: What they don’t tell you about event sourcing

#58
post #51

I've worked with event sourced systems quite a bit in production and at scale, and have written a book on Akka and another one on related topics. While I have been an advocate of the approach, My experiences are guiding me away from implementing Event Sourcing in many use cases (especially where the entities are long lived). While CQRS is more complex, I'm more likely to implement CQRS without event sourcing, where a…

I admittedly don't have your level experience, especially with such huge scale. However, I've worked with smaller systems and have found a "sweet-spot" that worked well in my cases: 1. Keep a traditional RDBM system. Place in here everything that needs consistency (e.g., the bank's accounts, balances and transactions) or has to be stored indefinitely. 2. That part of the system generates events. Those events are used…

Yeah I'm using kafka right now with small queues between processes (generally implemented as elixir/OTP gen_server). With a blend of RDBMS (postgres which is now a swiss army knife), Redis (also a swiss army knife), and zookeeper (mostly because I know it well and it's there with kafka. It's very useful for co-ordination across processes.)

Because the events coalesce into kafka, you can use a mechanism like logstash to spew this log data into eg elastic search and then use that for your queries. Or write to the db here (in process) or there (cqrs style.) There are different architectural approach used, and they all yield good results, of very high reliability in processing, if slightly "eventual."

It works well. We do use rdbms too, but a lot of the time we avoid reading from it after initialization (or after encountering an exception causing an actor to restart). Depends on the data though. Some places I read on every command because I know the risk front is small and it's easier for a jr to get in there and understand what's happening. A good rule is that a single process should own that data so you don't have to worry much about consistency related concerns. In microservices good practices, each service should really own its own data completely but I believe that it's fair enough to say a bounded context owns its own data (even if in a shared rdbms) if building at a smaller scale.

In terms of code, I'm usually building with some variation of onion architecture these days, where all context is build on the outside (eg receiving a request, getting info from state or db), and pure domain logic 100% effect free exists on the inside that receives all of the data from the outside and turns out some commands or events in response. This makes the important logic very testable, and easy to reason about. Core domain logic has no effects - not even logging - but only generates events/commands in response to commands and events. You never mock that stuff - it's the star of the show. Nor do you ever have to. The generated commands and events are later "applied" by the outer layer, logging, shipping them off to kafka, and/or writing some stuff to a database.

That's where I'm at today. I am working in realtime systems so it's a good fit for the approaches. I'm finding the software is turning out very well. We're working fairly quickly, the software is very encapsulated to a bounded context, the domain logic is clear in the core, easy to test and read, and change. Or even rewrite if needed.

Re: What they don’t tell you about event sourcing

#59
post #29

I've worked with event sourced systems quite a bit in production and at scale, and have written a book on Akka and another one on related topics. While I have been an advocate of the approach, My experiences are guiding me away from implementing Event Sourcing in many use cases (especially where the entities are long lived). While CQRS is more complex, I'm more likely to implement CQRS without event sourcing, where a…

Elixir is fantastic, especially in these use cases because it allows for stateful in-memory representations of an entity with less development burdens than elsewhere. I find it useful to have a GenServer for complex entities like state machines modeling business processes. I'm fine with the simple entities using the database schema as the state definition. However with the complex entities I still find I run into the…

Yeah for sure. That's kind of the idea with event sourcing in general, but I think with elixir/otp, it's easy to see how event-sourcing is only a persistence mechanism. Usually how I think about it is Event Sourcing (or gen server using a db as a recovery mechanism) "moves" the source of truth from a db into the process that owns the data.

Re: What they don’t tell you about event sourcing

#60
If we choose to build a business critical functionality around this eventual consistency can have dire ramifications. There are use cases that availability is the needed property of a system but there are also use cases where consistency also is, where is better to not make a decision rather than making it based on stale information.

I see this issue raised quiet often. If consistency is paramount you can make commands on certain aggregates be synchronous all the way to updating the read model. There's nothing that says you MUST have a queue in-between the event log for an aggregate and the logic that updates the read model. Use common sense.

Typically shines the most when pinpointing parts of the system that benefit from it, identifying a specific bounded context in DDD terms, but never on a whole system.

I feel like Greg Young has taken great pains to make this clear. This should be taken for granted when attempting CQRS.

Also your events will be based on a SomethingCreated or SomethingUpdated which has no business value at all. If the events are being designing like this then it is clear you’re not using DDD at all and you’re better of without event sourcing. Finally, depending on the requirements on how the synchronous the UI and the flow of the task is the eventual consistency can, and most of the times will, have a klinky feel to it and deliver a poor user experience.

If the read and write model are being updated asynchronously from the UI you're gonna have to adopt an optimistic caching scheme on the client. This is why GraphQL subscriptions are pretty much boilerplate for any client I build against a CQRS service. The Apollo client seems to handle this rather well.

Converting data between two different schemas while continuing the operation of the system is a challenge when that system is expected to be always available. Due to the very nature of software development new requirements are bound to appear that will affect the schema of your events that is inevitable.

I hereby give you permission to use the Strategy Pattern. Problem solved.

The events can’t be too small, neither too large they have to be just right. Having the instinct to get it right requires an extensive knowledge of the system, business and consumer applications, it’s very easy to choose the wrong design.

Greg Young and others have talked quite a bit about how to bound aggregates.

However the events in a event store are immutable and can’t be deleted, to undo an action means sending the command with the opposite action.

This is why bookkeeping systems have the idea of "journal entries". I haven't implemented one for an event sourced system but I can see how this might work.

Overall great post. Really enjoyed that the author took the time to walk us through all of these issues. Most are non-trivial.

Post reply on HN