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

  1. Ensure GetPartitionKey produces a unique partition key per JournalId (the default percent-encodes JournalId.Value — keep it or make your mapper injective).
  2. If the collision already happened, move one journal's data to a separate table or delete the conflicting partition after confirming ownership.
  3. 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

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


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/cec75650ad9112a0. Report an issue: GitHub.