Как организовать параллельный bulk insert и откуда ExecutorService получает задания?

Разбиение вставки на пакеты, явная передача задач исполнителю, ограничение очереди и параллелизма, обработка ошибок и ожидание завершения.

Короткий ответ

Что ответить на собеседовании

Приложение формирует независимые пакеты и передает Runnable или Callable через execute либо submit. ExecutorService — интерфейс; в ThreadPoolExecutor задания назначаются потокам или ожидают в очереди. Параллелизм ограничивают возможностями БД и пула соединений. Нужны контроль перегрузки, отдельные соединения для одновременных задач, обработка Future и ожидание завершения.

Подробный разбор

Ответ с пояснениями

Сначала оцените обычную пакетную вставку: дополнительные потоки не обязательно ускоряют базу данных. Если параллелизм оправдан, приложение разбивает входные данные на ограниченные пакеты и создает для них Runnable или Callable. Именно вызывающий код передает задания через execute или submit; исполнитель не извлекает их из базы автоматически.

ExecutorService — интерфейс, а не обещание конкретного устройства пула. В реализации ThreadPoolExecutor задачи передаются рабочим потокам или ожидают в очереди согласно настройкам. Для большого потока данных нужны ограниченная очередь и явная политика перегрузки: контролируемое замедление отправителя либо обработка отказа. Неограниченная очередь способна исчерпать память. Эти механизмы описаны в документации ThreadPoolExecutor.

Число одновременно выполняемых вставок согласуют с доступными соединениями и возможностями СУБД, оставляя ресурс другим запросам. Каждая одновременная задача должна использовать собственное соединение и согласованную транзакционную границу; делить одно JDBC-соединение между такими задачами не следует. Ресурсы закрывают, а ошибки транзакции обрабатывают явно. Несколько самостоятельных транзакций не обеспечивают атомарность всей загрузки.

После submit сохраняют Future и проверяют результат через get, обрабатывая исключения, таймауты и прерывание. Иначе завершившаяся с ошибкой задача может остаться незамеченной. Нужно также определить безопасный повтор пакета и поведение при частично выполненной загрузке.

shutdown() прекращает прием новых задач, но не ждет окончания прежних. Ожидание организуют через awaitTermination и результаты задач, контролируя исход. Различие закреплено в контракте ExecutorService.

Практика в реальном времени

Подготовьтесь к следующему собеседованию

Interview Boost учитывает вакансию, резюме и технологии и помогает сформулировать ответ прямо во время интервью.

Начать подготовку