Repository navigation
[Fusion] Make FusionArchive generation deterministic - #8994
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8994 +/- ##
============================
============================
☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR makes FusionArchive generation deterministic by using a fixed timestamp for ZIP entries, ensuring that archives created with identical inputs produce identical binary outputs. Previously, archives had different hashes due to timestamps reflecting the current time during creation.
Key Changes
- Introduced deterministic file ordering in CommitAsync by sorting files by path using StringComparer.Ordinal
- Refactored ZIP entry creation into a new CreateEntryFromFileAsync method that sets a fixed timestamp (January 1, 2000) for all entries
- Removed redundant conditional compilation blocks and consolidated the implementation
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| foreach (var file in _files.Values.OrderBy(f => f.Path, StringComparer.Ordinal)) | ||
| { | ||
| #if NET10_0_OR_GREATER | ||
| switch (file.State) | ||
| { | ||
| case FileState.Created: | ||
| await _archive.CreateEntryFromFileAsync( | ||
| file.TempPath, | ||
| file.Path, | ||
| cancellationToken: cancellationToken); | ||
| await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken); | ||
| break; | ||
|
|
||
| case FileState.Replaced: | ||
| _archive.GetEntry(file.Path)?.Delete(); | ||
| await _archive.CreateEntryFromFileAsync( | ||
| file.TempPath, | ||
| file.Path, | ||
| cancellationToken); | ||
| await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken); | ||
| break; | ||
|
|
||
| case FileState.Deleted: | ||
| _archive.GetEntry(file.Path)?.Delete(); | ||
| break; | ||
| } | ||
| #else | ||
| switch (file.State) | ||
| { | ||
| case FileState.Created: | ||
| _archive.CreateEntryFromFile(file.TempPath, file.Path); | ||
| break; | ||
|
|
||
| case FileState.Replaced: | ||
| _archive.GetEntry(file.Path)?.Delete(); | ||
| _archive.CreateEntryFromFile(file.TempPath, file.Path); | ||
| break; | ||
| file.MarkRead(); | ||
| } | ||
| } |
There was a problem hiding this comment.
The deterministic archive generation feature (fixed timestamp and ordered file processing) lacks test coverage. Consider adding a test that creates two archives with identical content and verifies they produce byte-for-byte identical output. This would ensure the determinism is maintained across future changes.
🚀 Fusion Gateway Performance ResultsSimple Composite QueryConstant Load (50 VUs)
📊 Response Time Metrics
Ramping Load (0→50→500→50 VUs)
📊 Response Time Metrics
Executed Query fragment User on User {
id
username
name
}
fragment Review on Review {
id
body
}
fragment Product on Product {
inStock
name
price
shippingEstimate
upc
weight
}
query TestQuery {
topProducts(first: 5) {
...Product
reviews {
...Review
author {
...User
}
}
}
}Deep Recursion QueryConstant Load (50 VUs)
📊 Response Time Metrics
Ramping Load (0→50→500→50 VUs)
📊 Response Time Metrics
Executed Query fragment User on User {
id
username
name
}
fragment Review on Review {
id
body
}
fragment Product on Product {
inStock
name
price
shippingEstimate
upc
weight
}
query TestQuery {
users {
...User
reviews {
...Review
product {
...Product
reviews {
...Review
author {
...User
reviews {
...Review
product {
...Product
}
}
}
}
}
}
}
topProducts(first: 5) {
...Product
reviews {
...Review
author {
...User
reviews {
...Review
product {
...Product
}
}
}
}
}
}Variable Batching ThroughputConstant Load (50 VUs)
📊 Response Time Metrics
Ramping Load (0→50→500→50 VUs)
📊 Response Time Metrics
Executed Query query TestQuery_8f7a46ce_2(
$__fusion_1_upc: ID!
$__fusion_2_price: Long!
$__fusion_2_weight: Long!
) {
productByUpc(upc: $__fusion_1_upc) {
inStock
shippingEstimate(weight: $__fusion_2_weight, price: $__fusion_2_price)
}
}Variables (5 sets batched in single request) [
{ "__fusion_1_upc": "1", "__fusion_2_price": 899, "__fusion_2_weight": 100 },
{ "__fusion_1_upc": "2", "__fusion_2_price": 1299, "__fusion_2_weight": 1000 },
{ "__fusion_1_upc": "3", "__fusion_2_price": 15, "__fusion_2_weight": 20 },
{ "__fusion_1_upc": "4", "__fusion_2_price": 499, "__fusion_2_weight": 100 },
{ "__fusion_1_upc": "5", "__fusion_2_price": 1299, "__fusion_2_weight": 1000 }
]No baseline data available for comparison. Run 20132916209 • Commit 65249e2 • Thu, 11 Dec 2025 12:39:29 GMT |
FusionArchives previously always had a different hash even for the same source schema and settings inputs, since the "last write time" of each entry was set to the current timestamp.
This PR back-ports a fix I did for the
OpenApiCollectionArchivethat introduces a fixed timestamp, resulting in deterministic archive generation.