Bulk event insert applies the schema once per database, not per call (#4946)#4949
Merged
Conversation
…H-4946) The batch BulkInsertEventsAsync overloads began with Storage.ApplyAllConfiguredChangesToDatabaseAsync() on every call: a full schema delta -- partition introspection plus information_schema sweeps -- across every database in the store, per batch. A conversion tool importing in 1,000-event batches paid that per batch; measured in a 512-database sharded store it collapsed throughput to ~17 events/s and parked the connection pool on Weasel's partition introspection query. The streaming overload has no such call and is fine, which is the tell that the apply is a leftover, not part of the contract. The apply is now: - skipped entirely when the effective AutoCreate is None, where it is a no-op by contract and only the introspection cost remains, and - otherwise run at most once per *database*, and only for the database being imported into rather than every database in the store. Concurrent batches are safe: the per-database entry is a Lazy<Task> with ExecutionAndPublication, so racing callers await a single in-flight apply. A failed apply is evicted so a later call retries instead of caching the failure. Reported by @erdtsieck with production measurements. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…H-4946) The per-database apply is memoized, so the first caller to arrive owns the single in-flight task that every concurrent caller for that database awaits. Binding that shared task to the first caller's CancellationToken meant one batch's cancellation would fail sibling batches that were never cancelled. The apply now runs untethered and each caller awaits it under its own token. A schema apply is short and idempotent, so letting it run to completion is the cheaper trade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merged
This was referenced Jul 14, 2026
Merged
Open
This was referenced Jul 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4946
Mechanism
Both batch
BulkInsertEventsAsyncoverloads onDocumentStorebegan with:on every call. That is not "check the schema cheaply" —
IMartenStorage.ApplyAllConfiguredChangesToDatabaseAsync()callsTenancy.BuildDatabases()and runs a full schema-delta computation (partition introspection +information_schemasweeps) against every database in the store, in parallel. A conversion tool importing in ~1,000-event batches therefore paid a full store-wide introspection per batch, even underAutoCreate.Nonewhere the apply cannot do anything by design.Measured in production on a 512-database sharded store: import throughput collapsed to ~17 events/s (a 686k-event tenant projecting to ~11 hours), with the connection pool filled by ~370 connections whose last statement was the Weasel partition-introspection query. The streaming overload
BulkInsertEventStreamAsynchas no such call and does >3,000 events/s — the tell that the apply was a leftover rather than part of the contract.The fix
ensureBulkInsertSchemaAsync(IMartenDatabase, CancellationToken)inDocumentStore.csreplaces the unconditional call:AutoCreateisNone. The apply is a contractual no-op there, so calling it is pure waste. Nothing is executed, no connection is taken.Thread safety
Batches may be imported concurrently. The memo is a
ConcurrentDictionary<string, Lazy<Task>>keyed byIMartenDatabase.Identifier, withLazyThreadSafetyMode.ExecutionAndPublication, so racing callers for the same database all await one in-flight apply —GetOrAdd's factory can race, butLazyguarantees the apply body runs once. Keying on the database identifier rather than on theDocumentStoreis what makes the guard correct for a sharded/multi-tenant store: per-database, once each; never per-call.A failed apply evicts its entry, so a later call retries rather than caching the failure (or a cancellation) forever.
Document bulk insert path
Checked: no,
BulkInsertAsync/BulkInsertDocumentsAsyncdo not have this bug. They go throughBulkInsertion, which calls_tenant.Database.EnsureStorageExistsAsync(typeof(T), ...)— the ordinary per-feature, per-database ensure that Weasel memoizes, the same one every session write uses. It is not a full-store schema delta. Left alone.Tests
src/EventSourcingTests/Bugs/Bug_4946_bulk_insert_does_not_apply_schema_per_call.cs— these assert how many times the apply runs, which is the actual regression, not merely that the insert still works:AutoCreate.Noneover a pre-applied schema, 3 batches → 0 applies, events land correctlyGreen: the new 4, the existing
BulkEventAppendTests/BulkEventStreamAppendTests(29), andTenantPartitionedEventsTestsRegressions + Migration (26), which cover bulk import on a partitioned store.Not verifiable locally: I cannot stand up a 512-database sharded store, so the production throughput numbers are the reporter's, not remeasured here. What is verified locally is the invocation count, which is the mechanism behind them.
Docs: added a "Schema Application" note to
docs/events/bulk-appending.md.Reported by @erdtsieck with production measurements.
🤖 Generated with Claude Code