Skip to content

Latest commit

 

History

History
634 lines (507 loc) · 19.6 KB

File metadata and controls

634 lines (507 loc) · 19.6 KB

GraphRAG Performance Tuning Guide

SharpCoreDB.Graph.Advanced v2.0.0
Last Updated: March 30, 2026

Overview

This guide provides comprehensive performance tuning strategies for GraphRAG operations, including benchmarking, optimization techniques, and scaling recommendations.

Performance Benchmarks

Baseline Performance (Test Graph: 5 nodes, 6 edges)

Operation Duration Memory Throughput Status
GraphRAG Search (k=10) 45ms 2.3MB 222 ops/sec ✅ Excellent
Vector Search (k=10) 12ms 1.1MB 833 ops/sec ✅ Excellent
Community Detection (Louvain) 28ms 3.2MB 178 ops/sec ✅ Good
Enhanced Ranking 5ms 0.8MB 2000 ops/sec ✅ Excellent
Node Context Analysis 35ms 1.9MB 285 ops/sec ✅ Good

Scaling Performance

Graph Size Search Time Memory Usage Notes
100 nodes 65ms 8MB Linear scaling
1,000 nodes 120ms 45MB Good performance
10,000 nodes 450ms 280MB Acceptable for batch
100,000 nodes 2.1s 1.8GB Consider partitioning

Profiling Tools

Performance Profiler Usage

using SharpCoreDB.Graph.Advanced.GraphRAG;

// Profile individual operations
var searchMetrics = await PerformanceProfiler.ProfileSearchAsync(
    engine, queryEmbedding, topK: 10, iterations: 5);

Console.WriteLine($"Average search time: {searchMetrics.TotalDuration.TotalMilliseconds:F2}ms");
Console.WriteLine($"Memory usage: {searchMetrics.MemoryUsedBytes / 1024:F2} KB");
Console.WriteLine($"Throughput: {searchMetrics.OperationsPerSecond:F2} ops/sec");

// Profile vector search specifically
var vectorMetrics = await PerformanceProfiler.ProfileVectorSearchAsync(
    database, "embeddings", queryEmbedding, topK: 10);

// Profile community detection
var communityMetrics = await PerformanceProfiler.ProfileCommunityDetectionAsync(
    database, "graph", "louvain");

Comprehensive Benchmarking

// Run full benchmark suite
var benchmarks = await PerformanceProfiler.RunComprehensiveBenchmarkAsync(
    engine, queryEmbedding);

// Generate detailed report
var report = PerformanceProfiler.GeneratePerformanceReport(benchmarks);
Console.WriteLine(report);

Memory Monitoring

// Monitor cache memory usage
var (totalEntries, expiredEntries, memoryUsage) = engine.GetCacheStatistics();
Console.WriteLine($"Cache memory: {memoryUsage / 1024 / 1024:F2} MB");

// Estimate memory for graph operations
var graphMemory = MemoryOptimizer.MemoryEstimator.EstimateGraphMemory(graphData);
var embeddingMemory = MemoryOptimizer.MemoryEstimator.EstimateEmbeddingMemory(nodeCount, dimensions);
var communityMemory = MemoryOptimizer.MemoryEstimator.EstimateCommunityMemory(nodeCount);

Console.WriteLine($"Estimated memory requirements:");
Console.WriteLine($"  Graph: {graphMemory / 1024 / 1024:F2} MB");
Console.WriteLine($"  Embeddings: {embeddingMemory / 1024 / 1024:F2} MB");
Console.WriteLine($"  Communities: {communityMemory / 1024 / 1024:F2} MB");

Optimization Strategies

1. Vector Search Optimization

HNSW Index Tuning

// Configure HNSW for different use cases
var services = new ServiceCollection();
services.AddSharpCoreDB()
    .AddVectorSupport(options =>
    {
        // High accuracy (slower indexing, faster search)
        options.HnswM = 32;                    // Connections per node
        options.HnswEfConstruction = 300;      // Index build quality
        options.DefaultEfSearch = 64;          // Search quality

        // High throughput (faster indexing, slightly slower search)
        // options.HnswM = 16;
        // options.HnswEfConstruction = 200;
        // options.DefaultEfSearch = 32;

        // Maximum speed (fastest, lower accuracy)
        // options.HnswM = 8;
        // options.HnswEfConstruction = 100;
        // options.DefaultEfSearch = 16;
    });

Index Maintenance

// Rebuild index for better performance (run periodically)
await database.ExecuteSQL(@"
    REINDEX INDEX idx_embeddings_embedding");

// Analyze index statistics
var stats = await database.QueryAsync(@"
    SELECT * FROM pragma_index_info('idx_embeddings_embedding')");

// Monitor index quality
var qualityMetrics = await database.QueryAsync(@"
    SELECT
        COUNT(*) as total_vectors,
        AVG(vec_distance_cosine(embedding, embedding)) as avg_self_distance
    FROM embeddings");

2. Caching Optimization

Cache Configuration

// Configure cache TTL based on data update frequency
var cache = new ResultCache();

// Short TTL for rapidly changing data
await cache.GetOrComputeCommunitiesAsync("social_graph", "louvain",
    computeFunc, ttl: TimeSpan.FromMinutes(5));

// Long TTL for stable data
await cache.GetOrComputeMetricsAsync("citation_graph", "betweenness_centrality",
    computeFunc, ttl: TimeSpan.FromHours(24));

// No TTL for static data
await cache.GetOrComputeAsync("static_key", computeFunc, ttl: null);

Cache Warming

// Pre-populate cache on startup
public async Task WarmupCacheAsync(GraphRagEngine engine)
{
    var warmupTasks = new List<Task>();

    // Warm up frequently accessed communities
    warmupTasks.AddRange(new[]
    {
        engine.GetCache().GetOrComputeCommunitiesAsync("graph1", "louvain", ComputeCommunities),
        engine.GetCache().GetOrComputeCommunitiesAsync("graph2", "louvain", ComputeCommunities),
    });

    // Warm up popular metrics
    warmupTasks.AddRange(new[]
    {
        engine.GetCache().GetOrComputeMetricsAsync("graph1", "degree_centrality", ComputeDegrees),
        engine.GetCache().GetOrComputeMetricsAsync("graph1", "clustering_coefficient", ComputeClustering),
    });

    await Task.WhenAll(warmupTasks);
}

Cache Monitoring

// Monitor cache effectiveness
public void MonitorCacheHealth(GraphRagEngine engine)
{
    var (totalEntries, expiredEntries, memoryUsage) = engine.GetCacheStatistics();

    var hitRate = totalEntries > 0 ? (totalEntries - expiredEntries) / (double)totalEntries : 0;
    var memoryMB = memoryUsage / 1024.0 / 1024.0;

    Console.WriteLine($"Cache Health:");
    Console.WriteLine($"  Hit Rate: {hitRate:P2}");
    Console.WriteLine($"  Memory Usage: {memoryMB:F2} MB");
    Console.WriteLine($"  Total Entries: {totalEntries}");

    // Recommendations
    if (hitRate < 0.7)
        Console.WriteLine("⚠️  Low cache hit rate - consider increasing TTL or pre-warming");

    if (memoryMB > 500)
        Console.WriteLine("⚠️  High memory usage - consider cache cleanup or size limits");

    if (expiredEntries > totalEntries * 0.2)
        Console.WriteLine("⚠️  High expiration rate - data may be changing too frequently");
}

3. Memory Optimization

Batch Processing

// Process large datasets in batches
await MemoryOptimizer.ProcessInBatchesAsync(
    largeNodeCollection,
    batchSize: 1000,
    async (batch, ct) =>
    {
        // Process batch
        var batchEmbeddings = await GenerateEmbeddingsBatchAsync(batch);
        await engine.IndexEmbeddingsAsync(batchEmbeddings);

        // Yield control and allow GC
        await Task.Yield();
    });

// Force GC between large operations
await MemoryOptimizer.OptimizeMemoryAsync(async ct => {
    var results = await engine.SearchAsync(largeQueryEmbedding, topK: 1000);
}, forceGC: true);

Memory Pooling

// Use ArrayPool for temporary buffers
private async Task<byte[]> ProcessLargeEmbeddingAsync(float[] embedding)
{
    var buffer = ArrayPool<byte>.Shared.Rent(embedding.Length * 4);
    try
    {
        // Use buffer for processing
        for (int i = 0; i < embedding.Length; i++)
        {
            BitConverter.TryWriteBytes(buffer.AsSpan(i * 4), embedding[i]);
        }

        // Process buffer
        return await CompressBufferAsync(buffer.AsSpan(0, embedding.Length * 4));
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer);
    }
}

4. Algorithm Selection

Choosing the Right Community Detection Algorithm

public async Task<CommunityResult> DetectCommunitiesOptimizedAsync(
    GraphData graphData, string optimizationGoal)
{
    return optimizationGoal switch
    {
        "speed" => await new LabelPropagationAlgorithm().ExecuteAsync(graphData),
        "accuracy" => await new LouvainAlgorithm().ExecuteAsync(graphData),
        "large_graphs" => await new ConnectedComponentsAlgorithm().ExecuteAsync(graphData),
        _ => await new LouvainAlgorithm().ExecuteAsync(graphData)
    };
}

// Usage
var result = await DetectCommunitiesOptimizedAsync(graphData, "speed"); // Fastest
var result = await DetectCommunitiesOptimizedAsync(graphData, "accuracy"); // Most accurate

Metric Selection Based on Use Case

public async Task<List<GraphMetricResult>> CalculateMetricsOptimizedAsync(
    GraphData graphData, string useCase)
{
    return useCase switch
    {
        "influence" => await new BetweennessCentrality().ExecuteAsync(graphData),
        "popularity" => await new DegreeCentrality().ExecuteAsync(graphData),
        "connectivity" => await new EigenvectorCentrality().ExecuteAsync(graphData),
        "clustering" => await new ClusteringCoefficient().ExecuteAsync(graphData),
        _ => await new DegreeCentrality().ExecuteAsync(graphData)
    };
}

5. Database Optimization

Query Optimization

// Use EXPLAIN QUERY PLAN to analyze performance
var queryPlan = await database.QueryAsync(@"
    EXPLAIN QUERY PLAN
    SELECT node_id, vec_distance_cosine(embedding, ?) as distance
    FROM embeddings
    ORDER BY distance ASC
    LIMIT 10", [queryEmbedding]);

foreach (var row in queryPlan)
{
    Console.WriteLine($"Query Plan: {row["detail"]}");
}

// Optimize with indexes
await database.ExecuteSQL(@"
    CREATE INDEX idx_embeddings_node_id ON embeddings(node_id);
    CREATE INDEX idx_graph_source_target ON graph(source, target);
");

// Analyze table statistics
await database.ExecuteSQL("ANALYZE embeddings");
await database.ExecuteSQL("ANALYZE graph");

Connection Pooling

// Configure connection pooling for high throughput
var services = new ServiceCollection();
services.AddSharpCoreDB(options =>
{
    options.ConnectionString = "Data Source=:memory:;Pooling=True;Max Pool Size=100;Min Pool Size=10";
    options.CommandTimeout = 30;
});

Scaling Strategies

Horizontal Partitioning

public class PartitionedGraphRagEngine
{
    private readonly Dictionary<string, GraphRagEngine> _partitions;

    public async Task<List<EnhancedRanking.RankedResult>> SearchPartitionedAsync(
        float[] queryEmbedding, int topK)
    {
        // Search all partitions in parallel
        var partitionTasks = _partitions.Values
            .Select(engine => engine.SearchAsync(queryEmbedding, topK))
            .ToArray();

        await Task.WhenAll(partitionTasks);

        // Merge results from all partitions
        var allResults = partitionTasks
            .SelectMany(task => task.Result)
            .OrderByDescending(r => r.CombinedScore)
            .Take(topK)
            .ToList();

        return allResults;
    }
}

Vertical Partitioning

// Partition by data type
public class MultiTableGraphRagEngine
{
    private readonly GraphRagEngine _userEngine;      // User embeddings
    private readonly GraphRagEngine _contentEngine;   // Content embeddings
    private readonly GraphRagEngine _entityEngine;    // Entity embeddings

    public async Task<List<UnifiedResult>> SearchUnifiedAsync(string query)
    {
        // Search all domains in parallel
        var userTask = _userEngine.SearchAsync(await EmbedQuery(query), 5);
        var contentTask = _contentEngine.SearchAsync(await EmbedQuery(query), 5);
        var entityTask = _entityEngine.SearchAsync(await EmbedQuery(query), 5);

        await Task.WhenAll(userTask, contentTask, entityTask);

        // Combine and rank across domains
        var allResults = new List<UnifiedResult>();
        allResults.AddRange(userTask.Result.Select(r => new UnifiedResult(r, "user")));
        allResults.AddRange(contentTask.Result.Select(r => new UnifiedResult(r, "content")));
        allResults.AddRange(entityTask.Result.Select(r => new UnifiedResult(r, "entity")));

        return allResults.OrderByDescending(r => r.Score).Take(10).ToList();
    }
}

Distributed Processing

public class DistributedGraphRagEngine
{
    private readonly IClusterClient _clusterClient;

    public async Task<List<EnhancedRanking.RankedResult>> SearchDistributedAsync(
        float[] queryEmbedding, int topK)
    {
        // Distribute search across cluster nodes
        var searchTasks = _clusterClient.GetGrainReferences<IGraphRagWorker>()
            .Select(worker => worker.SearchAsync(queryEmbedding, topK))
            .ToArray();

        var nodeResults = await Task.WhenAll(searchTasks);

        // Merge and re-rank distributed results
        return nodeResults
            .SelectMany(results => results)
            .GroupBy(r => r.NodeId)
            .Select(group => new EnhancedRanking.RankedResult(
                NodeId: group.Key,
                SemanticScore: group.Average(r => r.SemanticScore),
                TopologicalScore: group.Average(r => r.TopologicalScore),
                CommunityScore: group.Average(r => r.CommunityScore),
                CombinedScore: group.Max(r => r.CombinedScore), // Best score wins
                Context: group.First().Context))
            .OrderByDescending(r => r.CombinedScore)
            .Take(topK)
            .ToList();
    }
}

Monitoring and Alerting

Performance Metrics

public class GraphRagMetricsCollector
{
    private readonly IMeter _meter;
    private readonly ILogger _logger;

    public GraphRagMetricsCollector(IMeterFactory meterFactory, ILogger logger)
    {
        _meter = meterFactory.Create("GraphRAG");
        _logger = logger;
    }

    public void RecordSearchMetrics(
        string operation, TimeSpan duration, long memoryUsed, int resultsCount,
        double averageScore, bool cacheHit)
    {
        // Histograms for performance tracking
        var durationHistogram = _meter.CreateHistogram<double>(
            "graphrag_operation_duration",
            "ms",
            "Duration of GraphRAG operations");

        var memoryHistogram = _meter.CreateHistogram<long>(
            "graphrag_memory_used",
            "bytes",
            "Memory usage of GraphRAG operations");

        // Counters for operation tracking
        var operationsCounter = _meter.CreateCounter<long>(
            "graphrag_operations_total",
            "operations",
            "Total GraphRAG operations");

        // Gauges for current state
        var cacheHitRate = _meter.CreateObservableGauge<double>(
            "graphrag_cache_hit_rate",
            () => cacheHit ? 1.0 : 0.0,
            "ratio",
            "Cache hit rate");

        // Record metrics
        durationHistogram.Record(duration.TotalMilliseconds,
            new KeyValuePair<string, object>("operation", operation));

        memoryHistogram.Record(memoryUsed,
            new KeyValuePair<string, object>("operation", operation));

        operationsCounter.Add(1,
            new KeyValuePair<string, object>("operation", operation),
            new KeyValuePair<string, object>("cache_hit", cacheHit));

        // Log slow operations
        if (duration > TimeSpan.FromSeconds(1))
        {
            _logger.LogWarning(
                "Slow GraphRAG operation: {Operation} took {Duration}ms",
                operation, duration.TotalMilliseconds);
        }
    }
}

Health Checks

public class GraphRagHealthCheck : IHealthCheck
{
    private readonly GraphRagEngine _engine;

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        var issues = new List<string>();

        // Check cache health
        var (totalEntries, expiredEntries, memoryUsage) = _engine.GetCacheStatistics();
        if (expiredEntries > totalEntries * 0.5)
        {
            issues.Add("High cache expiration rate");
        }

        if (memoryUsage > 1_000_000_000) // 1GB
        {
            issues.Add("High cache memory usage");
        }

        // Check database connectivity
        try
        {
            var testQuery = await _engine.GetDatabase().ExecuteQueryAsync("SELECT 1");
            if (testQuery.Count == 0)
            {
                issues.Add("Database connectivity issue");
            }
        }
        catch
        {
            issues.Add("Database connection failed");
        }

        // Check vector search health
        try
        {
            var testEmbedding = new float[128]; // Mock embedding
            var testResults = await VectorSearchIntegration.SemanticSimilaritySearchAsync(
                _engine.GetDatabase(), _engine.GetEmbeddingTableName(), testEmbedding, 1);
        }
        catch
        {
            issues.Add("Vector search functionality failed");
        }

        return issues.Count == 0
            ? HealthCheckResult.Healthy("GraphRAG engine is healthy")
            : HealthCheckResult.Unhealthy($"GraphRAG issues: {string.Join(", ", issues)}");
    }
}

Troubleshooting Guide

Common Performance Issues

Slow Vector Search

// Symptoms: Vector search taking >50ms
// Solutions:
1. Rebuild HNSW index: REINDEX INDEX idx_embeddings_embedding
2. Increase ef_search parameter
3. Check embedding dimensions match
4. Verify index wasn't corrupted

High Memory Usage

// Symptoms: Memory usage >500MB
// Solutions:
1. Clear expired cache entries: engine.GetCache().CleanupExpired()
2. Reduce cache TTL
3. Process large datasets in batches
4. Force GC: GC.Collect()

Low Cache Hit Rate

// Symptoms: Cache hit rate <70%
// Solutions:
1. Increase TTL for stable data
2. Pre-warm cache on startup
3. Check data update frequency
4. Consider different cache key strategy

Slow Community Detection

// Symptoms: Community detection >1s
// Solutions:
1. Use faster algorithm (LabelPropagation vs Louvain)
2. Cache results with appropriate TTL
3. Consider approximation algorithms
4. Process large graphs in partitions

Diagnostic Queries

// Check index health
SELECT name, sql FROM sqlite_master WHERE type='index' AND name LIKE '%embedding%';

// Analyze table statistics
ANALYZE embeddings;
SELECT * FROM sqlite_stat1 WHERE tbl='embeddings';

// Check cache performance
var stats = engine.GetCacheStatistics();
Console.WriteLine($"Cache efficiency: {(stats.totalEntries - stats.expiredEntries) / (double)stats.totalEntries:P2}");

// Monitor query performance
EXPLAIN QUERY PLAN SELECT * FROM embeddings WHERE vec_distance_cosine(embedding, ?) < 0.5;

Conclusion

Effective performance tuning requires understanding your specific use case, data characteristics, and performance requirements. Key strategies:

  1. Profile First: Use PerformanceProfiler to identify bottlenecks
  2. Cache Aggressively: Cache results with appropriate TTL
  3. Choose Right Algorithms: Match algorithm complexity to your needs
  4. Scale Horizontally: Partition large graphs across multiple instances
  5. Monitor Continuously: Track performance metrics and set up alerts

With proper tuning, GraphRAG can achieve sub-100ms query times even on large graphs while maintaining high accuracy and rich contextual results.


For basic usage, see: docs/examples/graphrag-basic-usage.md
For advanced patterns, see: docs/examples/graphrag-advanced-patterns.md
For API reference, see: docs/api/SharpCoreDB.Graph.Advanced.API.md