abp icon indicating copy to clipboard operation
abp copied to clipboard

Timeout (task cancelled) when using "DeleteManyAsync" or "DeleteAsync" with predicate on a couple of thousand records

Open alexandis opened this issue 3 years ago • 1 comments

ABP 5.1.2

I was trying to delete data in chunks to avoid slow performance, but still getting timeout ("Task was cancelled"):

    public virtual async Task<bool> DeleteAsync(PlateListKeyDto key, CancellationToken cancellationToken = default)
    {
        const int chunkSize = 10;
        var item = await GetInnerAsync(key.Id);
        try
        {
            //var dbContext = await _plateListAuthorizationRepository.GetDbContextAsync();
            //dbContext.ChangeTracker.AutoDetectChangesEnabled = false;
            var plateListAuthorizationDbSet = await _plateListAuthorizationRepository.GetDbSetAsync();
            var plateListAuthorizations = plateListAuthorizationDbSet.Where(x => x.PlateListId == item.Id).ToList();
            for (var skip = 0; skip < plateListAuthorizations.Count; skip += chunkSize)
            {
                await _plateListAuthorizationRepository.DeleteManyAsync(plateListAuthorizations.Skip(skip).Take(chunkSize), true, cancellationToken); // EXCEPTION HAPPENS HERE
            }
            await _plateListRepository.DeleteAsync(item, true, cancellationToken);
        }
        catch(DbUpdateException dbExtension) when (dbExtension.IsChildRecordFoundException())
        {
            throw new BusinessException(DomainErrorCodes.ChildRecordFound, _stringLocalizer.GetString("ChildRecordFound"));
        }
        return true;            
    }

This is the exception stack:

at Oracle.EntityFrameworkCore.Update.Internal.OracleModificationCommandBatch.<ConsumeAsync>d__27.MoveNext() at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.<ExecuteAsync>d__29.MoveNext() at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.<ExecuteAsync>d__29.MoveNext() at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext() at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext() at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext() at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__103.MoveNext() at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__107.MoveNext() at Oracle.EntityFrameworkCore.Storage.Internal.OracleExecutionStrategy.<ExecuteAsync>d__82.MoveNext() at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__60.MoveNext() at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__60.MoveNext() at Volo.Abp.EntityFrameworkCore.AbpDbContext1.<SaveChangesAsync>d__41.MoveNext() at Volo.Abp.Domain.Repositories.EntityFrameworkCore.EfCoreRepository2.<DeleteManyAsync>d__25.MoveNext() at Castle.DynamicProxy.AsyncInterceptorBase.<ProceedAsynchronous>d__13.MoveNext() at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapter.<ProceedAsync>d__7.MoveNext() at Volo.Abp.Uow.UnitOfWorkInterceptor.<InterceptAsync>d__2.MoveNext() at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter1.<InterceptAsync>d__2.MoveNext() at AbxEps.MasterData.PlateLists.PlateListsAppService.<DeleteAsync>d__11.MoveNext() in C:\CT\AbxEps.MasterData\aspnet-core\src\AbxEps.MasterData.Application\PlateLists\PlateListAppService.cs:line 97

alexandis avatar Dec 13 '22 14:12 alexandis

When I try to use DeleteAsync - it is the same. And - BTW - why it is so weirdly translated to SQL?

    public virtual async Task<bool> DeleteAsync(PlateListKeyDto key, CancellationToken cancellationToken = default)
    {
        const int chunkSize = 10;
        var item = await GetInnerAsync(key.Id);
        try
        {
            var dbContext = await _plateListAuthorizationRepository.GetDbContextAsync();
            dbContext.ChangeTracker.AutoDetectChangesEnabled = false;
            var plateListAuthorizationDbSet = await _plateListAuthorizationRepository.GetDbSetAsync();
            var plateListAuthorizationIds = plateListAuthorizationDbSet.Where(x => x.TenantId == item.TenantId && x.PlateListId == item.Id).OrderBy(x => x.Id).Select(x => x.Id);
            for (var skip = 0; skip < plateListAuthorizationIds.Count(); skip += chunkSize)
            {
                var chunky = plateListAuthorizationIds.Skip(skip).Take(chunkSize).ToList();
                await _plateListAuthorizationRepository.DeleteAsync(x => chunky.Contains(x.Id), true, cancellationToken);
            }
            await _plateListRepository.DeleteAsync(item, true, cancellationToken);
        }
        catch(DbUpdateException dbExtension) when (dbExtension.IsChildRecordFoundException())
        {
            throw new BusinessException(DomainErrorCodes.ChildRecordFound, _stringLocalizer.GetString("ChildRecordFound"));
        }
        return true;            
    }

DeleteAsync(x => chunky.Contains(x.Id), ...) always translated into bunch of DELETEs per entry, instead of the single DELETE FROM MD_LA_AUTH m WHERE m.N_AUTH IN (102761, 102762, 102763, 102764, 102765, 102766, 102767, 102768, 102769, 102770):

  SELECT m.C_TENANT, m.N_AUTH, m.D_CREATE, m.S_AUTH, m.C_AUTH_TYPE, m.D_INS, m.C_USR_INS, m.D_END, m.D_UPD, m.C_USR_UPD, m.N_LOCATION, m.S_PLATE, m.C_COUNTRY, m.N_PLTLST, m.S_PLATE_NORM, m.ROWID, m.D_START, m.N_VEH_CAT, m.N_VEH_GRP
  FROM MD_LA_AUTH m
  WHERE m.N_AUTH IN (102761, 102762, 102763, 102764, 102765, 102766, 102767, 102768, 102769, 102770)

info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (118ms) [Parameters=[:p0='102761', :p1='3', :p2='102762', :p3='3', :p4='102763', :p5='3', :p6='102764', :p7='3', :p8='102765', :p9='3', :p10='102766', :p11='3', :p12='102767', :p13='3', :p14='102768', :p15='3', :p16='102769', :p17='3', :p18='102770', :p19='3', :cur1=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur2=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur3=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur4=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur5=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur6=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur7=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur8=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur9=NULL (Nullable = false) (Direction = Output) (DbType = Object), :cur10=NULL (Nullable = false) (Direction = Output) (DbType = Object)], CommandType='Text', CommandTimeout='0']

  DECLARE
  v_RowCount INTEGER;
  BEGIN
  DELETE FROM MD_LA_AUTH
  WHERE N_AUTH = :p0 AND C_TENANT = :p1;
  v_RowCount := SQL%ROWCOUNT;
  OPEN :cur1 FOR SELECT v_RowCount FROM DUAL;
  DELETE FROM MD_LA_AUTH
  WHERE N_AUTH = :p2 AND C_TENANT = :p3;
  v_RowCount := SQL%ROWCOUNT;
  OPEN :cur2 FOR SELECT v_RowCount FROM DUAL;
  DELETE FROM MD_LA_AUTH
  WHERE N_AUTH = :p4 AND C_TENANT = :p5;
  v_RowCount := SQL%ROWCOUNT;
  OPEN :cur3 FOR SELECT v_RowCount FROM DUAL;
  DELETE FROM MD_LA_AUTH
  WHERE N_AUTH = :p6 AND C_TENANT = :p7;
  v_RowCount := SQL%ROWCOUNT;
  OPEN :cur4 FOR SELECT v_RowCount FROM DUAL;
  DELETE FROM MD_LA_AUTH
  WHERE N_AUTH = :p8 AND C_TENANT = :p9;
  v_RowCount := SQL%ROWCOUNT;
  OPEN :cur5 FOR SELECT v_RowCount FROM DUAL;

alexandis avatar Dec 13 '22 16:12 alexandis

Please try to start a new transaction in the delete method.

maliming avatar Dec 21 '22 01:12 maliming

I've resolved it by using raw SQL "delete from [table] where [predicate]". It's weird that your DeleteAsync is not transformed into this kind of SQL, but instead is transformed into one SELECT and sequential DELETEs - each for one SELECT result...

alexandis avatar Dec 21 '22 05:12 alexandis