-
Notifications
You must be signed in to change notification settings - Fork 5.1k
[Storage] [Webjobs] Implementing lightweight detection for new write operations #53081
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a lightweight detection mechanism for new write operations in Azure Storage blob monitoring, optimizing the zero-to-one scaling logic. Instead of reading and parsing complete log files, it now checks only for the presence of write operations in metadata within a specified time window.
Key changes:
- Added a new
HasBlobWritesAsyncmethod that checks for write operations without downloading log content - Simplified the zero-to-one scaling monitor to use the lightweight detection
- Updated logging messages and removed debugging-related code
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| BlobLogListener.cs | Added HasBlobWritesAsync method for lightweight write detection and made constructor public |
| BlobScalerMonitorProvider.cs | Replaced detailed write enumeration with simple boolean check and updated logging |
| BlobLogListenerTests.cs | Added comprehensive tests for the new HasBlobWritesAsync method with various scenarios |
...age/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/tests/Listeners/BlobLogListenerTests.cs
Show resolved
Hide resolved
| private static readonly System.Reflection.PropertyInfo NameProp = BlobItemType.GetProperty(nameof(BlobItem.Name))!; | ||
| private static readonly System.Reflection.PropertyInfo MetadataProp = BlobItemType.GetProperty(nameof(BlobItem.Metadata))!; | ||
|
|
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The null-forgiving operator (!) is used without null checks. If these properties don't exist, this will throw at runtime. Add proper null validation or use more defensive reflection patterns.
| private static readonly System.Reflection.PropertyInfo NameProp = BlobItemType.GetProperty(nameof(BlobItem.Name))!; | |
| private static readonly System.Reflection.PropertyInfo MetadataProp = BlobItemType.GetProperty(nameof(BlobItem.Metadata))!; | |
| private static readonly System.Reflection.PropertyInfo NameProp = BlobItemType.GetProperty(nameof(BlobItem.Name)); | |
| private static readonly System.Reflection.PropertyInfo MetadataProp = BlobItemType.GetProperty(nameof(BlobItem.Metadata)); | |
| static BlobItemFactory() | |
| { | |
| if (NameProp == null) | |
| { | |
| throw new InvalidOperationException($"Property '{nameof(BlobItem.Name)}' not found on type '{BlobItemType.FullName}'."); | |
| } | |
| if (MetadataProp == null) | |
| { | |
| throw new InvalidOperationException($"Property '{nameof(BlobItem.Metadata)}' not found on type '{BlobItemType.FullName}'."); | |
| } | |
| } |
| DateTime hourCursor = DateTime.UtcNow; | ||
| BlobContainerClient containerClient = _blobClient.GetBlobContainerClient(LogContainer); | ||
|
|
||
| int processedCount = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this isn't really used and can be removed
sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/src/Listeners/BlobLogListener.cs
Show resolved
Hide resolved
|
|
||
| await foreach (var page in containerClient | ||
| .GetBlobsAsync(traits: BlobTraits.Metadata, prefix: prefix, states: BlobStates.None, cancellationToken: cancellationToken) | ||
| .AsPages(pageSizeHint: 200) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you choose this 200 - is it a default we use elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| processedCount++; | ||
|
|
||
| // Examine metadata only if present. | ||
| if (blob.Metadata is not null && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could make this a little more succinct:
if (blob.Metadata?.TryGetValue(LogType, out string logType) &&
logType?.Contains("write", StringComparison.OrdinalIgnoreCase))
{
return true;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not compile. Copilot:
The shown replacement is problematic for multiple reasons:
- Null-conditional invocation result type: blob.Metadata?.TryGetValue(...) returns bool? (nullable bool). You can’t use && with a bool? left operand. Won’t compile.
- API availability: string.Contains(string, StringComparison) is NOT available in .NET Standard 2.0. This project multi-targets .NET 8 and .NET Standard 2.0, so that overload breaks the netstandard2.0 build. The original IndexOf(..., StringComparison.OrdinalIgnoreCase) works on both.
- Semantics / safety: The original guarded against null or empty logType explicitly. Your version relies on logType?.Contains, which is fine, but coupled with point (1) & (2) it fails anyway.
- Consistency: Elsewhere in this file (GetLogsWithPrefixAsync) you have logType.Contains("write") without a StringComparison, which is culture-sensitive. The original pattern (IndexOf + StringComparison.OrdinalIgnoreCase) is the safer, consistent approach.
Recommended fixed version (compiles for both targets and remains clear):
if (blob.Metadata is not null &&
blob.Metadata.TryGetValue(LogType, out var logType) &&
!string.IsNullOrEmpty(logType) &&
logType.IndexOf("write", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
|
|
||
| public static BlobItem Create(string name, IDictionary<string, string> metadata) | ||
| { | ||
| var ctor = BlobItemType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, binder: null, types: Type.EmptyTypes, modifiers: null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if @amnguye knows a better way to mock this stuff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BlobItem does not have public ctor. It seems there is no other way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use the BlobsModelFactory we have some constructors of the BlobItem you can mock.
For example:
BlobItem blobItem = BlobsModelFactory.BlobItem(
name: result.Item1,
properties: BlobsModelFactory.BlobItemProperties(
accessTierInferred: true,
contentLength: 1024,
blobType: BlobType.Block,
eTag: new ETag("etag")));
blobHierarchyItems.Add(new BlobHierarchyItem(default, blobItem));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - yes that's what I recalled in the past for other SDK types - there was a model factory pattern that could be used
...age/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/tests/Listeners/BlobLogListenerTests.cs
Show resolved
Hide resolved
|
Closed in favor: #53263 |

From #52977 @alrod :
"This PR introduces a streamlined approach to zero-to-one scaling logic within the Azure SDK. The current implementation reads and parses all classic monitoring logs in the $log container to extract modified blob paths. However, for scale-out from zero to one, this level of detail is unnecessary.
The updated logic simplifies the process by checking only for the presence of write operations within the last two hours—without downloading or parsing the full log content. This optimization reduces memory consumption and improves performance, especially in environments with high write volumes."