SQL Server: every stream lookup by name does a key lookup to get `Version`
メンテナーはふだん 7 日以内に返信
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 25/100
調査の方向性
Start with the schema in src/SqlServer/src/Eventuous.SqlServer/Scripts/1_Schema.sql, where the Streams table and UQ_StreamName are defined, and with check_stream, append_events and the CATCH block that matches the constraint name. Pull request #621 is already open against this issue, so check it before starting. Done means the lookup by name becomes a covered index seek, existing databases get an upgrade step, and the concurrency tests from #598 still pass.
索引モデルが issue の本文から書いたものです。
説明
Summary
check_stream, read_stream_forwards, and read_stream_backwards all start with the same query:
SELECT
@current_version = [Version],
@stream_id = StreamId
FROM __schema__.Streams
WHERE StreamName = @stream_name;
The only index on StreamName is the unique constraint UQ_StreamName. It holds StreamName and the clustered key StreamId, but not Version. So SQL Server seeks UQ_StreamName and then does a key lookup into PK_Streams to get Version. This occurs once for every append and every stream read.
How I found it
We capture the actual execution plans from our integration tests and analyze them with Performance Studio. It reports this statement as a critical "Key Lookup":
Key Lookup — SQL Server found rows via a nonclustered index but had to go back to the clustered index for additional columns. Columns fetched: Streams.Version
Statement in the plan (from check_stream, called by append_events):
SELECT @current_version = [Version], @stream_id = StreamId
FROM eventuous.Streams
WHERE StreamName = N'InventoryNodeStock-102'
Environment: Eventuous 0.17.0, SQL Server 2022. The schema in src/SqlServer/src/Eventuous.SqlServer/Scripts/1_Schema.sql on dev has the same index, so the plan shape is the same there.
Suggested change
Replace the unique constraint with a unique index that has the same name and includes Version:
CREATE TABLE __schema__.Streams
(
StreamId INT IDENTITY (1,1) NOT NULL,
StreamName NVARCHAR(850) NOT NULL,
[Version] INT DEFAULT (-1) NOT NULL,
CONSTRAINT PK_Streams PRIMARY KEY CLUSTERED (StreamId) WITH (OPTIMIZE_FOR_SEQUENTIAL_KEY = ON),
CONSTRAINT CK_VersionGteNegativeOne CHECK ([Version] >= -1),
INDEX UQ_StreamName UNIQUE NONCLUSTERED (StreamName) INCLUDE ([Version])
);
A UNIQUE constraint cannot have INCLUDE columns, so it must become a unique index. Keep the name UQ_StreamName: the CATCH block in check_stream looks for that name in the error message, and already handles both error 2627 (constraint) and 2601 (unique index).
Trade-offs
- Benefit: The lookup by name becomes one index seek. This saves a few logical reads on each append and each stream read, and the read path touches one index instead of two.
- Cost: The
UPDATE ... SET [Version]inappend_eventsmust now also write the nonclustered index.Versionis a 4-byteINT, so the index stays narrow, but it is one more index write for each append. - Locking:
check_streamtakesUPDLOCKon the row it reads. With a covering index, that read locks only theUQ_StreamNamekey, not the clustered row. Two concurrent appends to the same stream still meet on the sameUQ_StreamNamekey, so they must still be serialized, but the concurrency tests added in #598 should confirm this. - Existing databases:
1_Schema.sqlcreates the table onlyIF OBJECT_ID(...) IS NULL, so existing databases do not get the change. The change needs an upgrade step (drop the constraint, create the index), or a note in the release notes.
The gain for each call is small. But this query runs on the hot path of every append and every aggregate load, so I think the change is worth it. I can open a PR if you agree with the approach.
- 主要言語
- C#
- スター
- 512
- フォーク
- 99
- 平均マージ
- 5日 6時間
- マージ済み PR(30日)
- 5
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Eventuous/eventuous のほかの issue
-
MongoDB integration tests fail on newer GitHub Actions runner kernels対応中かも @nmummau が 3 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
Eventuous/eventuous#626 · コメント 2 件 ·
メンテナーはふだん 7 日以内に返信
-
`dotnet format style --verify-no-changes` fails with naming violations and unnecessary imports対応中かも @nmummau が 3 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
Eventuous/eventuous#624 · コメント 1 件 ·
メンテナーはふだん 7 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
Eventuous/eventuous#627 · コメント 1 件 ·
メンテナーはふだん 7 日以内に返信
-
SQL Server: `append_events` does not need the `@inserted` table variable対応中かも @nmummau が 9 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 20/100
Eventuous/eventuous#619 · コメント 1 件 ·
メンテナーはふだん 7 日以内に返信
-
SQL Server: stream reads do one key lookup per event, and `IDX_EventsStream` duplicates `UQ_StreamIdAndStreamPosition`対応中かも @nmummau が 9 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 18/100
Eventuous/eventuous#618 · コメント 1 件 ·
メンテナーはふだん 7 日以内に返信
Eventuous/eventuous の issue をすべて見る
似ている issue
-
devtools
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
clawsweeper:fix-shape-clear clawsweeper:needs-maintainer-review clawsweeper:no-new-fix-pr clawsweeper:not-repro-on-main impact:ux-release-blocker issue-rating: 🦪 silver shellfish P0
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
openclaw/openclaw-windows-node#1843 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
-
kind:ux size:S type:bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
elsa-workflows/elsa-foundation#3256 ·
メンテナーはふだん 1 日以内に返信
-
[Bug]: Revit selector reads process ids 2000-2099 as years (flaky AgentClientTests.AYearOrAProcessIdSelectsTheHost)対応中かも @Wurschdhaud が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
pyrevitlabs/pyRevit#3817 ·
メンテナーはふだん 1 日以内に返信