Skip to content

yohasacura/dtde

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

DTDE - Distributed Temporal Data Engine

Transparent horizontal sharding and temporal versioning for Entity Framework Core

.NET EF Core License Tests GitHub

πŸ“š Documentation Β· πŸš€ Quick Start Β· πŸ’‘ Samples Β· πŸ“Š Benchmarks


Overview

DTDE is a NuGet package that adds transparent horizontal sharding and optional temporal versioning to Entity Framework Core applications. Write standard LINQ queries β€” DTDE handles data distribution, query routing, and result merging automatically.

// Standard EF Core LINQ - DTDE routes queries transparently
var euCustomers = await db.Customers
    .Where(c => c.Region == "EU")
    .ToListAsync();  // Automatically queries only EU shard

// Point-in-time queries for temporal entities
var ordersLastMonth = await db.ValidAt<Order>(DateTime.Today.AddMonths(-1))
    .Where(o => o.Status == "Completed")
    .ToListAsync();

✨ Key Features

Feature Description
πŸ”€ Transparent Sharding Distribute data across tables or databases invisibly
⏱️ Temporal Versioning Track entity history with point-in-time queries
πŸ”— Cross-Shard Transactions ACID transactions spanning multiple database shards
🎯 Property Agnostic Use ANY property names for sharding keys and temporal boundaries
πŸ“ EF Core Native Works with standard LINQ β€” no special query syntax required
⚑ Multiple Strategies Date-based, hash-based, range-based, or composite sharding
πŸ—„οΈ Hot/Warm/Cold Tiers Support for data tiering across storage tiers
βœ… Fully Tested 400+ unit and integration tests

πŸ“¦ Installation

# All-in-one package (recommended)
dotnet add package Dtde.EntityFramework

# Or install individual packages
dotnet add package Dtde.Abstractions  # Core interfaces
dotnet add package Dtde.Core          # Core implementations
dotnet add package Dtde.EntityFramework  # EF Core integration

Requirements: .NET 8.0+ / 9.0+ / 10.0+, Entity Framework Core 8.0+ / 9.0+ / 10.0+


πŸš€ Quick Start

1. Define Your Entity

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Region { get; set; } = string.Empty;  // Shard key
    public DateTime CreatedAt { get; set; }
}

2. Create Your DbContext

public class AppDbContext : DtdeDbContext
{
    public DbSet<Customer> Customers => Set<Customer>();

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>(entity =>
        {
            entity.ShardBy(c => c.Region);  // Enable sharding by Region
        });
    }
}

3. Configure Services

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
    options.UseDtde(dtde =>
    {
        dtde.AddShard(s => s.WithId("EU").WithShardKeyValue("EU").WithTier(ShardTier.Hot));
        dtde.AddShard(s => s.WithId("US").WithShardKeyValue("US").WithTier(ShardTier.Hot));
        dtde.AddShard(s => s.WithId("APAC").WithShardKeyValue("APAC").WithTier(ShardTier.Hot));
    });
});

4. Use It!

// Queries are automatically routed to correct shard(s)
var allCustomers = await _context.Customers.ToListAsync();  // Queries all shards
var euCustomers = await _context.Customers
    .Where(c => c.Region == "EU")
    .ToListAsync();  // Only queries EU shard

// Inserts are automatically routed based on shard key
_context.Customers.Add(new Customer { Name = "Acme Corp", Region = "US" });  // Routes to US shard
await _context.SaveChangesAsync();

πŸ”€ Sharding Strategies

DTDE supports multiple sharding strategies to match your data access patterns:

Property-Based Sharding

Distribute data by a property value (region, tenant, category):

modelBuilder.Entity<Customer>(entity =>
{
    entity.ShardBy(c => c.Region);
});

Use cases: Multi-region deployments, GDPR compliance, data residency

Date-Based Sharding

Partition data by date for time-series workloads:

modelBuilder.Entity<Transaction>(entity =>
{
    entity.ShardByDate(t => t.TransactionDate, DateInterval.Month);
});

Use cases: Financial transactions, audit logs, metrics, event sourcing

Hash-Based Sharding

Even distribution across shards using consistent hashing:

modelBuilder.Entity<UserProfile>(entity =>
{
    entity.ShardByHash(u => u.UserId, shardCount: 8);
});

Use cases: High-volume data, preventing hotspots, horizontal scaling

Composite Sharding

Combine strategies for complex scenarios:

modelBuilder.Entity<Order>(entity =>
{
    entity.ShardBy(o => o.Region)
          .ThenByDate(o => o.OrderDate);
});

⏱️ Temporal Versioning

Track entity history and query data at any point in time:

Enable Temporal Tracking

modelBuilder.Entity<Contract>(entity =>
{
    entity.HasTemporalValidity(
        validFrom: nameof(Contract.ValidFrom),
        validTo: nameof(Contract.ValidTo));
});

Temporal Queries

// Current data
var current = await _context.ValidAt<Contract>(DateTime.UtcNow).ToListAsync();

// Historical data (as of a specific date)
var historical = await _context.ValidAt<Contract>(new DateTime(2024, 1, 1)).ToListAsync();

// All versions (bypass temporal filtering)
var allVersions = await _context.AllVersions<Contract>()
    .Where(c => c.ContractNumber == "C-001")
    .OrderBy(c => c.ValidFrom)
    .ToListAsync();

// Data within a date range
var rangeData = await _context.ValidBetween<Contract>(startDate, endDate).ToListAsync();

Temporal Operations

// Add with effective date
_context.AddTemporal(contract, effectiveFrom: DateTime.UtcNow);

// Create new version (closes old, opens new)
var newVersion = _context.CreateNewVersion(existing, changes, effectiveDate);

// Terminate (close validity)
_context.Terminate(contract, terminationDate: DateTime.UtcNow);

await _context.SaveChangesAsync();

πŸ”„ Mixed Usage: Sharded + Regular Entities

DTDE works seamlessly alongside regular EF Core entities:

public class AppDbContext : DtdeDbContext
{
    public DbSet<Customer> Customers => Set<Customer>();   // Sharded
    public DbSet<Contract> Contracts => Set<Contract>();   // Temporal
    public DbSet<AuditLog> AuditLogs => Set<AuditLog>();   // Regular EF Core
}
Entity Configuration Behavior
ShardBy() configured Queries routed by shard key
HasTemporalValidity() configured Temporal filtering applied
No special configuration Standard EF Core entity
Direct DbSet<T> access Bypasses DTDE filtering

πŸ—„οΈ Shard Tiers

Organize shards by access frequency for cost optimization:

dtde.AddShard(s => s
    .WithId("2024-current")
    .WithTier(ShardTier.Hot)         // Frequently accessed, recent data
    .WithConnectionString(fastStorage));

dtde.AddShard(s => s
    .WithId("2023-archive")
    .WithTier(ShardTier.Cold)        // Archived, rarely accessed
    .WithConnectionString(cheapStorage)
    .AsReadOnly());                   // Prevent accidental writes
Tier Description Typical Storage
Hot Frequently accessed, recent data SSD, Premium SQL
Warm Less frequently accessed Standard SQL
Cold Archived, rarely accessed Archive storage
Archive Long-term retention Cold storage

βš™οΈ Configuration Options

Fluent API

options.UseDtde(dtde =>
{
    // Entity configuration
    dtde.ConfigureEntity<Order>(e => e.ShardByDate(o => o.OrderDate));

    // Shard definitions
    dtde.AddShard(s => s.WithId("primary").WithConnectionString("..."));

    // Load from configuration file
    dtde.AddShardsFromConfig("shards.json");

    // Default temporal context
    dtde.SetDefaultTemporalContext(() => DateTime.UtcNow);

    // Performance tuning
    dtde.SetMaxParallelShards(10);

    // Diagnostics
    dtde.EnableDiagnostics();
});

JSON Configuration (shards.json)

{
  "shards": [
    {
      "shardId": "2024-q4",
      "name": "Q4 2024 Data",
      "connectionString": "Server=...;Database=Data2024Q4",
      "tier": "Hot",
      "dateRangeStart": "2024-10-01",
      "dateRangeEnd": "2024-12-31",
      "isReadOnly": false,
      "priority": 100
    },
    {
      "shardId": "2024-archive",
      "name": "2024 Archive",
      "connectionString": "Server=...;Database=Archive2024",
      "tier": "Cold",
      "isReadOnly": true,
      "priority": 10
    }
  ]
}

πŸ“Š Performance Benchmarks

Comprehensive benchmarks comparing single table vs sharded approaches:

Test Environment

Component Specification
OS Windows 11 (10.0.26200.7462)
CPU 12th Gen Intel Core i9-12900H (14 cores, 20 threads)
Runtime .NET 9.0.10, RyuJIT AVX2
SDK .NET SDK 10.0.101
Database SQLite (file-based, separate DBs per benchmark)
Framework BenchmarkDotNet v0.15.0

Key Results

Query Type Records Single Table Sharded Improvement
Point Lookup (by ID) 100K 153.1 ns 145.7 ns ~Same
Date Range (1 month) 100K 16,257 Β΅s 3,555 Β΅s 4.6x faster
Region Scan (single) 100K 3,638 Β΅s 1,774 Β΅s 2.1x faster
Region Scan (all) 100K 5,535 Β΅s 2,014 Β΅s 2.7x faster
Count (all records) 100K 7,873 Β΅s 1,330 Β΅s 5.9x faster
Count (all records) 50K 3,387 Β΅s 24.1 Β΅s 141x faster

Concurrent Access Performance

Scenario Threads Single Table Sharded Improvement
Parallel Aggregation 4 1,668 Β΅s 546 Β΅s 3.1x faster
Parallel Aggregation 8 2,043 Β΅s 696 Β΅s 2.9x faster
Parallel Reads (diff regions) 4 1,822 Β΅s 542 Β΅s 3.4x faster
Parallel Reads (diff regions) 8 2,479 Β΅s 758 Β΅s 3.3x faster

Write Operations

Operation Batch Size Single Table Sharded Notes
Batch Update 100 414.5 Β΅s 317.7 Β΅s 23% faster
Batch Update 1000 388.8 Β΅s 299.5 Β΅s 23% faster
Batch Delete 100 326.8 Β΅s 302.4 Β΅s ~Same

Key insight: Sharded queries benefit significantly from partition pruning β€” queries that target a specific shard key value only scan relevant partitions. Concurrent workloads show even greater improvements due to reduced contention.

When to Use Sharding

βœ… Good candidates:

  • Large datasets (millions+ rows)
  • Time-series / temporal data
  • Multi-tenant applications
  • Geographic distribution requirements
  • High write throughput needs
  • Hot/cold data patterns
  • Concurrent read-heavy workloads

⚠️ Consider carefully:

  • Small datasets (<100K rows)
  • Random access patterns
  • Complex cross-entity joins
  • Simple CRUD applications
# Run benchmarks
cd benchmarks/Dtde.Benchmarks
dotnet run -c Release

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Your Application                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                   Dtde.EntityFramework                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚DtdeDbContextβ”‚  β”‚Query Engine β”‚  β”‚   Update Engine     β”‚  β”‚
β”‚  β”‚  - ValidAt  β”‚  β”‚ - Rewriter  β”‚  β”‚  - VersionManager   β”‚  β”‚
β”‚  β”‚  - AllVersionsβ”‚ β”‚ - Executor  β”‚  β”‚  - ShardRouter     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                      Dtde.Core                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Metadata   β”‚  β”‚  Sharding   β”‚  β”‚     Temporal        β”‚  β”‚
β”‚  β”‚  Registry   β”‚  β”‚  Strategies β”‚  β”‚     Context         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                   Dtde.Abstractions                          β”‚
β”‚         Interfaces β€’ Contracts β€’ Exceptions                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Project Structure

dtde/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Dtde.Abstractions/        # Core interfaces and contracts
β”‚   β”œβ”€β”€ Dtde.Core/                # Core implementations
β”‚   └── Dtde.EntityFramework/     # EF Core integration
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ Dtde.Core.Tests/          # Unit tests
β”‚   β”œβ”€β”€ Dtde.EntityFramework.Tests/
β”‚   └── Dtde.Integration.Tests/
β”œβ”€β”€ samples/                       # Sample applications
β”‚   β”œβ”€β”€ Dtde.Sample.WebApi/       # Basic getting started
β”‚   β”œβ”€β”€ Dtde.Samples.RegionSharding/   # Property-based sharding
β”‚   β”œβ”€β”€ Dtde.Samples.DateSharding/     # Date-based sharding
β”‚   β”œβ”€β”€ Dtde.Samples.HashSharding/     # Hash-based sharding
β”‚   β”œβ”€β”€ Dtde.Samples.MultiTenant/      # Multi-tenancy
β”‚   └── Dtde.Samples.Combined/         # Mixed strategies
β”œβ”€β”€ benchmarks/
β”‚   └── Dtde.Benchmarks/          # Performance benchmarks
└── docs/                         # Documentation (MkDocs)

πŸ’‘ Sample Projects

Explore working examples for each sharding strategy:

Sample Strategy Use Case
Dtde.Sample.WebApi Basic Getting started
Dtde.Samples.RegionSharding Property-based Multi-region data residency
Dtde.Samples.DateSharding Date-based Time-series, audit logs
Dtde.Samples.HashSharding Hash-based Even data distribution
Dtde.Samples.MultiTenant Tenant-based SaaS multi-tenancy
Dtde.Samples.Combined Mixed Complex enterprise scenarios

πŸ§ͺ Testing

# Run all tests (101 tests)
dotnet test

# Run with coverage
dotnet test --collect:"XPlat Code Coverage"

# Run specific test project
dotnet test tests/Dtde.Core.Tests/

πŸ“š Documentation


🀝 Contributing

Contributions are welcome! Please read our Contributing Guide before submitting PRs.

# Clone and build
git clone https://github.com/yohasacura/dtde.git
cd dtde
dotnet build
dotnet test

See also:


πŸ“„ License

This project is licensed under the MIT License β€” see the LICENSE file for details.


πŸ™ Acknowledgments


Made with ❀️ for the .NET community

⭐ Star on GitHub Β· πŸ› Report Bug Β· πŸ’¬ Discussions

About

Distributed Temporal Data Engine - Transparent horizontal sharding and temporal versioning for Entity Framework Core

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Packages

 
 
 

Contributors

Languages