an aggregate is per (ddd) definition consistent. That means it will always be in a valid state, and when committed, it is either completely committed, or not at all. It will never be half-way. To guard that consistency in Axon, all events generated by an aggregate have a sequence number. The combination of aggregate and sequence number must be unique in the events in the event store.
Now, imagine two machines, A and B, that both load an aggregate Z. They both get a copy of the aggregate, based on past events in the event store. Let’s say they got Z@22 (last applied event was sequence 22).
Machine A is executing a “PlaceBuyOrderCommand” on Z. The result is OrderPlacedEvent@23 and TradeExecutedEvent@24 (because there was a matching sell-order).
Meanwhile, machine B is processing a CancelSellOrderCommand, which results in SellOrderCancelledEvent@23.
Now here is your concurrency problem: they both want to store their events in the Event Store, but only one of them (the happy first one) will succeed. In some cases, you could automatically “merge” the events by saying that the loser would resequence its numbers. But what if the cancelled sell order was the one that matched the new buy order on machine A? Those changes cannot be merged.
Therefore, the “losing” change will need to be discarded, the aggregate reloaded including the newly generated events and the command re-executed.
In a high-speed environment, you want to prevent this. The only way to do so, is by ensuring that a single aggregate instance only lives on a single machine (preferably in the cache). If all commands on a single aggregate are executed on that machine, you don’t have the concurrency problems.
And now comes the last step. If you have a command that executed on more than one aggregate, it is a lot harder to guarantee that each of the aggregates only “lives” on a single machine at any given time. That’s an advantage that sticking to a 1 command to 1 aggregate relation gives you.
Hope that clarifies it a bit.