architecture

Реализация шардирования

Практическая реализация шардирования в приложениях

#scaling #sharding #databases #implementation

Реализация шардирования

Практическое руководство по реализации шардирования в реальных приложениях.

Shard Manager

class ShardManager {
  private shards: Map<string, Shard> = new Map();
  private strategy: ShardingStrategy;
  
  constructor(config: ShardConfig) {
    this.initializeShards(config.shards);
    this.strategy = this.createStrategy(config.strategy);
  }
  
  async query(shardKey: string, sql: string, params: any[]): Promise<any> {
    const shard = this.strategy.getShard(shardKey);
    const connection = await this.getConnection(shard);
    return connection.query(sql, params);
  }
  
  async queryAll(sql: string, params: any[]): Promise<any[]> {
    const promises = Array.from(this.shards.values()).map(async (shard) => {
      const connection = await this.getConnection(shard);
      return connection.query(sql, params);
    });
    return Promise.all(promises);
  }
}

Repository Pattern

abstract class ShardedRepository<T> {
  constructor(protected shardManager: ShardManager) {}
  
  protected abstract getShardKey(entity: T): string;
  
  async findById(id: string, shardKey: string): Promise<T | null> {
    const sql = `SELECT * FROM ${this.getTableName()} WHERE id = ?`;
    const result = await this.shardManager.query(shardKey, sql, [id]);
    return result.length > 0 ? this.mapToEntity(result[0]) : null;
  }
  
  async save(entity: T): Promise<T> {
    const shardKey = this.getShardKey(entity);
    const data = this.mapToData(entity);
    
    const columns = Object.keys(data);
    const values = Object.values(data);
    
    const sql = `INSERT INTO ${this.getTableName()} 
      (${columns.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
    
    await this.shardManager.query(shardKey, sql, values);
    return entity;
  }
}

Migration

class MigrationManager {
  async migrate(tableName: string): Promise<void> {
    const totalRecords = await this.getTotalRecords(tableName);
    const batchSize = 1000;
    let processed = 0;
    
    for (const shard of this.shardManager.getShards()) {
      let offset = 0;
      
      while (true) {
        const batch = await this.readBatch(shard, tableName, offset, batchSize);
        if (batch.length === 0) break;
        
        for (const record of batch) {
          await this.migrateRecord(tableName, record);
        }
        
        processed += batch.length;
        offset += batchSize;
      }
    }
  }
}

Мониторинг

class ShardingMetrics {
  recordQuery(shardId: string, duration: number, type: 'read' | 'write'): void {
    const metric = this.getOrCreateMetric(shardId);
    metric.totalQueries++;
    metric.totalDuration += duration;
  }
  
  getMetrics(shardId: string): ShardMetric {
    return this.getOrCreateMetric(shardId);
  }
}

Best Practices

  1. Connection Pooling — используйте пулы соединений
  2. Retry Logic — реализуйте повторные попытки
  3. Monitoring — отслеживайте метрики шардов
  4. Graceful Degradation — обрабатывайте отказы шардов
  5. Testing — тестируйте на нескольких шардах

Заключение

Реализация шардирования требует:

  • Правильного выбора shard key
  • Эффективной маршрутизации запросов
  • Мониторинга и метрик
  • Стратегии миграции данных