|
|
@ -16,6 +16,7 @@ using Squidex.Caching; |
|
|
using Squidex.Infrastructure.EventSourcing; |
|
|
using Squidex.Infrastructure.EventSourcing; |
|
|
using Squidex.Infrastructure.States; |
|
|
using Squidex.Infrastructure.States; |
|
|
using Squidex.Infrastructure.Tasks; |
|
|
using Squidex.Infrastructure.Tasks; |
|
|
|
|
|
using Squidex.Log; |
|
|
|
|
|
|
|
|
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
|
|
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
|
|
|
|
|
|
|
|
@ -26,6 +27,7 @@ namespace Squidex.Infrastructure.Commands |
|
|
private readonly ILocalCache localCache; |
|
|
private readonly ILocalCache localCache; |
|
|
private readonly IEventStore eventStore; |
|
|
private readonly IEventStore eventStore; |
|
|
private readonly IServiceProvider serviceProvider; |
|
|
private readonly IServiceProvider serviceProvider; |
|
|
|
|
|
private readonly ISemanticLog log; |
|
|
|
|
|
|
|
|
private static class Factory<T, TState> where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
private static class Factory<T, TState> where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
{ |
|
|
{ |
|
|
@ -40,14 +42,23 @@ namespace Squidex.Infrastructure.Commands |
|
|
public Rebuilder( |
|
|
public Rebuilder( |
|
|
ILocalCache localCache, |
|
|
ILocalCache localCache, |
|
|
IEventStore eventStore, |
|
|
IEventStore eventStore, |
|
|
IServiceProvider serviceProvider) |
|
|
IServiceProvider serviceProvider, |
|
|
|
|
|
ISemanticLog log) |
|
|
{ |
|
|
{ |
|
|
this.eventStore = eventStore; |
|
|
this.eventStore = eventStore; |
|
|
this.serviceProvider = serviceProvider; |
|
|
this.serviceProvider = serviceProvider; |
|
|
|
|
|
this.log = log; |
|
|
this.localCache = localCache; |
|
|
this.localCache = localCache; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public virtual async Task RebuildAsync<T, TState>(string filter, int batchSize, |
|
|
public virtual Task RebuildAsync<T, TState>(string filter, int batchSize, |
|
|
|
|
|
CancellationToken ct = default) |
|
|
|
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
|
|
|
{ |
|
|
|
|
|
return RebuildAsync<T, TState>(filter, batchSize, 0, ct); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public virtual async Task RebuildAsync<T, TState>(string filter, int batchSize, double errorThreshold, |
|
|
CancellationToken ct = default) |
|
|
CancellationToken ct = default) |
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
{ |
|
|
{ |
|
|
@ -55,10 +66,17 @@ namespace Squidex.Infrastructure.Commands |
|
|
|
|
|
|
|
|
var ids = eventStore.QueryAllAsync(filter, ct: ct).Select(x => x.Data.Headers.AggregateId()); |
|
|
var ids = eventStore.QueryAllAsync(filter, ct: ct).Select(x => x.Data.Headers.AggregateId()); |
|
|
|
|
|
|
|
|
await InsertManyAsync<T, TState>(ids, batchSize, ct); |
|
|
await InsertManyAsync<T, TState>(ids, batchSize, errorThreshold, ct); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public virtual Task InsertManyAsync<T, TState>(IEnumerable<DomainId> source, int batchSize, |
|
|
|
|
|
CancellationToken ct = default) |
|
|
|
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
|
|
|
{ |
|
|
|
|
|
return InsertManyAsync<T, TState>(source, batchSize, 0, ct); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public virtual async Task InsertManyAsync<T, TState>(IEnumerable<DomainId> source, int batchSize, |
|
|
public virtual async Task InsertManyAsync<T, TState>(IEnumerable<DomainId> source, int batchSize, double errorThreshold = 0, |
|
|
CancellationToken ct = default) |
|
|
CancellationToken ct = default) |
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
{ |
|
|
{ |
|
|
@ -66,10 +84,10 @@ namespace Squidex.Infrastructure.Commands |
|
|
|
|
|
|
|
|
var ids = source.ToAsyncEnumerable(); |
|
|
var ids = source.ToAsyncEnumerable(); |
|
|
|
|
|
|
|
|
await InsertManyAsync<T, TState>(ids, batchSize, ct); |
|
|
await InsertManyAsync<T, TState>(ids, batchSize, errorThreshold, ct); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private async Task InsertManyAsync<T, TState>(IAsyncEnumerable<DomainId> source, int batchSize, |
|
|
private async Task InsertManyAsync<T, TState>(IAsyncEnumerable<DomainId> source, int batchSize, double errorThreshold, |
|
|
CancellationToken ct = default) |
|
|
CancellationToken ct = default) |
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() |
|
|
{ |
|
|
{ |
|
|
@ -77,6 +95,11 @@ namespace Squidex.Infrastructure.Commands |
|
|
|
|
|
|
|
|
var parallelism = Environment.ProcessorCount; |
|
|
var parallelism = Environment.ProcessorCount; |
|
|
|
|
|
|
|
|
|
|
|
var handledIds = new HashSet<DomainId>(); |
|
|
|
|
|
var handlerErrors = 0; |
|
|
|
|
|
|
|
|
|
|
|
using (localCache.StartContext()) |
|
|
|
|
|
{ |
|
|
var workerBlock = new ActionBlock<DomainId[]>(async ids => |
|
|
var workerBlock = new ActionBlock<DomainId[]>(async ids => |
|
|
{ |
|
|
{ |
|
|
try |
|
|
try |
|
|
@ -99,6 +122,15 @@ namespace Squidex.Infrastructure.Commands |
|
|
{ |
|
|
{ |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
catch (Exception ex) |
|
|
|
|
|
{ |
|
|
|
|
|
log.LogWarning(ex, w => w |
|
|
|
|
|
.WriteProperty("reason", "CorruptData") |
|
|
|
|
|
.WriteProperty("domainObjectId", id.ToString()) |
|
|
|
|
|
.WriteProperty("domainObjectType", typeof(T).Name)); |
|
|
|
|
|
|
|
|
|
|
|
Interlocked.Increment(ref handlerErrors); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -122,10 +154,6 @@ namespace Squidex.Infrastructure.Commands |
|
|
|
|
|
|
|
|
batchBlock.BidirectionalLinkTo(workerBlock); |
|
|
batchBlock.BidirectionalLinkTo(workerBlock); |
|
|
|
|
|
|
|
|
var handledIds = new HashSet<DomainId>(); |
|
|
|
|
|
|
|
|
|
|
|
using (localCache.StartContext()) |
|
|
|
|
|
{ |
|
|
|
|
|
await foreach (var id in source.WithCancellation(ct)) |
|
|
await foreach (var id in source.WithCancellation(ct)) |
|
|
{ |
|
|
{ |
|
|
if (handledIds.Add(id)) |
|
|
if (handledIds.Add(id)) |
|
|
@ -138,11 +166,18 @@ namespace Squidex.Infrastructure.Commands |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
batchBlock.Complete(); |
|
|
batchBlock.Complete(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await workerBlock.Completion; |
|
|
await workerBlock.Completion; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var errorRate = (double)handlerErrors / handledIds.Count; |
|
|
|
|
|
|
|
|
|
|
|
if (errorRate >= errorThreshold) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new InvalidOperationException($"Error rate of {errorRate} is above threshold {errorThreshold}."); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private async Task ClearAsync<TState>() where TState : class, IDomainState<TState>, new() |
|
|
private async Task ClearAsync<TState>() where TState : class, IDomainState<TState>, new() |
|
|
{ |
|
|
{ |
|
|
var store = serviceProvider.GetRequiredService<IStore<TState>>(); |
|
|
var store = serviceProvider.GetRequiredService<IStore<TState>>(); |
|
|
|