Skip to content

Commit 73aa8f5

Browse files
committed
Write partition scopes directly and reuse the registered payload serializer
Remove the profiled intermediate key component lists and reuse Orleans' singleton typed serializer with independently pooled sessions. Preserve literal key bytes, complete decoding validation, component bounds, aliases and versions. Validation: strict Debug solution build, touched-source formatting verification, governance, 279 focused operation/key tests, 144 serialization tests, one real HTTP SDK/MCP revocation and two-cold-restart scenario, 3041-case discovery and all five coverage selectors. Three alternating million-call measurements retain exact results: key allocation 408 to 152 B/call and decoded VectorSpace allocation 248 to 208 B/call; these are local component measurements.
1 parent 9d1a3b3 commit 73aa8f5

9 files changed

Lines changed: 110 additions & 28 deletions

File tree

‎docs/Features/InternalSerialization.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# InternalSerialization
22

3+
The internal payload reader obtains its typed serializer from Orleans' existing singleton registration. Each decode still owns a separate pooled session and performs the original complete envelope, version, root-type and trailing-byte validation. Reusing the registered serializer changes no generated bytes, aliases or persisted/public versions.
4+
35
The official Orleans 10.4.2 generated serializer encodes owned internal DTOs.
46
[ADR-060](../ADR/ADR-060-internal-serialization.md) defines the current contract.
57
Public HTTP/MCP JSON, exact user content, sortable keys, opaque ZoneTree bytes,

‎docs/Features/StorageRecovery.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# StorageRecovery
22

3+
REQ-KEYCODEC-001/003: partition scopes and dynamic suffixes are written directly into one canonical key buffer. The component ceiling applies to their combined count. `KeySpacePartitionEncodingTests` preserves literal persisted bytes, case, NUL escaping, Unicode, typed/dynamic value parity, the exact component boundary and the original null/unsupported-value refusals. No key version or persisted format changes.
4+
35
Storage mode selects physical ownership. With `IsolatedVolumes`, a silo-local Orleans Grain Service owns that node's ZoneTree stores (each with ZoneTree's own write-ahead log), file locks and ordered apply gate; copies use typed Orleans calls to those owners. With `SharedVolume`, all silos use the configured shared paths and one ordinary database grain activation acquires the physical session; other silos call that owner. Shared storage requires `ReplicaCount=1` and never permits concurrent writers to the same ZoneTree files. The local runtime retains handles until actual borrowers join and the session closes. Orleans owns placement and activation movement; deployed shared-filesystem locking and flush behavior remain unqualified.
46

57
This feature specifies the canonical key codec, physical ownership and lifetime, ZoneTree write-ahead-log durability and recovery, scoped read cuts, the current format and process-kill recovery qualification. With isolated storage, a successful persisted receipt requires every configured copy to confirm its synced local apply and reports the actual `PersistedCopies` ([ADR-136](../ADR/ADR-136-orleans-partition-ownership.md), [ADR-143](../ADR/ADR-143-independent-silo-replica-topology.md)); storage mode adds no format here. Backup and restore of a store are specified in [BackupRestore](BackupRestore.md).

‎docs/implementation/competitor-performance-review.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ The confirmed common-path waste is an auxiliary database read of the current pri
88

99
Independent request execution activations remain necessary. The real synchronous-concurrency test holds one execution thread and requires another request on the same connection and physical owner to complete. Orleans interleaving cannot preempt synchronous work. Removing request grains failed this design review and is not part of the implementation.
1010

11+
The follow-up active allocation profile attributes repeated list growth and component-array copies to `KeySpace.Partition`, and repeated typed serializer construction to our payload reader. Partition scope and suffix now share one encoder buffer; the reader uses Orleans' registered singleton typed serializer with an independent pooled session per decode. Literal key bytes, malformed envelopes, concurrent round trips and retained native records remain correctness gates. This is consumer API reuse, not a dependency defect or a change to storage formats.
12+
1113
The next targets are actual access and materialization work: scalar index-range seeks, exact vector top-K and graph count/vertex projection. An active allocation profile also identified repeated catalog decoding inside our storage adapter for every candidate point read. One immutable native read operation can reuse its own resource-mode decision while validating every complete key. This introduces no cross-request metadata authority or engine cache.
1214

1315
## Versions relevant to the repository benchmark

‎docs/implementation/status.json‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎scripts/Features/CodeQuality/functional-coverage.unit-test-inventory.json‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60378,6 +60378,50 @@
6037860378
"nonContributorReason": "required-control",
6037960379
"reviewBasis": "Direct encoded-key component-limit and malformed-input validation control; no database operation is invoked. Required ordinary control, not operation contributor."
6038060380
},
60381+
{
60382+
"className": "KeyLoad.FunctionalTests.StorageRecovery.KeySpacePartitionEncodingTests",
60383+
"reportClassName": "KeyLoad.FunctionalTests.StorageRecovery.KeySpacePartitionEncodingTests",
60384+
"methodName": "PartitionScopeKeepsLiteralBytesCaseEscapesAndEveryDynamicComponent",
60385+
"instanceName": "PartitionScopeKeepsLiteralBytesCaseEscapesAndEveryDynamicComponent",
60386+
"sourcePath": "tests/KeyLoad.FunctionalTests/StorageRecovery/Cases/KeyCodec/KeySpacePartitionEncodingTests.cs",
60387+
"sourceSha256": "0b4f2b2215bcf1999dfe279b44a7698977c8f9c6021c61b2a0c49a1292b0a02d",
60388+
"lineNumber": 14,
60389+
"endLineNumber": 26,
60390+
"classification": "required-non-contributor",
60391+
"exclusionReason": null,
60392+
"requirements": [
60393+
"REQ-KEYCODEC-001",
60394+
"REQ-KEYCODEC-003"
60395+
],
60396+
"acceptance": [
60397+
"AC-KEYCODEC-001",
60398+
"AC-KEYCODEC-003"
60399+
],
60400+
"nonContributorReason": "required-control",
60401+
"reviewBasis": "Direct partition-key byte, scope/suffix component-limit and malformed-input control; no database operation is invoked. Required ordinary control, not operation contributor."
60402+
},
60403+
{
60404+
"className": "KeyLoad.FunctionalTests.StorageRecovery.KeySpacePartitionEncodingTests",
60405+
"reportClassName": "KeyLoad.FunctionalTests.StorageRecovery.KeySpacePartitionEncodingTests",
60406+
"methodName": "ScopeAndSuffixShareOneComponentLimitAndPreserveOriginalRefusals",
60407+
"instanceName": "ScopeAndSuffixShareOneComponentLimitAndPreserveOriginalRefusals",
60408+
"sourcePath": "tests/KeyLoad.FunctionalTests/StorageRecovery/Cases/KeyCodec/KeySpacePartitionEncodingTests.cs",
60409+
"sourceSha256": "0b4f2b2215bcf1999dfe279b44a7698977c8f9c6021c61b2a0c49a1292b0a02d",
60410+
"lineNumber": 28,
60411+
"endLineNumber": 42,
60412+
"classification": "required-non-contributor",
60413+
"exclusionReason": null,
60414+
"requirements": [
60415+
"REQ-KEYCODEC-001",
60416+
"REQ-KEYCODEC-003"
60417+
],
60418+
"acceptance": [
60419+
"AC-KEYCODEC-001",
60420+
"AC-KEYCODEC-003"
60421+
],
60422+
"nonContributorReason": "required-control",
60423+
"reviewBasis": "Direct partition-key byte, scope/suffix component-limit and malformed-input control; no database operation is invoked. Required ordinary control, not operation contributor."
60424+
},
6038160425
{
6038260426
"className": "KeyLoad.FunctionalTests.StorageRecovery.NativeMvccStorageTests",
6038360427
"reportClassName": "KeyLoad.FunctionalTests.StorageRecovery.NativeMvccStorageTests",

‎src/KeyLoad.Abstractions/InternalSerialization/Serialization/OrleansSerialization.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static class OrleansSerialization
1414
{
1515
private static readonly ServiceProvider Services = CreateServices();
1616
internal static Serializer Serializer { get; } = Services.GetRequiredService<Serializer>();
17+
private static Serializer<RuntimePayload> PayloadSerializer { get; } = Services.GetRequiredService<Serializer<RuntimePayload>>();
1718
private static DeepCopier Copier { get; } = Services.GetRequiredService<DeepCopier>();
1819

1920
/// <summary>Encodes one nonnull owned value through the generated Orleans codec.</summary>
@@ -85,7 +86,7 @@ private static RuntimePayload ReadGeneratedPayload(ReadOnlySpan<byte> value, Ser
8586
try
8687
{
8788
var reader = Reader.Create(value, session);
88-
var payload = Serializer.GetSerializer<RuntimePayload>().Deserialize(ref reader);
89+
var payload = PayloadSerializer.Deserialize(ref reader);
8990
if (payload is null || reader.Remaining != EmptyRemainingBytes)
9091
{
9192
throw Errors.Fail(ErrorCode.Corruption, PayloadVersion.InvalidPayload);

‎src/KeyLoad.Abstractions/Storage/Serialization/KeyCodec.cs‎

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,7 @@ public static class KeyCodec
1717
/// <returns>The encoded key bytes.</returns>
1818
/// <exception cref="KeyLoadException">A component cannot be encoded or the component count exceeds the limit.</exception>
1919
public static byte[] Encode(params ReadOnlySpan<KeyComponent> components)
20-
{
21-
if (components.Length > MaximumComponents)
22-
{
23-
throw Errors.Fail(ErrorCode.ResourceExhausted, TooManyComponentsMessage);
24-
}
25-
26-
Span<byte> initial = stackalloc byte[InitialKeyBytes];
27-
var writer = new KeyBufferWriter(initial);
28-
try
29-
{
30-
writer.Write(Version);
31-
foreach (ref readonly var component in components)
32-
{
33-
KeyCodecWritePrimitives.Write(ref writer, in component);
34-
}
35-
36-
return writer.ToArray();
37-
}
38-
finally
39-
{
40-
writer.Dispose();
41-
}
42-
}
20+
=> Encode(components, []);
4321

4422
/// <summary>Encodes boxed components into a version 1 sortable binary key; the bytes equal the typed overload's.</summary>
4523
/// <param name="components">The key components, in their comparison order.</param>
@@ -49,7 +27,13 @@ public static byte[] Encode(params ReadOnlySpan<KeyComponent> components)
4927
public static byte[] Encode(object?[] components)
5028
{
5129
ArgumentNullException.ThrowIfNull(components);
52-
if (components.Length > MaximumComponents)
30+
return Encode([], components);
31+
}
32+
33+
/// <summary>Writes a typed scope and dynamic components directly into the same canonical key buffer.</summary>
34+
internal static byte[] Encode(ReadOnlySpan<KeyComponent> prefix, ReadOnlySpan<object?> suffix)
35+
{
36+
if (prefix.Length > MaximumComponents || suffix.Length > MaximumComponents - prefix.Length)
5337
{
5438
throw Errors.Fail(ErrorCode.ResourceExhausted, TooManyComponentsMessage);
5539
}
@@ -59,7 +43,11 @@ public static byte[] Encode(object?[] components)
5943
try
6044
{
6145
writer.Write(Version);
62-
foreach (var component in components)
46+
foreach (ref readonly var component in prefix)
47+
{
48+
KeyCodecWritePrimitives.Write(ref writer, in component);
49+
}
50+
foreach (var component in suffix)
6351
{
6452
var typed = KeyComponent.From(component);
6553
KeyCodecWritePrimitives.Write(ref writer, in typed);

‎src/KeyLoad.Core/KeySpace.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static byte[] Partition(string space, PartitionRef partition, params obje
4141
ArgumentNullException.ThrowIfNull(partition);
4242
ArgumentNullException.ThrowIfNull(suffix);
4343
return KeyCodec.Encode([space, partition.TenantId, partition.DatabaseId,
44-
partition.TransactionDomainId, partition.PartitionKey, .. suffix]);
44+
partition.TransactionDomainId, partition.PartitionKey], suffix);
4545
}
4646

4747
/// <summary>Creates an owned resource-catalog key within a tenant and database.</summary>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using KeyLoad.Storage;
2+
3+
namespace KeyLoad.FunctionalTests.StorageRecovery;
4+
5+
internal sealed class KeySpacePartitionEncodingTests
6+
{
7+
private const string Space = "document";
8+
private const string PartitionKey = "Case";
9+
private const int MaximumComponents = 256;
10+
private const int ScopeComponents = 5;
11+
private const string LiteralKey = "0150646F63756D656E7400005074656E616E740000506461746162617365000050646F6D61696E000050436173650000506974656D73000050494400FFC3A90000";
12+
private static readonly PartitionRef Partition = new("tenant", "database", "domain", PartitionKey);
13+
14+
[Test]
15+
public async Task PartitionScopeKeepsLiteralBytesCaseEscapesAndEveryDynamicComponent()
16+
{
17+
await Assert.That(KeySpace.Partition(Space, Partition, "items", "ID\0é")
18+
.SequenceEqual(Convert.FromHexString(LiteralKey))).IsTrue();
19+
object?[] suffix = ["items", long.MinValue, 1.25m, double.MaxValue, DateTimeOffset.MaxValue,
20+
Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"), true, null, MissingValue.Instance, new byte[] { 0, 255 }];
21+
object?[] complete = [Space, "tenant", "database", "domain", PartitionKey, .. suffix];
22+
var actual = KeySpace.Partition(Space, Partition, suffix);
23+
await Assert.That(actual.SequenceEqual(KeyCodec.Encode(complete))).IsTrue();
24+
KeyCodecValidation.Validate(actual);
25+
await Assert.That(KeyCodec.Decode(actual).Length).IsEqualTo(ScopeComponents + suffix.Length);
26+
}
27+
28+
[Test]
29+
public async Task ScopeAndSuffixShareOneComponentLimitAndPreserveOriginalRefusals()
30+
{
31+
object?[] suffix = [.. Enumerable.Repeat<object?>(MissingValue.Instance, MaximumComponents - ScopeComponents)];
32+
var maximum = KeySpace.Partition(Space, Partition, suffix);
33+
await Assert.That(KeyCodec.Decode(maximum).Length).IsEqualTo(MaximumComponents);
34+
var excessive = Assert.ThrowsExactly<KeyLoadException>(() => KeySpace.Partition(Space, Partition, [.. suffix, null]));
35+
await Assert.That(excessive.Code).IsEqualTo(ErrorCode.ResourceExhausted);
36+
var unsupported = Assert.ThrowsExactly<KeyLoadException>(() => KeySpace.Partition(Space, Partition, new object()));
37+
await Assert.That(unsupported.Code).IsEqualTo(ErrorCode.UnsupportedCapability);
38+
_ = Assert.ThrowsExactly<ArgumentNullException>(() => KeySpace.Partition(Space, null!, suffix));
39+
_ = Assert.ThrowsExactly<ArgumentNullException>(() => KeySpace.Partition(Space, Partition, (object?[])null!));
40+
await Assert.That(KeySpace.Partition(Space, Partition, "items", "ID\0é")
41+
.SequenceEqual(Convert.FromHexString(LiteralKey))).IsTrue();
42+
}
43+
}

0 commit comments

Comments
 (0)