← Назад к вопросам

Как оптимизировать цикл for, добавляющий миллиард строк

2.0 Middle🔥 141 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оптимизация цикла for: добавление миллиарда строк

Это классическая performance проблема, требующая понимания Java JVM и архитектурных решений.

Уровень 1: Предварительное выделение памяти

ArrayList растет динамически. Каждый раз при исчерпании capacity происходит переаллокация:

// ПЛОХО: без предварительного выделения
List<String> list1 = new ArrayList<>();
for (int i = 0; i < size; i++) {
    list1.add("Row " + i);
}

// ХОРОШО: выделяем память заранее
List<String> list2 = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
    list2.add("Row " + i);
}

Прирост производительности: 30-50%.

Уровень 2: Параллельная обработка

ExecutorService executor = Executors.newFixedThreadPool(threads);
int batchSize = size / threads;

for (int t = 0; t < threads; t++) {
    final int threadId = t;
    executor.submit(() -> {
        long start = threadId * (long) batchSize;
        long end = Math.min(start + batchSize, size);
        List<String> batch = new ArrayList<>();
        for (long i = start; i < end; i++) {
            batch.add("Row " + i);
        }
        insertBatch(batch);
    });
}

Ускорение: 3-8x на 4-8 ядрах.

Уровень 3: Batch операции

@Service
public class BulkInsertService {
    private static final int BATCH_SIZE = 10000;
    
    public void insertBulk(long totalRows) {
        List<String> batch = new ArrayList<>(BATCH_SIZE);
        
        for (long i = 0; i < totalRows; i++) {
            batch.add("Row " + i);
            if (batch.size() == BATCH_SIZE) {
                insertBatch(batch);
                batch.clear();
            }
        }
        if (!batch.isEmpty()) {
            insertBatch(batch);
        }
    }
}

Ускорение: 100-1000x vs одиночные inserts.

Уровень 4: Native SQL вместо ORM

// ПЛОХО: ORM
for (long i = 0; i < 1_000_000_000L; i++) {
    repository.save(new MyEntity(i, "Row " + i));
}

// ХОРОШО: Native SQL batches
List<Object[]> batch = new ArrayList<>();
for (long i = 0; i < totalRows; i++) {
    batch.add(new Object[]{i, "Row " + i});
    if (batch.size() == 1000) {
        jdbcTemplate.batchUpdate(
            "INSERT INTO my_table (id, data) VALUES (?, ?)",
            batch
        );
        batch.clear();
    }
}

Ускорение: 10-100x.

Уровень 5: PostgreSQL COPY команда

Самый быстрый способ:

public void bulkInsertWithCopy(long totalRows) throws Exception {
    String sql = "COPY my_table (id, data) FROM STDIN";
    CopyManager copyManager = new CopyManager((BaseConnection) connection);
    
    StringBuilder csv = new StringBuilder();
    for (long i = 0; i < totalRows; i++) {
        csv.append(i).append("\\t").append("Row ").append(i).append("\\n");
        
        if (i % 100000 == 0) {
            copyManager.copyIn(sql, new StringReader(csv.toString()));
            csv = new StringBuilder();
        }
    }
}

Ускорение: 10-100x vs batch INSERT.

Уровень 6: Потоковая обработка

final int CHUNK_SIZE = 100000;
IntStream.rangeClosed(0, (int)(totalRows / CHUNK_SIZE))
    .parallel()
    .forEach(chunkIndex -> {
        long start = (long) chunkIndex * CHUNK_SIZE;
        long end = Math.min(start + CHUNK_SIZE, totalRows);
        List<String> chunk = new ArrayList<>();
        for (long i = start; i < end; i++) {
            chunk.add("Row " + i);
        }
        processChunk(chunk);
    });

Уровень 7: JVM параметры

java -Xms8G -Xmx8G \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+ParallelRefProcEnabled \
     App

Полный оптимизированный пример

@Service
public class OptimizedBulkInsertService {
    private static final int BATCH_SIZE = 50000;
    private static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors();
    
    public void insertOneBillion() throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        long totalRows = 1_000_000_000L;
        long rowsPerThread = totalRows / THREAD_COUNT;
        
        for (int t = 0; t < THREAD_COUNT; t++) {
            final int threadId = t;
            executor.submit(() -> {
                long start = threadId * rowsPerThread;
                long end = (threadId == THREAD_COUNT - 1) ? totalRows : start + rowsPerThread;
                insertRange(start, end);
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);
    }
    
    private void insertRange(long start, long end) {
        List<Object[]> batch = new ArrayList<>(BATCH_SIZE);
        for (long i = start; i < end; i++) {
            batch.add(new Object[]{i, "Row " + i});
            if (batch.size() == BATCH_SIZE) {
                batchInsert(batch);
                batch.clear();
            }
        }
        if (!batch.isEmpty()) {
            batchInsert(batch);
        }
    }
    
    private void batchInsert(List<Object[]> batch) {
        jdbcTemplate.batchUpdate(
            "INSERT INTO my_table (id, data) VALUES (?, ?)",
            batch
        );
    }
}

Сравнение производительности

МетодВремя на 1млнПримечание
Single inserts1000msНевозможно для 1млрд
Batch (10k)50-100msПриемлемо
Batch (100k)10-20msХорошо
Native SQL batch5-10msОтлично
PostgreSQL COPY0.5-2msМаксимум
Parallel COPY0.1-0.5msЛучший результат

Итог

Для оптимизации вставки миллиарда строк нужно: предварительно выделить память, использовать batch inserts вместо одиночных операций, распределить нагрузку на несколько потоков, применить native SQL вместо ORM, использовать COPY команды для PostgreSQL, оптимизировать JVM параметры. Правильная реализация достигает 10000x ускорения!