Akka-persistence-inmemory is a plugin for akka-persistence that stores journal and snapshot messages memory, which is very useful when testing persistent actors, persistent FSM and akka cluster
The problem happened then buffer's bound stops in between of events with timeuuid like:
...
e3e99ed0-a21d-11e8-b31a-e9435a127f49 // A: last event which put into a buffer
e3e99ed1-a21d-11e8-b31a-e9435a127f49 // B: next one
...
InMemoryReadJournal::eventsByTag::nextFromOffset uses unix timestamp to calculate 'next' event:
case TimeBasedUUID(time) => TimeBasedUUID(UUIDs.startOf(UUIDs.unixTimestamp(time) + 1))
and it skips event B because both of them have same unix timestamp: 1534510989629, and 'next' uuid will be:
e3e9c5e0-a21d-11e8-b31a-e9435a127f49
The difference in nanoseconds:
137538037896290000 for A
137538037896290001 for B
Where it comes from ?
InMemoryAsyncWriteJournal uses following functions to generate timeuuid for an event:
def nowUuid: UUID = UUIDs.timeBased()
def getTimeBasedUUID: TimeBasedUUID = TimeBasedUUID(nowUuid)
def timeBased(): UUID = {
new UUID(makeMSB(UUIDUtil.getCurrentTimestamp()), ClockSeqAndNode)
}
If we will take a look on UUIDUtil.getCurrentTimestamp more closely, we can see following:
public static final AtomicLong lastTimestamp = new AtomicLong(0L);
...
long now = fromUnixTimestamp(System.currentTimeMillis());
long last = lastTimestamp.get();
if (now > last) { ...
} else { ...
long candidate = last + 1;
So if two (or more) events are persisted in same millisecond, nanoseconds will be added to timeuuid. But they are not
taken into account when events are read from a journal.
PS: I also added the test for that scenario, unfortunately test is very depended on timing (performance)
and may NOT fail even with broken implementation.
I was able to choose parameters which gives me like ~100% failure rate.
I mean, the test never passed successfully with original implementation on my box
but I cannot guarantee that for other boxes.
(cherry picked from commit da779101645a01e9861df94eec7723c3afcc4ad9)
The problem happened then buffer's bound stops in between of events with timeuuid like: ... e3e99ed0-a21d-11e8-b31a-e9435a127f49 // A: last event which put into a buffer e3e99ed1-a21d-11e8-b31a-e9435a127f49 // B: next one ...
InMemoryReadJournal::eventsByTag::nextFromOffset uses unix timestamp to calculate 'next' event: case TimeBasedUUID(time) => TimeBasedUUID(UUIDs.startOf(UUIDs.unixTimestamp(time) + 1))
and it skips event B because both of them have same unix timestamp: 1534510989629, and 'next' uuid will be: e3e9c5e0-a21d-11e8-b31a-e9435a127f49
The difference in nanoseconds: 137538037896290000 for A 137538037896290001 for B
Where it comes from ? InMemoryAsyncWriteJournal uses following functions to generate timeuuid for an event: def nowUuid: UUID = UUIDs.timeBased() def getTimeBasedUUID: TimeBasedUUID = TimeBasedUUID(nowUuid) def timeBased(): UUID = { new UUID(makeMSB(UUIDUtil.getCurrentTimestamp()), ClockSeqAndNode) }
If we will take a look on UUIDUtil.getCurrentTimestamp more closely, we can see following: public static final AtomicLong lastTimestamp = new AtomicLong(0L); ... long now = fromUnixTimestamp(System.currentTimeMillis()); long last = lastTimestamp.get(); if (now > last) { ... } else { ... long candidate = last + 1;
So if two (or more) events are persisted in same millisecond, nanoseconds will be added to timeuuid. But they are not taken into account when events are read from a journal.
PS: I also added the test for that scenario, unfortunately test is very depended on timing (performance) and may NOT fail even with broken implementation. I was able to choose parameters which gives me like ~100% failure rate. I mean, the test never passed successfully with original implementation on my box but I cannot guarantee that for other boxes.
(cherry picked from commit da779101645a01e9861df94eec7723c3afcc4ad9)