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
services.AddTayra(opts => opts.LicenseKey = licenseKey)
.UsePostgreSqlKeyStore(connectionString);
services.AddTayraEFCore();
// IBlindIndexRecomputeService is registered automaticallyRunning a Recompute
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:
- Loads entities in batches of
batchSize(default 100) - For each entity, decrypts PII fields (via EF Core interceptors)
- Calls
IBlindIndexer.ComputeIndexesAsyncwith the current HMAC key - Saves the updated companion columns
Example: Full Rotation Workflow
// 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
services.AddMarten(opts =>
{
opts.Connection(connectionString);
opts.UseTayra();
});
// ITayraMartenBlindIndexRecomputeService is registered automaticallyRunning a Recompute
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:
| Property | Type | Description |
|---|---|---|
TotalScanned | int | Total entities/documents loaded |
Recomputed | int | Entities with updated blind indexes |
Skipped | int | Entities with no blind indexes or null values |
Errors | int | Entities that failed during recomputation |
Duration | TimeSpan | Total elapsed time |
Operational Guidance
Batch Size
Choose a batch size based on your data volume and database capacity:
| Data volume | Recommended batch size |
|---|---|
| < 10,000 rows | 500–1,000 |
| 10,000–100,000 rows | 100–500 |
| > 100,000 rows | 50–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)
- Take the application offline or disable search
- Rotate the HMAC key
- Run the recompute
- Bring the application back online
Dual-Read (zero downtime)
- Store the old HMAC key under a backup name (e.g.,
bi:customer_email:v1) - Rotate the HMAC key to a new value
- Start the recompute in the background
- During the recompute, query with both old and new keys and merge results
- 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:
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
- Blind Index Key Management -- HMAC key storage and rotation
- Blind Index Security -- Threat model
- EF Core Integration -- Entity Framework Core setup
- Marten Integration -- Marten document store setup
- Grafana Dashboards -- Monitoring recompute operations
