I'm designing an internal event store library based off SQL Server. One challenge I had to solve is to allow concurrent writes while still being able to generate a reliable Log Sequence Number (LSN) that could be used to consume the stream of events in order as they appear.
The way I'm solving this is by having a
loose_position bigint IDENTITY(1, 1) column and a
strict_position bigint NULL column which will get updated by a single writer and used as a consuming checkpoint. The sequencer does something like the following in a
READ COMMITTED transaction:
DECLARE @maxPosition bigint; SELECT @maxPosition = MAX(strict_position) FROM RecordedEvent WITH(READPAST) WHERE strict_position IS NOT NULL; WITH seq AS ( SELECT id, @maxPosition + ROW_NUMBER() OVER(ORDER BY loose_position) AS strict_position FROM RecordedEvent WITH (READPAST) WHERE strict_position IS NULL ) UPDATE r WITH (READPAST) SET strict_position = @maxPosition + seq.strict_position FROM RecordedEvent r INNER JOIN seq ON seq.id = r.id;
I wanted to prevent the sequencer from being blocked by newly inserted records and vice-versa, so I've used
READPAST which seems to work, but I'm not sure that's the best approach.
Please note that I also have a
UNIQUE CLUSTERED INDEX ON loose_position and a
UNIQUE NONCLUSTERED INDEX ON strict_position WHERE strict_position IS NOT NULL, but I'm not entirely sure how these affects locking.
Could someone confirm that my current solution is appropriate or if there are better ways to achieve what I want?