dotnet/orleans · error · OrleansException

Failed to read table

Error message

Failed to read table {tableName}: {exc.Message}

What it means

Thrown by the DynamoDB read/scan path when any exception escapes the paged ScanAsync loop while reading a table. The library logs the underlying exception and re-throws it wrapped in a generic OrleansException so callers see a single error type for arbitrary DynamoDB read failures. The inner exception carries the real cause.

Solutions

  1. Inspect the inner Exception (exc.Message is included in the text) to identify throttling, auth, or ResourceNotFound.
  2. If throttling: raise read capacity or switch the table to PAY_PER_REQUEST / on-demand.
  3. If the table is missing, recreate it (or re-run Init so the provider recreates it).
  4. For transient errors, rely on Orleans' built-in retries or add a caller-side retry policy around the read.

Example fix

// The exception wraps the real cause; surface the inner exception for diagnosis
// before
try { await storage.Read...; } catch (OrleansException e) { log.Error(e.Message); }

// after
try { await storage.Read...(); }
catch (OrleansException e) when (e.InnerException is ProvisionedThroughputExceededException)
{
    // raise capacity or switch to on-demand, then retry
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm the table exists and has read capacity headroom before heavy reads
var desc = await DescribeExistingTableAsync(tableName);
if (desc is null) throw new InvalidOperationException($"Table {tableName} does not exist");

Type guard

static bool IsReadableFailure(Exception inner) => inner switch
{
    ProvisionedThroughputExceededException => true,
    ResourceNotFoundException => true,
    AmazonDynamoDBException => true,
    _ => false
};

Try / catch

try { await storage.ReadAllAsync(...); }
catch (OrleansException ox) when (ox.InnerException is ProvisionedThroughputExceededException)
{
    _logger.LogWarning("DynamoDB read throttled; raising read capacity or retrying");
    throw;
}

Prevention

When it happens

Trigger: Produced during a paginated consistent-read Scan used by clustering/persistence/reminders lookups when ScanAsync throws. Triggered by DynamoDB throttling (ProvisionedThroughputExceededException), a missing table (ResourceNotFoundException surfaced as inner), network/timeout errors, auth errors, or item-size/expression errors.

Common situations: Read capacity exceeded on a provisioned table; transient network blips to DynamoDB; expired/invalid credentials surfacing during a read; a table that was deleted while the silo was running; malformed filter expressions.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/edf2245a3e95493a. Report an issue: GitHub.

Appendix: source

Thrown at src/AWS/Shared/Storage/DynamoDBStorage.cs:866

                        }
                    }

                    if (response.LastEvaluatedKey == null || response.LastEvaluatedKey.Count == 0)
                    {
                        break;
                    }
                    else
                    {
                        exclusiveStartKey = response.LastEvaluatedKey;
                    }
                }

                return resultList;
            }
            catch (Exception exc)
            {
                LogWarningFailedToReadTable(_logger, exc, tableName);
                throw new OrleansException($"Failed to read table {tableName}: {exc.Message}", exc);
            }
        }

        /// <summary>
        /// Crete or replace multiple entries in a DynamoDB table (Batch put)
        /// </summary>
        /// <param name="tableName">The name of the table to search for the entry</param>
        /// <param name="toCreate">List of key values for each entry that must be created or replaced in the batch</param>
        /// <returns></returns>
        public Task PutEntriesAsync(string tableName, IReadOnlyCollection<Dictionary<string, AttributeValue>> toCreate)
        {
            LogTracePutEntries(_logger, tableName);

            if (toCreate == null) throw new ArgumentNullException(nameof(toCreate));

            if (toCreate.Count == 0)
                return Task.CompletedTask;

View on GitHub (pinned to fca799fa70)