Skip to content

Blind Index Recompute

When you rotate an HMAC key for a blind index, all existing companion column values become stale — queries against the new key won't match rows written with the old key. The recompute services in Tayra.EFCore and Tayra.Marten rebuild these companion columns in batches.

When to Recompute

Recomputation is required after:

  • HMAC key rotation — the existing companion values were computed with the old key
  • Transform changes — you added, removed, or reordered transforms on a [BlindIndex] attribute
  • Initial adoption — you added blind indexes to an entity type that already has data

EF Core

Service Registration

csharp
services.AddTayra(opts => opts.LicenseKey = licenseKey)
    .UsePostgreSqlKeyStore(connectionString);

services.AddTayraEFCore();
// IBlindIndexRecomputeService is registered automatically

Running a Recompute

csharp
var recompute = sp.GetRequiredService<IBlindIndexRecomputeService>();

var result = await recompute.RecomputeAsync<Customer>(
    dbContext,
    batchSize: 500);

Console.WriteLine($"Scanned: {result.TotalScanned}");
Console.WriteLine($"Recomputed: {result.Recomputed}");
Console.WriteLine($"Skipped: {result.Skipped}");
Console.WriteLine($"Errors: {result.Errors}");
Console.WriteLine($"Duration: {result.Duration}");

The service:

  1. Loads entities in batches of batchSize (default 100)
  2. For each entity, decrypts PII fields (via EF Core interceptors)
  3. Calls IBlindIndexer.ComputeIndexesAsync with the current HMAC key
  4. Saves the updated companion columns

Example: Full Rotation Workflow

csharp
// 1. Generate new HMAC key (replaces old key in key store)
await blindIndexKeyProvider.RotateKeyAsync("bi:customer_email");

// 2. Recompute all Customer blind indexes with the new key
var result = await recompute.RecomputeAsync<Customer>(dbContext, batchSize: 500);

// 3. Verify
Console.WriteLine($"Recomputed {result.Recomputed} of {result.TotalScanned} customers");

Marten

Service Registration

csharp
services.AddMarten(opts =>
{
    opts.Connection(connectionString);
    opts.UseTayra();
});
// ITayraMartenBlindIndexRecomputeService is registered automatically

Running a Recompute

csharp
var recompute = sp.GetRequiredService<ITayraMartenBlindIndexRecomputeService>();

var result = await recompute.RecomputeAsync<Customer>(
    documentStore,
    batchSize: 500,
    tenantId: null);   // or specify tenant for multi-tenant stores

Console.WriteLine($"Recomputed: {result.Recomputed}/{result.TotalScanned}");

The Marten service follows the same pattern as EF Core but operates on Marten document sessions.

Result

Both services return a BlindIndexRecomputeResult:

PropertyTypeDescription
TotalScannedintTotal entities/documents loaded
RecomputedintEntities with updated blind indexes
SkippedintEntities with no blind indexes or null values
ErrorsintEntities that failed during recomputation
DurationTimeSpanTotal elapsed time

Operational Guidance

Batch Size

Choose a batch size based on your data volume and database capacity:

Data volumeRecommended batch size
< 10,000 rows500–1,000
10,000–100,000 rows100–500
> 100,000 rows50–100

Larger batches reduce round-trips but increase memory usage and transaction duration.

Downtime Strategy

Between rotating the key and completing the recompute, blind index queries will not find rows with stale companion values. Two strategies:

Maintenance Window (simplest)

  1. Take the application offline or disable search
  2. Rotate the HMAC key
  3. Run the recompute
  4. Bring the application back online

Dual-Read (zero downtime)

  1. Store the old HMAC key under a backup name (e.g., bi:customer_email:v1)
  2. Rotate the HMAC key to a new value
  3. Start the recompute in the background
  4. During the recompute, query with both old and new keys and merge results
  5. After the recompute completes, remove the dual-read path and delete the old key

CLI Integration

The dotnet tayra CLI does not yet include a built-in recompute command. Run recomputes programmatically via the services above, or wrap them in a hosted service:

csharp
public class BlindIndexRecomputeJob : BackgroundService
{
    private readonly IBlindIndexRecomputeService _recompute;
    private readonly MyDbContext _db;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        var result = await _recompute.RecomputeAsync<Customer>(_db, 200, ct);
        // Log result, send notification, etc.
    }
}

Monitoring

The tayra_blind_index_recomputed_total OpenTelemetry metric tracks recompute operations. The pre-built Grafana dashboard includes a panel for this metric.

See Also