Skip to content

Troubleshooting

This guide covers common issues and solutions when working with Foundatio.Repositories.

Connection Issues

Cannot Connect to Elasticsearch

Symptoms:

  • No connection could be made
  • Connection refused
  • Timeout errors

Solutions:

  1. Verify Elasticsearch is running:
bash
curl http://localhost:9200
  1. Check connection string:
csharp
protected override IConnectionPool CreateConnectionPool()
{
    // Ensure URL is correct
    return new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
}
  1. Check firewall/network:
bash
# Test connectivity
telnet localhost 9200
  1. Enable debug logging:
csharp
protected override void ConfigureSettings(ConnectionSettings settings)
{
    settings.DisableDirectStreaming();
    settings.PrettyJson();
    settings.EnableDebugMode();
}

Authentication Errors

Symptoms:

  • 401 Unauthorized
  • 403 Forbidden

Solutions:

csharp
protected override void ConfigureSettings(ConnectionSettings settings)
{
    // Basic authentication
    settings.BasicAuthentication("username", "password");
    
    // Or API key
    settings.ApiKeyAuthentication("api-key-id", "api-key");
}

Index Issues

Index Not Found

Symptoms:

  • index_not_found_exception
  • no such index

Solutions:

  1. Configure indexes on startup:
csharp
await configuration.ConfigureIndexesAsync();
  1. Check index name:
csharp
// Versioned indexes have version suffix
// "employees" -> "employees-v1"
var indexName = index.VersionedName;
  1. Verify index exists:
bash
curl http://localhost:9200/_cat/indices

Mapping Conflicts

Symptoms:

  • mapper_parsing_exception
  • failed to parse field

Solutions:

  1. Increment index version:
csharp
// Change version to trigger reindex
public EmployeeIndex(...) : base(configuration, "employees", version: 2) { }
  1. Delete and recreate index (development only):
csharp
await configuration.DeleteIndexesAsync();
await configuration.ConfigureIndexesAsync();
  1. Check field types match:
csharp
// Ensure mapping matches data types
.Number(f => f.Name(e => e.Age).Type(NumberType.Integer))

Query Issues

No Results Returned

Symptoms:

  • Empty results when data exists
  • Total: 0

Solutions:

  1. Check soft delete mode:
csharp
// Include soft-deleted documents
var results = await repository.FindAsync(query, o => o.IncludeSoftDeletes());
  1. Use immediate consistency:
csharp
// Wait for index refresh
await repository.AddAsync(entity, o => o.ImmediateConsistency());
var results = await repository.FindAsync(query);
  1. Verify filter syntax:
csharp
// Check filter expression
var results = await repository.FindAsync(q => q.FieldEquals(e => e.Status, "active"));

// Debug: Log the query
var results = await repository.FindAsync(query, o => o.QueryLogLevel(LogLevel.Debug));
  1. Check field names:
csharp
// Use exact field names from mapping
// "name" vs "name.keyword" for exact match

Query Syntax Errors

Symptoms:

  • query_parsing_exception
  • failed to parse query

Solutions:

  1. Escape special characters:
csharp
// Escape: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
var escaped = Regex.Escape(userInput);
  1. Use strongly-typed queries:
csharp
// Instead of filter expression
var results = await repository.FindAsync(q => q
    .FieldEquals(e => e.Status, "active")
    .FieldCondition(e => e.Name, ComparisonOperator.Contains, "John"));

For numeric comparisons, use FilterExpression with Lucene syntax:

csharp
var results = await repository.FindAsync(q => q
    .FieldEquals(e => e.Status, "active")
    .FilterExpression("age:[25 TO *]"));

Cache Issues

Stale Data

Symptoms:

  • Old data returned after updates
  • Changes not reflected

Solutions:

  1. Manually invalidate cache:
csharp
await repository.InvalidateCacheAsync(document);
await repository.InvalidateCacheAsync("custom-cache-key");
  1. Disable cache for debugging:
csharp
var results = await repository.FindAsync(query, o => o.Cache(false));
  1. Check cache invalidation gaps:
csharp
// PatchAllAsync doesn't invalidate custom keys
await repository.PatchAllAsync(query, patch);
await repository.InvalidateCacheAsync("affected-key");

Cache Key Conflicts

Symptoms:

  • Wrong data returned
  • Data from different queries mixed

Solutions:

csharp
// Use unique, consistent cache keys
var key = $"employee:email:{email.ToLowerInvariant()}";
var results = await repository.FindOneAsync(query, o => o.Cache(key));

Version Conflicts

VersionConflictDocumentException

Symptoms:

  • version_conflict_engine_exception
  • VersionConflictDocumentException

Solutions:

  1. Implement retry logic:
csharp
int retries = 3;
while (retries > 0)
{
    try
    {
        var doc = await repository.GetByIdAsync(id);
        doc.Name = "Updated";
        await repository.SaveAsync(doc);
        break;
    }
    catch (VersionConflictDocumentException)
    {
        retries--;
        if (retries == 0) throw;
    }
}
  1. Skip version check (if appropriate):
csharp
await repository.SaveAsync(document, o => o.SkipVersionCheck());
  1. Use atomic operations:
csharp
// Atomic increment avoids conflicts
await repository.PatchAsync(id, new ScriptPatch("ctx._source.counter++"));

Performance Issues

Slow Queries

Symptoms:

  • High query latency
  • Timeouts

Solutions:

  1. Add appropriate indexes:
csharp
// Ensure fields are properly mapped
.Keyword(f => f.Name(e => e.Status))  // For filtering
.Text(f => f.Name(e => e.Name).AddKeywordAndSortFields())  // For search + sort
  1. Limit result size:
csharp
var results = await repository.FindAsync(query, o => o.PageLimit(100));
  1. Use field selection:
csharp
var results = await repository.FindAsync(query, o => o
    .Include(e => e.Id)
    .Include(e => e.Name));
  1. Use search-after for deep pagination:
csharp
var results = await repository.FindAsync(query, o => o.SearchAfterPaging());

Memory Issues

Symptoms:

  • OutOfMemoryException
  • High memory usage

Solutions:

  1. Use batch processing:
csharp
await repository.BatchProcessAsync(query, async batch =>
{
    // Process in batches
    return true;
}, o => o.PageLimit(500));
  1. Use snapshot paging for large exports:
csharp
var results = await repository.FindAsync(query, o => o.SnapshotPaging());

Notification Issues

EntityChanged Not Received

Symptoms:

  • Subscribers not receiving notifications
  • Message bus appears silent

Solutions:

  1. Verify message bus is configured:
csharp
public MyElasticConfiguration(IMessageBus messageBus, ...) 
    : base(messageBus: messageBus, ...) { }
  1. Check notifications are enabled:
csharp
// Repository level
NotificationsEnabled = true;

// Operation level
await repository.SaveAsync(entity, o => o.Notifications(true));
  1. Verify subscription:
csharp
await messageBus.SubscribeAsync<EntityChanged>(async (msg, ct) =>
{
    Console.WriteLine($"Received: {msg.Type} {msg.ChangeType}");
});

Soft Delete Not Sending Removed

Symptoms:

  • Soft delete sends ChangeType.Saved instead of Removed

Solutions:

csharp
// Enable originals tracking
public class EmployeeRepository : ElasticRepositoryBase<Employee>
{
    public EmployeeRepository(EmployeeIndex index) : base(index)
    {
        OriginalsEnabled = true;  // Required for soft delete detection
    }
}

Debugging Tips

Enable Detailed Logging

csharp
// In configuration
protected override void ConfigureSettings(ConnectionSettings settings)
{
    settings.DisableDirectStreaming();
    settings.PrettyJson();
    settings.OnRequestCompleted(details =>
    {
        _logger.LogDebug("Request: {Method} {Uri}", 
            details.HttpMethod, details.Uri);
        if (details.RequestBodyInBytes != null)
            _logger.LogDebug("Body: {Body}", 
                Encoding.UTF8.GetString(details.RequestBodyInBytes));
    });
}

// Per query
var results = await repository.FindAsync(query, o => o.QueryLogLevel(LogLevel.Debug));

Inspect Elasticsearch Directly

bash
# Check cluster health
curl http://localhost:9200/_cluster/health

# List indexes
curl http://localhost:9200/_cat/indices

# View mapping
curl http://localhost:9200/employees/_mapping

# Search directly
curl -X POST http://localhost:9200/employees/_search -H 'Content-Type: application/json' -d '
{
  "query": { "match_all": {} }
}'

Check Index Statistics

bash
curl http://localhost:9200/employees/_stats

Common Error Messages

ErrorCauseSolution
index_not_found_exceptionIndex doesn't existRun ConfigureIndexesAsync()
mapper_parsing_exceptionType mismatchCheck field types in mapping
version_conflict_engine_exceptionConcurrent modificationImplement retry or skip version check
search_phase_execution_exceptionQuery errorCheck query syntax
circuit_breaking_exceptionMemory limitReduce batch size
cluster_block_exceptionCluster read-onlyCheck disk space

Repository Exception Types

Foundatio.Repositories uses typed exceptions so callers can handle specific failure modes. The exceptions listed below inherit from DocumentException.

ExceptionWhen ThrownRetryable?
DuplicateDocumentExceptionAddAsync when a document with the same ID already existsNo — remove the existing document or use SaveAsync
VersionConflictDocumentExceptionSaveAsync / PatchAsync when the document version doesn't match (HTTP 409)Yes — re-fetch the document and retry
DocumentNotFoundExceptionPatchAsync when the target document doesn't exist (HTTP 404)No — verify the document ID
DocumentValidationExceptionAny write operation when document validation failsNo — fix the document data
DocumentExceptionOther Elasticsearch errors not covered aboveDepends on the underlying cause

Partial Failures on Bulk Operations

When AddAsync or SaveAsync is called with multiple documents, some may succeed and others may fail. The repository:

  1. Processes all successes first — fires events, populates cache, sends notifications.
  2. Leaves failed documents' cache unchanged — failed writes don't mutate Elasticsearch, so existing cache entries remain valid. The writer that caused a conflict handles its own cache update via message bus notifications.
  3. Throws a typed exceptionDuplicateDocumentException for add, VersionConflictDocumentException for save.
csharp
try
{
    await repository.AddAsync(documents);
}
catch (DuplicateDocumentException ex)
{
    // Successful documents were fully processed.
    // Duplicate documents: existing cache entries preserved (nothing was mutated).
    _logger.LogWarning(ex, "Partial failure: some documents already existed");
}
catch (VersionConflictDocumentException ex)
{
    _logger.LogWarning(ex, "Partial failure: some documents had version conflicts");
}

Transient Error Retries

The repository automatically retries transient Elasticsearch errors:

  • HTTP 429 (Too Many Requests) — retried with exponential backoff, up to 3 retries (4 total attempts)
  • HTTP 503 (Service Unavailable) — retried with exponential backoff, up to 3 retries (4 total attempts)
  • HTTP 409 (Version Conflict) — not retried; the caller must handle conflict resolution
  • DuplicateDocumentExceptionnot retried by the resilience policy

Aggregation Warnings

doc_count_error_upper_bound Warning

Symptoms:

  • Warning-level log message about doc_count_error_upper_bound in terms aggregation results

Explanation:

When running terms aggregations across multiple shards, Elasticsearch returns an approximate count. The doc_count_error_upper_bound field indicates the maximum potential error in document counts for each term bucket. A non-zero value means shard-level approximations may have affected the results.

Solutions:

  1. Increase shard_size if accuracy matters for your use case — this makes Elasticsearch consider more terms per shard before combining results
  2. Use a single shard for small indexes where exact counts are important
  3. Ignore the warning if approximate counts are acceptable for your use case (this is common for analytics and dashboards)

Getting Help

  1. Check logs - Enable debug logging
  2. Inspect Elasticsearch - Use Kibana or curl
  3. Review documentation - Check specific feature guides
  4. GitHub Issues - Search existing issues or create new one
  5. Discord - Join the Foundatio Discord community

Next Steps

Released under the Apache 2.0 License.