Выборка Activerecord По Частям
#1
Posted 12 September 2013 - 03:49 AM
Но у нас по крону ночью должно обрабатываться около 100 тысяч записей из БД.
Если делать выборку такого числа записей с помощью AR, то это сжирает больше гигабайта оперативки и вообще как-то не хорошо, ведь findAll() сначала выберет всю инфу, потом нагенерирует все 100 тыс. моделей и все это будет жить в памяти.
В голову приходит несколько вариантов оптимизации.
1. Отказаться в системных операциях от AR, написать запросы вручную, обработать вручную. Будет относительно экономично, но теряется вся прелесть AR.
2. Выбирать по частям (например, с LIMIT или с BETWEEN ID), обрабатывать по частям. Тогда за раз будет создаваться только некоторая часть моделей, которые в цикле будут обработаны и потом убиты сборщиком мусора. В итоге пиковое потребление памяти упадет.
Есть ли в Yii уже какие-либо готовые способы решения задачи?
#2
Posted 12 September 2013 - 03:54 AM
А в чем состоит обработка? Можно ведь получать данные в виде массива (без создания AR).
#3
Posted 12 September 2013 - 04:11 AM
Charger, on 12 September 2013 - 03:54 AM, said:
А в чем состоит обработка? Можно ведь получать данные в виде массива (без создания AR).
Там обработка пользователей с выборкой по многим условиям и relations. Причем все условия, фильтры и параметры заданы, как и положено, во всевозможных scope.
Или я не правильно Вас понял?
#4
Posted 12 September 2013 - 04:50 AM
Во вторых AR не предназначен для highload, он изначально в разы медленнее и прожорливее. Делайте с помощью DAO или просто в массив queryAll(). Если вы обрабатываете 100к записей за ночь - это значит что нужно было думать хорошенько перед тем как цеплять все к AR.
Тут вступает в силу требования которые вам нужны:
а) скорость и оптимальная нагрузка - DAO или просто выборка данных в массив.
б) меньше скорости и больше памяти - AR и выборка в массив.
в) меньше скорости и минимальные затраты памяти - выборка по 1 записи с помощью DAO.
г) самая маленькая скорости и небольшие затраты памяти - выборка по 1 записи AR.
Сам совершил похожую ошибку в прошлом проекте. Пришлось перековырять половину AR для оптимизаций, с некоторыми таблицами пришлось совсем убрать. В тяжелых крон задачах - AR нет, кроме одной по сбору статистики, которая выполняется 1 раз в день приблизительно за 2 минуты с выборкой findAll() ~80к записей. Почему - пока не дошли руки до нее.
Чем выше уровень абстракции - тем больше затраты ресурсов.
Да и кстати 1гб это очень много, мой скрипт сбора статистики с выборкой на 80к и последующей обработкой с выборкой еще из 2 таблиц укладывается в 150-200 мб.
#5
Posted 12 September 2013 - 04:58 AM
ineersa, on 12 September 2013 - 04:50 AM, said:
Во вторых AR не предназначен для highload, он изначально в разы медленнее и прожорливее. Делайте с помощью DAO или просто в массив queryAll(). Если вы обрабатываете 100к записей за ночь - это значит что нужно было думать хорошенько перед тем как цеплять все к AR.
Тут вступает в силу требования которые вам нужны:
а) скорость и оптимальная нагрузка - DAO или просто выборка данных в массив.
б) меньше скорости и больше памяти - AR и выборка в массив.
в) меньше скорости и минимальные затраты памяти - выборка по 1 записи с помощью DAO.
г) самая маленькая скорости и небольшие затраты памяти - выборка по 1 записи AR.
Сам совершил похожую ошибку в прошлом проекте. Пришлось перековырять половину AR для оптимизаций, с некоторыми таблицами пришлось совсем убрать. В тяжелых крон задачах - AR нет, кроме одной по сбору статистики, которая выполняется 1 раз в день приблизительно за 2 минуты с выборкой findAll() ~80к записей. Почему - пока не дошли руки до нее.
Чем выше уровень абстракции - тем больше затраты ресурсов.
Оно и понятно. Я с Вами не спорю.
Задача выполняется по крону раз в сутки и время ее выполнения не очень критично. Нужно просто, что бы она не клала сервер на время своего выполнения.
И если я обрабатываю 100к записей за ночь - это не значит, что нужн оотказаться от AR в проекте. 99% операций в системе происходят 1-2 записями и их связями, что явно укладывается в AR.
А есть ли способ на основе Scopes в AR создать кастомную выборку?
Самое большое, чего я опасаюсь - это дублирования кусков SQL по всему проекту. В этом случае правка бизнес-логики превращается в ад и именно решения этой проблемы ждешь от AR.
По поводу 1Гб - там просто много колонок нужно выбирать в каждой таблице, которая связана с моделью.
#6
Posted 12 September 2013 - 06:13 AM
Проведите эксперимент с выборкой по 1 - через find(). В этом случае нагрузка перейдет к БД, посмотрите устроит ли вас это.
По поводу scopes - что значит кастомная выборка? Вы записываете туда то что часто используете - например статус или роль, и потом делаете выборку по типу:
$users=User::model()->status()->role()->findAll();
Для меня это и есть уже кастомной выборкой. Тут вы действительно избегаете дублирования, например если изменятся константы статуса - не нужно их менять везде.
Если честно я предпочитаю немного другие методы для избежания таких проблем. Я делаю мелкие функции в моделях для выборок из БД. Но это личное мнение.
Да и еще по личному опыту - избегайте join-ов, особенно на больших таблицах - они часто и есть причина непомерного расходования памяти и времени.
Бд сейчас на старом проэкте выросла до 20 гб, и 50 миллионов записей в 1 таблице. Это сравнительно немного, но когда нативный AR уже на 30м начинает захлебываться и делать выборки по 5 минут - заставляет задуматься об его уместности.
#7
Posted 12 September 2013 - 06:56 AM
ineersa, on 12 September 2013 - 06:13 AM, said:
Проведите эксперимент с выборкой по 1 - через find(). В этом случае нагрузка перейдет к БД, посмотрите устроит ли вас это.
По поводу scopes - что значит кастомная выборка? Вы записываете туда то что часто используете - например статус или роль, и потом делаете выборку по типу:
$users=User::model()->status()->role()->findAll();
Для меня это и есть уже кастомной выборкой. Тут вы действительно избегаете дублирования, например если изменятся константы статуса - не нужно их менять везде.
Если честно я предпочитаю немного другие методы для избежания таких проблем. Я делаю мелкие функции в моделях для выборок из БД. Но это личное мнение.
Да и еще по личному опыту - избегайте 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, что очень гибко
Quote
Я всегда проверяю все запросы, которые генерирует AR вручную с помощью EXPLAIN и слежу, что бы везде джойны были по индексам. Так что это не слишком большая проблема. И я всегда представляю, какой запрос сгенерирует AR

Ну что касается 50 миллионов записей.. выбирать 30 миллионов с помощью AR точно не следует =)
#8
Posted 12 September 2013 - 07:17 AM
$list = MyModel::find()->select(array('id', 'name')->where(..)->limit(...)->asArray()->all();
И никакого AR!

#10
Posted 12 September 2013 - 07:59 AM
#11
Posted 12 September 2013 - 08:27 AM

Если вам скорость неважна делайте выборку по 1. Нагрузка на БД должна быть приемлима, а затраты памяти минимальны.
Yii 2 действительно сыроват.
#12
Posted 19 September 2013 - 05:40 AM
bFree, on 12 September 2013 - 06:56 AM, said:
Собственно я спрашивал, есть ли в yii, например что-то готовое для реализации, например "постраничной" выборки. Как-то так:
$ModelIterator = Model::model()->mySuperScopes()->findAllLimited(200); // вернет итератор, возвращающий по 200 моделей foreach($ModelIterator as $models) { // Получили новый кусок моделей и работаем с ними }
О боже мой! Оказывается в версии 1.13 это было сделано. Разработчики yii опередили меня =(
Кто столкнется с подобной задачей, велкам: http://yiiframework....ovider.iterator
#13
Posted 22 September 2013 - 01:03 PM
#14
Posted 13 March 2014 - 01:34 AM