Лекция
Привет, Вы узнаете о том , что такое порционная обработка данных, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое порционная обработка данных , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend) .
В PHP можно организовать обработку данных порциями разными способами, в зависимости от того, с чем именно мы работаем: с базой данных, файлами или массивами.
В PHP порционная обработка данных (batch / chunk processing) обычно нужна, когда данных много и нельзя грузить все в память или обрабатывать за один запрос.
Основные методы:
рассмотрим эти и другие методы более подробнее
При работе с базой данных мы можем загружать данные порциями с помощью LIMIT и OFFSET
$limit = 10; // Количество записей на страницу
$offset = 0; // Смещение (номер страницы * $limit)
$sql = "SELECT * FROM users ORDER BY id LIMIT $limit OFFSET $offset";
$result = $pdo->query($sql)->fetchAll();
Альтернативный вариант – постраничная выборка без OFFSET
$last_id = 0;
$sql = "SELECT * FROM users WHERE id > $last_id ORDER BY id LIMIT $limit";
Просто
Подходит для небольших таблиц
OFFSET медленный на больших таблицах
Проблемы при изменении данных во время обработки
Используй только до ~100k записей
Идея: двигаться по первичному ключу.
$limit = 100;
$lastId = 0;
do
{
$rows = $db
->query( "SELECT * FROM users WHERE id > $lastId ORDER BY id LIMIT $limit" )
->fetchAll();
foreach ($rows as $row) {
process($row);
$lastId = $row['id'];
}
} while (!empty($rows));
Очень быстро
Не ломается при больших объемах
Стабильно при параллельных изменениях
Нужен монотонный ключ (id, created_at)
Лучший вариант для БД
При работе с большими файлами можно читать их построчно, а не загружать в память целиком.fgets() – чтение построчно
$handle = fopen("large_file.txt", "r");
while (($line = fgets($handle)) !== false) {
echo $line;
}
fclose($handle);
SplFileObject – более удобный способ
$file = new SplFileObject("large_file.txt");
while (!$file->eof()) {
echo $file->fgets();
}
Использовать JsonMachine
Или потоковое чтение
foreach (JsonMachine\Items::fromFile('big.json') as $item) {
process($item);
}
Если у нас массив данных, можно разбить его на части
$data = range(1, 100);
$chunks = array_chunk($data, 10);
foreach ($chunks as $chunk) {
print_r($chunk); // Выведет массивы по 10 элементов
}
Генераторы позволяют возвращать элементы по одному, не загружая все в память сразу.
Идея: обрабатывать данные по одной записи, но читать пачками.
function getData() {
for ($i = 1; $i <= 100; $i++) {
yield $i; // Возвращает по одному значению
}
}
foreach (getData() as $num) {
echo $num . "\n";
}
пример для выборки данных из бд
function getUsers(PDO $db, int $chunk = 100): Generator {
$lastId = 0;
while (true) {
$stmt = $db->prepare(
"SELECT * FROM users WHERE id > :id ORDER BY id LIMIT :limit"
);
$stmt->bindValue(':id', $lastId, PDO::PARAM_INT);
$stmt->bindValue(':limit', $chunk, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll();
if (!$rows) {
break;
}
foreach ($rows as $row) {
$lastId = $row['id'];
yield $row;
}
}
}
Использование:
foreach (getUsers($db) as $user) {
process($user);
}
Минимум памяти
Чистый код
Генераторы (yield) в PHP — штука мощная, но не серебряная пуля. Об этом говорит сайт https://intellect.icu . В контексте порционной обработки данных / ETL / batch-скриптов у них есть вполне реальные минусы.
1 Нельзя «перемотать назад»
Генератор — одноразовый поток.
$gen = getUsers(); foreach ($gen as $u) {} foreach ($gen as $u) {} // не сработает
Если нужна повторная обработка — генератор надо создавать заново.
2 Сложно отлаживать
нельзя просто var_dump($gen)
нельзя посмотреть «все данные»
стек вызовов хуже читается
При ошибках внутри yield дебаг неприятнее, чем с массивами.
3 Ошибки «вылезают» поздно
$gen = getUsers(); // OK
// ...
foreach ($gen as $row) {
// ❌ ошибка здесь, а не при создании
}
Исключение возникает в момент итерации, а не инициализации — это иногда ломает логику обработки ошибок.
4 Не подходит, если нужны все данные сразу
Генераторы плохо подходят для:
сортировки
поиска по всему набору
повторного прохода
подсчета итогов без промежуточного состояния
$count = count($gen); // Нужно копить данные — и смысл yield пропадает.
5 Сложно комбинировать с транзакциями
$db->beginTransaction();
foreach (getUsers($db) as $row) {
process($row); // долгий процесс
}
$db->commit(); // долго открытая транзакция
Транзакции могут висеть минутами, что плохо для БД.
6 Накладные расходы на каждый yield
каждый yield — переключение контекста
медленнее, чем простой while + массив, если данных мало
Для малых объемов генераторы часто медленнее, а не быстрее.
7 Сложнее делать retry и откаты
Если:
процесс упал на середине
сервер перезапустился
генератор не знает, где он был
Нужно вручную:
хранить lastId
писать чекпоинты
логировать прогресс
8 Не все PHP-разработчики умеют с ними работать
сложнее читать код
больше ошибок при поддержке
«магия» для джунов
В командной разработке это реальный минус.
Когда генераторы — плохой выбор
Нужна повторная обработка данных
Нужна сортировка / агрегация всего набора
Есть транзакции на большие блоки
Нужны гарантии восстановления после падения
Код поддерживает большая команда
Когда генераторы — отличный выбор
Огромные данные
Минимум памяти
Последовательная обработка
Read-only сценарии
ETL без строгих rollback’ов
yield — это оптимизация памяти, а не архитектура обработки
Он отлично решает одну задачу (memory),
но не решает:
надежность
повторы
контроль выполнения
Идея: данные → очередь → воркеры
queue()->push(['user_id' => 123]);
Воркер:
while ($job = queue()->pop()) { process($job); }
Масштабируемо
Можно распараллелить
Отказоустойчиво
Для тяжелых и долгих задач — лучший выбор
Общие минусы очередей (в целом)
Независимо от реализации:
Сложность архитектуры
Уже не «один PHP-скрипт», а:
брокер
продюсеры
воркеры
мониторинг
Сложнее отлаживать
Ошибка может быть:
в очереди
в воркере
в повторной доставке
Не моментальный результат
Это асинхронная модель — пользователь не ждет выполнения
Нужно следить за падениями воркеров
Без supervisor / systemd все ломается
Идея: обрабатывать частями по времени.
// берем 100 необработанных SELECT * FROM tasks WHERE processed = 0 LIMIT 100;
После обработки:
UPDATE tasks SET processed = 1 WHERE id = ?;
Импорта
Синхронизаций
Регулярных джоб
php process.php --chunk=1000
Можно форкать:
pcntl_fork();
Используется в high-load и ETL (E — Extract (извлечение) T — Transform (преобразование) L — Load (загрузка))
| Сценарий | Лучший вариант |
|---|---|
| Малый объем | LIMIT + OFFSET |
| Большая БД | WHERE id > lastId |
| Мало памяти | Generators |
| Долгие задачи | Очереди |
| Регулярно | CRON |
| Максимальная скорость | CLI + очередь |
Исследование, описанное в статье про порционная обработка данных, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое порционная обработка данных и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)
Из статьи мы узнали кратко, но содержательно про порционная обработка данных
Комментарии
Оставить комментарий
Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)
Термины: Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)