dotnet/orleans · critical · InvalidOperationException
Azure Table journal header belongs to journal id "{journalId
Error message
Azure Table journal header belongs to journal id "{journalId ?? "<missing>"}", not "{_journalId.Value}". Ensure that configured partition keys are unique. What it means
The header row's JournalId property does not match the journal id this storage instance was constructed with. ValidateHeaderJournalId (AzureTableJournalStorage.cs:1051) allows a missing JournalId (legacy headers are backfilled), but a present-but-different value means two distinct journals are mapping to the same Azure Table partition key — a collision that would mix or overwrite unrelated data.
Source
Thrown at src/Azure/Orleans.Journaling.AzureStorage/AzureTableJournalStorage.cs:1058
}
private IJournalMetadata CreateJournalMetadata(ETag eTag, TableEntity entity)
{
ValidateHeaderJournalId(entity);
return new JournalMetadata(
NormalizeFormat(entity.GetString(FormatPropertyName)),
eTag == default ? null : eTag.ToString(),
DeserializeCallerMetadata(entity.GetString(MetadataPropertyName)));
}
private void ValidateHeaderJournalId(TableEntity entity)
{
var journalId = entity.GetString(JournalIdPropertyName);
// Legacy headers did not store the canonical journal id. They remain addressable through the
// configured partition mapping and are backfilled by the next append, replace, or metadata update.
if (journalId is not null && !string.Equals(journalId, _journalId.Value, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Azure Table journal header belongs to journal id \"{journalId ?? "<missing>"}\", not \"{_journalId.Value}\". " +
"Ensure that configured partition keys are unique.");
}
}
private static string? NormalizeFormat(string? format) => format is { Length: > 0 } ? format : null;
private static string SerializeCallerMetadata(IReadOnlyDictionary<string, string>? metadata)
=> metadata is { Count: > 0 } ? JsonSerializer.Serialize(metadata) : "{}";
private static Dictionary<string, string> DeserializeCallerMetadata(string? json)
{
if (json is not { Length: > 0 })
{
return new Dictionary<string, string>(StringComparer.Ordinal);
}
Dictionary<string, string>? parsed;View on GitHub (pinned to fca799fa70)
Solutions
- Ensure GetPartitionKey produces a unique partition key per JournalId (the default percent-encodes JournalId.Value — keep it or make your mapper injective).
- If the collision already happened, move one journal's data to a separate table or delete the conflicting partition after confirming ownership.
- Do not reuse partition keys across journals that share a table.
Example fix
// before — collides for many journals options.GetPartitionKey = _ => "journal"; // after — unique per journal id options.GetPartitionKey = jid => Uri.EscapeDataString(jid.Value);
Defensive patterns
Strategy: validation
Validate before calling
// Verify the partition-key mapper is injective for the journals in use
var keys = journals.Select(options.GetPartitionKeyForJournal).ToList();
if (keys.Distinct(StringComparer.Ordinal).Count() != keys.Count)
throw new InvalidOperationException("Partition key collision detected across journals"); Type guard
static bool IsPartitionKeyUniquePerJournal(
Func<JournalId, string> mapper, IReadOnlyList<JournalId> ids)
=> ids.Select(mapper).Distinct(StringComparer.Ordinal).Count() == ids.Count; Prevention
- Keep the default percent-encoding partition-key mapper or prove your mapper is injective.
- Do not share partition keys across journals.
- Use separate tables for unrelated journal sets.
When it happens
Trigger: GetPartitionKey maps two different JournalId values to the same partition key; the header read from that partition was written by a different journal and already carries its own JournalId; thrown at AzureTableJournalStorage.cs:1056-1061.
Common situations: A custom GetPartitionKey delegate (AzureTableJournalStorageOptions.GetPartitionKey) returns a constant or colliding key for multiple journals; two silos/apps share a table with overlapping partition-key ranges; a partition-key scheme that drops distinguishing parts of the JournalId.
Related errors
- The configured Azure Table journal client provider returned
- Value cannot be null. (Parameter 'createClientCallback')
- Value cannot be null. (Parameter 'connectionString')
- Value cannot be null. (Parameter 'serviceUri')
- Value cannot be null. (Parameter 'tokenCredential')
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/cec75650ad9112a0.
Report an issue: GitHub.