-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
Incorrect results returned from query under heavy load
One of our services was recently subject to a DDoS attack. The attack created elevated access to a single Azure Sql table. During the attack, reads from the same table threw expected timeout errors like
System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
and
Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
However, we occasionally saw the following error:
System.InvalidOperationException: The underlying reader doesn't have as many fields as expected.
at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.BufferedDataRecord.InitializeFields()
at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.BufferedDataRecord.InitializeAsync(DbDataReader reader, IReadOnlyList`1 columns, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.InitializeAsync(IReadOnlyList`1 columns, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func`4 operation, Func`4 verifySucceeded, TState state, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func`4 operation, Func`4 verifySucceeded, TState state, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
In addition to these errors we noticed that successful queries against the aforementioned table would return incorrect data. The code in question looks something like this:
var customer = await dbContext.Customers.SingleOrDefaultAsync(c => c.Id == request.CustomerId, cancellationToken);
After some investigation we added the following snippet just after the query:
if (request.CustomerId != customer.Id)
{
//log and throw exception
}
which resulted in many exceptions and logs indicating that the customer data returned from the query was different from the requested customer data.
We were also able to reproduce this problem in our test environment.
Steps to reproduce
- Create a target azure sql table
- Expose GET and POST endpoints through web api controller
- Subject to system to high load
- Used k6 to execute POST requests
- Table writes
- Simulate normal usage
- Used k6 to execute GET requests
- Table reads
- Wait for timeout and underlying reader exceptions
Note: we did decrease the maximum connection pool size to more easily reproduce the problem in the test environment.
Details
Microsoft.EntityFrameworkCore v3.1.12
Microsoft.EntityFrameworkCore.SqlServer v3.1.12
NETCoreApp v3.1.10