up
Some checks failed
Build Test Deploy / build-test (push) Has been cancelled
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
root
2025-10-10 06:53:40 +00:00
parent 3aed135fb5
commit df5984d07e
1081 changed files with 97764 additions and 61389 deletions

View File

@@ -1,38 +1,38 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
[BsonIgnoreExtraElements]
internal sealed class AliasDocument
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("advisoryKey")]
public string AdvisoryKey { get; set; } = string.Empty;
[BsonElement("scheme")]
public string Scheme { get; set; } = string.Empty;
[BsonElement("value")]
public string Value { get; set; } = string.Empty;
[BsonElement("updatedAt")]
public DateTime UpdatedAt { get; set; }
}
internal static class AliasDocumentExtensions
{
public static AliasRecord ToRecord(this AliasDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var updatedAt = DateTime.SpecifyKind(document.UpdatedAt, DateTimeKind.Utc);
return new AliasRecord(
document.AdvisoryKey,
document.Scheme,
document.Value,
new DateTimeOffset(updatedAt));
}
}
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
[BsonIgnoreExtraElements]
internal sealed class AliasDocument
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("advisoryKey")]
public string AdvisoryKey { get; set; } = string.Empty;
[BsonElement("scheme")]
public string Scheme { get; set; } = string.Empty;
[BsonElement("value")]
public string Value { get; set; } = string.Empty;
[BsonElement("updatedAt")]
public DateTime UpdatedAt { get; set; }
}
internal static class AliasDocumentExtensions
{
public static AliasRecord ToRecord(this AliasDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var updatedAt = DateTime.SpecifyKind(document.UpdatedAt, DateTimeKind.Utc);
return new AliasRecord(
document.AdvisoryKey,
document.Scheme,
document.Value,
new DateTimeOffset(updatedAt));
}
}

View File

@@ -1,157 +1,157 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
public sealed class AliasStore : IAliasStore
{
private readonly IMongoCollection<AliasDocument> _collection;
private readonly ILogger<AliasStore> _logger;
public AliasStore(IMongoDatabase database, ILogger<AliasStore> logger)
{
_collection = (database ?? throw new ArgumentNullException(nameof(database)))
.GetCollection<AliasDocument>(MongoStorageDefaults.Collections.Alias);
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<AliasUpsertResult> ReplaceAsync(
string advisoryKey,
IEnumerable<AliasEntry> aliases,
DateTimeOffset updatedAt,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryKey);
var aliasList = Normalize(aliases).ToArray();
var deleteFilter = Builders<AliasDocument>.Filter.Eq(x => x.AdvisoryKey, advisoryKey);
await _collection.DeleteManyAsync(deleteFilter, cancellationToken).ConfigureAwait(false);
if (aliasList.Length > 0)
{
var documents = new List<AliasDocument>(aliasList.Length);
var updatedAtUtc = updatedAt.ToUniversalTime().UtcDateTime;
foreach (var alias in aliasList)
{
documents.Add(new AliasDocument
{
Id = ObjectId.GenerateNewId(),
AdvisoryKey = advisoryKey,
Scheme = alias.Scheme,
Value = alias.Value,
UpdatedAt = updatedAtUtc,
});
}
if (documents.Count > 0)
{
await _collection.InsertManyAsync(
documents,
new InsertManyOptions { IsOrdered = false },
cancellationToken).ConfigureAwait(false);
}
}
if (aliasList.Length == 0)
{
return new AliasUpsertResult(advisoryKey, Array.Empty<AliasCollision>());
}
var collisions = new List<AliasCollision>();
foreach (var alias in aliasList)
{
var filter = Builders<AliasDocument>.Filter.Eq(x => x.Scheme, alias.Scheme)
& Builders<AliasDocument>.Filter.Eq(x => x.Value, alias.Value);
using var cursor = await _collection.FindAsync(filter, cancellationToken: cancellationToken).ConfigureAwait(false);
var advisoryKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
while (await cursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
{
foreach (var document in cursor.Current)
{
advisoryKeys.Add(document.AdvisoryKey);
}
}
if (advisoryKeys.Count <= 1)
{
continue;
}
var collision = new AliasCollision(alias.Scheme, alias.Value, advisoryKeys.ToArray());
collisions.Add(collision);
AliasStoreMetrics.RecordCollision(alias.Scheme, advisoryKeys.Count);
_logger.LogWarning(
"Alias collision detected for {Scheme}:{Value}; advisories: {Advisories}",
alias.Scheme,
alias.Value,
string.Join(", ", advisoryKeys));
}
return new AliasUpsertResult(advisoryKey, collisions);
}
public async Task<IReadOnlyList<AliasRecord>> GetByAliasAsync(string scheme, string value, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(scheme);
ArgumentException.ThrowIfNullOrWhiteSpace(value);
var normalizedScheme = NormalizeScheme(scheme);
var normalizedValue = value.Trim();
var filter = Builders<AliasDocument>.Filter.Eq(x => x.Scheme, normalizedScheme)
& Builders<AliasDocument>.Filter.Eq(x => x.Value, normalizedValue);
var documents = await _collection.Find(filter).ToListAsync(cancellationToken).ConfigureAwait(false);
return documents.Select(static d => d.ToRecord()).ToArray();
}
public async Task<IReadOnlyList<AliasRecord>> GetByAdvisoryAsync(string advisoryKey, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryKey);
var filter = Builders<AliasDocument>.Filter.Eq(x => x.AdvisoryKey, advisoryKey);
var documents = await _collection.Find(filter).ToListAsync(cancellationToken).ConfigureAwait(false);
return documents.Select(static d => d.ToRecord()).ToArray();
}
private static IEnumerable<AliasEntry> Normalize(IEnumerable<AliasEntry> aliases)
{
if (aliases is null)
{
yield break;
}
var seen = new HashSet<string>(StringComparer.Ordinal);
foreach (var alias in aliases)
{
if (alias is null)
{
continue;
}
var scheme = NormalizeScheme(alias.Scheme);
var value = alias.Value?.Trim();
if (string.IsNullOrEmpty(value))
{
continue;
}
var key = $"{scheme}\u0001{value}";
if (!seen.Add(key))
{
continue;
}
yield return new AliasEntry(scheme, value);
}
}
private static string NormalizeScheme(string scheme)
{
return string.IsNullOrWhiteSpace(scheme)
? AliasStoreConstants.UnscopedScheme
: scheme.Trim().ToUpperInvariant();
}
}
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
public sealed class AliasStore : IAliasStore
{
private readonly IMongoCollection<AliasDocument> _collection;
private readonly ILogger<AliasStore> _logger;
public AliasStore(IMongoDatabase database, ILogger<AliasStore> logger)
{
_collection = (database ?? throw new ArgumentNullException(nameof(database)))
.GetCollection<AliasDocument>(MongoStorageDefaults.Collections.Alias);
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<AliasUpsertResult> ReplaceAsync(
string advisoryKey,
IEnumerable<AliasEntry> aliases,
DateTimeOffset updatedAt,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryKey);
var aliasList = Normalize(aliases).ToArray();
var deleteFilter = Builders<AliasDocument>.Filter.Eq(x => x.AdvisoryKey, advisoryKey);
await _collection.DeleteManyAsync(deleteFilter, cancellationToken).ConfigureAwait(false);
if (aliasList.Length > 0)
{
var documents = new List<AliasDocument>(aliasList.Length);
var updatedAtUtc = updatedAt.ToUniversalTime().UtcDateTime;
foreach (var alias in aliasList)
{
documents.Add(new AliasDocument
{
Id = ObjectId.GenerateNewId(),
AdvisoryKey = advisoryKey,
Scheme = alias.Scheme,
Value = alias.Value,
UpdatedAt = updatedAtUtc,
});
}
if (documents.Count > 0)
{
await _collection.InsertManyAsync(
documents,
new InsertManyOptions { IsOrdered = false },
cancellationToken).ConfigureAwait(false);
}
}
if (aliasList.Length == 0)
{
return new AliasUpsertResult(advisoryKey, Array.Empty<AliasCollision>());
}
var collisions = new List<AliasCollision>();
foreach (var alias in aliasList)
{
var filter = Builders<AliasDocument>.Filter.Eq(x => x.Scheme, alias.Scheme)
& Builders<AliasDocument>.Filter.Eq(x => x.Value, alias.Value);
using var cursor = await _collection.FindAsync(filter, cancellationToken: cancellationToken).ConfigureAwait(false);
var advisoryKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
while (await cursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
{
foreach (var document in cursor.Current)
{
advisoryKeys.Add(document.AdvisoryKey);
}
}
if (advisoryKeys.Count <= 1)
{
continue;
}
var collision = new AliasCollision(alias.Scheme, alias.Value, advisoryKeys.ToArray());
collisions.Add(collision);
AliasStoreMetrics.RecordCollision(alias.Scheme, advisoryKeys.Count);
_logger.LogWarning(
"Alias collision detected for {Scheme}:{Value}; advisories: {Advisories}",
alias.Scheme,
alias.Value,
string.Join(", ", advisoryKeys));
}
return new AliasUpsertResult(advisoryKey, collisions);
}
public async Task<IReadOnlyList<AliasRecord>> GetByAliasAsync(string scheme, string value, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(scheme);
ArgumentException.ThrowIfNullOrWhiteSpace(value);
var normalizedScheme = NormalizeScheme(scheme);
var normalizedValue = value.Trim();
var filter = Builders<AliasDocument>.Filter.Eq(x => x.Scheme, normalizedScheme)
& Builders<AliasDocument>.Filter.Eq(x => x.Value, normalizedValue);
var documents = await _collection.Find(filter).ToListAsync(cancellationToken).ConfigureAwait(false);
return documents.Select(static d => d.ToRecord()).ToArray();
}
public async Task<IReadOnlyList<AliasRecord>> GetByAdvisoryAsync(string advisoryKey, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryKey);
var filter = Builders<AliasDocument>.Filter.Eq(x => x.AdvisoryKey, advisoryKey);
var documents = await _collection.Find(filter).ToListAsync(cancellationToken).ConfigureAwait(false);
return documents.Select(static d => d.ToRecord()).ToArray();
}
private static IEnumerable<AliasEntry> Normalize(IEnumerable<AliasEntry> aliases)
{
if (aliases is null)
{
yield break;
}
var seen = new HashSet<string>(StringComparer.Ordinal);
foreach (var alias in aliases)
{
if (alias is null)
{
continue;
}
var scheme = NormalizeScheme(alias.Scheme);
var value = alias.Value?.Trim();
if (string.IsNullOrEmpty(value))
{
continue;
}
var key = $"{scheme}\u0001{value}";
if (!seen.Add(key))
{
continue;
}
yield return new AliasEntry(scheme, value);
}
}
private static string NormalizeScheme(string scheme)
{
return string.IsNullOrWhiteSpace(scheme)
? AliasStoreConstants.UnscopedScheme
: scheme.Trim().ToUpperInvariant();
}
}

View File

@@ -1,7 +1,7 @@
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
public static class AliasStoreConstants
{
public const string PrimaryScheme = "PRIMARY";
public const string UnscopedScheme = "UNSCOPED";
}
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
public static class AliasStoreConstants
{
public const string PrimaryScheme = "PRIMARY";
public const string UnscopedScheme = "UNSCOPED";
}

View File

@@ -1,22 +1,22 @@
using System.Collections.Generic;
using System.Diagnostics.Metrics;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
internal static class AliasStoreMetrics
{
private static readonly Meter Meter = new("StellaOps.Feedser.Merge");
internal static readonly Counter<long> AliasCollisionCounter = Meter.CreateCounter<long>(
"feedser.merge.alias_conflict",
unit: "count",
description: "Number of alias collisions detected when the same alias maps to multiple advisories.");
public static void RecordCollision(string scheme, int advisoryCount)
{
AliasCollisionCounter.Add(
1,
new KeyValuePair<string, object?>("scheme", scheme),
new KeyValuePair<string, object?>("advisory_count", advisoryCount));
}
}
using System.Collections.Generic;
using System.Diagnostics.Metrics;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
internal static class AliasStoreMetrics
{
private static readonly Meter Meter = new("StellaOps.Feedser.Merge");
internal static readonly Counter<long> AliasCollisionCounter = Meter.CreateCounter<long>(
"feedser.merge.alias_conflict",
unit: "count",
description: "Number of alias collisions detected when the same alias maps to multiple advisories.");
public static void RecordCollision(string scheme, int advisoryCount)
{
AliasCollisionCounter.Add(
1,
new KeyValuePair<string, object?>("scheme", scheme),
new KeyValuePair<string, object?>("advisory_count", advisoryCount));
}
}

View File

@@ -1,27 +1,27 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
public interface IAliasStore
{
Task<AliasUpsertResult> ReplaceAsync(
string advisoryKey,
IEnumerable<AliasEntry> aliases,
DateTimeOffset updatedAt,
CancellationToken cancellationToken);
Task<IReadOnlyList<AliasRecord>> GetByAliasAsync(string scheme, string value, CancellationToken cancellationToken);
Task<IReadOnlyList<AliasRecord>> GetByAdvisoryAsync(string advisoryKey, CancellationToken cancellationToken);
}
public sealed record AliasEntry(string Scheme, string Value);
public sealed record AliasRecord(string AdvisoryKey, string Scheme, string Value, DateTimeOffset UpdatedAt);
public sealed record AliasCollision(string Scheme, string Value, IReadOnlyList<string> AdvisoryKeys);
public sealed record AliasUpsertResult(string AdvisoryKey, IReadOnlyList<AliasCollision> Collisions);
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Feedser.Storage.Mongo.Aliases;
public interface IAliasStore
{
Task<AliasUpsertResult> ReplaceAsync(
string advisoryKey,
IEnumerable<AliasEntry> aliases,
DateTimeOffset updatedAt,
CancellationToken cancellationToken);
Task<IReadOnlyList<AliasRecord>> GetByAliasAsync(string scheme, string value, CancellationToken cancellationToken);
Task<IReadOnlyList<AliasRecord>> GetByAdvisoryAsync(string advisoryKey, CancellationToken cancellationToken);
}
public sealed record AliasEntry(string Scheme, string Value);
public sealed record AliasRecord(string AdvisoryKey, string Scheme, string Value, DateTimeOffset UpdatedAt);
public sealed record AliasCollision(string Scheme, string Value, IReadOnlyList<string> AdvisoryKeys);
public sealed record AliasUpsertResult(string AdvisoryKey, IReadOnlyList<AliasCollision> Collisions);