Стали внедрять на проекте Yii. Переписали основные модели на AR. Все супер, рутинного кода ушло сразу очень много и появилась большая гибкость в разнообразных выборках.
Но у нас по крону ночью должно обрабатываться около 100 тысяч записей из БД.
Если делать выборку такого числа записей с помощью AR, то это сжирает больше гигабайта оперативки и вообще как-то не хорошо, ведь findAll() сначала выберет всю инфу, потом нагенерирует все 100 тыс. моделей и все это будет жить в памяти.
В голову приходит несколько вариантов оптимизации.
Отказаться в системных операциях от AR, написать запросы вручную, обработать вручную. Будет относительно экономично, но теряется вся прелесть AR.
Выбирать по частям (например, с LIMIT или с BETWEEN ID), обрабатывать по частям. Тогда за раз будет создаваться только некоторая часть моделей, которые в цикле будут обработаны и потом убиты сборщиком мусора. В итоге пиковое потребление памяти упадет.
Есть ли в Yii уже какие-либо готовые способы решения задачи?
Там обработка пользователей с выборкой по многим условиям и relations. Причем все условия, фильтры и параметры заданы, как и положено, во всевозможных scope.
Ну а кто вас заставляет делать findAll()? - это во первых.
Во вторых AR не предназначен для highload, он изначально в разы медленнее и прожорливее. Делайте с помощью DAO или просто в массив queryAll(). Если вы обрабатываете 100к записей за ночь - это значит что нужно было думать хорошенько перед тем как цеплять все к AR.
Тут вступает в силу требования которые вам нужны:
а) скорость и оптимальная нагрузка - DAO или просто выборка данных в массив.
б) меньше скорости и больше памяти - AR и выборка в массив.
в) меньше скорости и минимальные затраты памяти - выборка по 1 записи с помощью DAO.
г) самая маленькая скорости и небольшие затраты памяти - выборка по 1 записи AR.
Сам совершил похожую ошибку в прошлом проекте. Пришлось перековырять половину AR для оптимизаций, с некоторыми таблицами пришлось совсем убрать. В тяжелых крон задачах - AR нет, кроме одной по сбору статистики, которая выполняется 1 раз в день приблизительно за 2 минуты с выборкой findAll() ~80к записей. Почему - пока не дошли руки до нее.
Чем выше уровень абстракции - тем больше затраты ресурсов.
Да и кстати 1гб это очень много, мой скрипт сбора статистики с выборкой на 80к и последующей обработкой с выборкой еще из 2 таблиц укладывается в 150-200 мб.
Задача выполняется по крону раз в сутки и время ее выполнения не очень критично. Нужно просто, что бы она не клала сервер на время своего выполнения.
И если я обрабатываю 100к записей за ночь - это не значит, что нужн оотказаться от AR в проекте. 99% операций в системе происходят 1-2 записями и их связями, что явно укладывается в AR.
А есть ли способ на основе Scopes в AR создать кастомную выборку?
Самое большое, чего я опасаюсь - это дублирования кусков SQL по всему проекту. В этом случае правка бизнес-логики превращается в ад и именно решения этой проблемы ждешь от AR.
По поводу 1Гб - там просто много колонок нужно выбирать в каждой таблице, которая связана с моделью.
Проведите эксперимент с выборкой по 1 - через find(). В этом случае нагрузка перейдет к БД, посмотрите устроит ли вас это.
По поводу scopes - что значит кастомная выборка? Вы записываете туда то что часто используете - например статус или роль, и потом делаете выборку по типу:
Для меня это и есть уже кастомной выборкой. Тут вы действительно избегаете дублирования, например если изменятся константы статуса - не нужно их менять везде.
Если честно я предпочитаю немного другие методы для избежания таких проблем. Я делаю мелкие функции в моделях для выборок из БД. Но это личное мнение.
Да и еще по личному опыту - избегайте join-ов, особенно на больших таблицах - они часто и есть причина непомерного расходования памяти и времени.
Бд сейчас на старом проэкте выросла до 20 гб, и 50 миллионов записей в 1 таблице. Это сравнительно немного, но когда нативный AR уже на 30м начинает захлебываться и делать выборки по 5 минут - заставляет задуматься об его уместности.
Да, я измеряю сейчас производительность и думаю, что найду золотую середину для конкретной задачи.
Собственно я спрашивал, есть ли в yii, например что-то готовое для реализации, например "постраничной" выборки. Как-то так:
$ModelIterator = Model::model()->mySuperScopes()->findAllLimited(200); // вернет итератор, возвращающий по 200 моделей
foreach($ModelIterator as $models) {
// Получили новый кусок моделей и работаем с ними
}
Да, Scopes примерно так используются, только внтури более сложные условия и scope с параметрами.
Получается практически тоже самое, что делать статические методы вида User::getNeededForMeUsers();
Только у scope преимущество - позволяет использовать себя в качестве параметров при выборке relations, что очень гибко
Я всегда проверяю все запросы, которые генерирует AR вручную с помощью EXPLAIN и слежу, что бы везде джойны были по индексам. Так что это не слишком большая проблема. И я всегда представляю, какой запрос сгенерирует AR
Ну что касается 50 миллионов записей… выбирать 30 миллионов с помощью AR точно не следует =)
Кстати, заметил, что если я выберу через голое PDO те же 100к записей и , например, сохраню все строки в массив, то это займет ~600 Mb. Отсюда вывод, что большую часть оперативки жрет PDO, который хранит в себе полный результат выборки из мускуля.
Плюс к итераторам - если в запросах не используются джоины их можно легко превратить в sql и получить результат в виде простого массива, что конечно быстрее и ест меньше памяти.