Comments 21
А на кой черт вам все сто тыщ записей в памяти? Запрашивайте кусочками по тысяче, всё равно же обрабатываете по одной.
+9
Вот именно. Если вы зачем-то выбираете 100000 записей в приложении разом — вы что-то делаете не так. И уж тем более вам не нужно столько записей на одной странице сайта, так что заявка про «никто не будет ждать 52 секунды» тоже ни о чем.
Это, что характерно, не отменяет рассуждений о применимости тех или иных инструментов для тех или иных задач, но вот задачи у вас какие-то странные.
Это, что характерно, не отменяет рассуждений о применимости тех или иных инструментов для тех или иных задач, но вот задачи у вас какие-то странные.
0
Согласен.
Но! В данном посте не алгоритм работы моих функций описывался, а результаты тестирования двух подходов в получении данных.
Благодаря тесту можно видеть, что даже при выборке пачками в 1 000 штук, DAO жрет в 6,37 раз меньше ресурсов.
Но! В данном посте не алгоритм работы моих функций описывался, а результаты тестирования двух подходов в получении данных.
Благодаря тесту можно видеть, что даже при выборке пачками в 1 000 штук, DAO жрет в 6,37 раз меньше ресурсов.
-6
а как вы проверяете, что между запросами данные не поменялись и вы выберите всё, без пропусков и дублирований?
0
Что-то не припомню ничего подобного в исходной постановке задачи. Хотите менять условия — распишите целиком, что вам хочется получить и насколько критично чего-то из перечисленного не достичь.
0
То есть в задаче «сделать рассылку по 100 000 адресам» вам надо отдельно указывать, что «не разослать по десятку из них или разослать дважды — плохо»?
0
Конечно надо. Хотите предсказуемый результат — учитесь ставить задачи.
Вообще, в данном случае, первое, что приходит на ум — нарезать диапазоны по id, завернуть все select-ы в одну транзакцию и дальше не париться. Разослать дважды не получится (неуникальность почтового адреса не рассматриваем), проблемы с неактуальностью могут возникнуть только для записей, которые появились или отредактировались во время рассылки (первым ничего не придёт, вторым придёт на старый адрес). Насколько вероятны такие ситуации и насколько это плохо — решать бизнесу.
Вообще, в данном случае, первое, что приходит на ум — нарезать диапазоны по id, завернуть все select-ы в одну транзакцию и дальше не париться. Разослать дважды не получится (неуникальность почтового адреса не рассматриваем), проблемы с неактуальностью могут возникнуть только для записей, которые появились или отредактировались во время рассылки (первым ничего не придёт, вторым придёт на старый адрес). Насколько вероятны такие ситуации и насколько это плохо — решать бизнесу.
0
У меня на похожей задаче (выгрузка статистики пользовательских DNS-запросов в CSV), когда она возникла, была очень жесткая позиция: только потоковая обработка. Никаких переменных, хранящих весь результат запроса (потенциально сотни тысяч строк). Любая попытка сложить все в память (в том числе любой вызов C-функции mysql_fetch_result()) автоматически является багом. Вот рекомендации, как этого бага избежать.
C: используйте mysql_use_result()
PHP: выполняйте небуферизованные запросы с флагом MYSQLI_USE_RESULT, см. www.php.net/manual/en/mysqlinfo.concepts.buffering.php
Python: есть класс MySQLdb.cursors.SSCursor.
Во всех случаях перед формированием очередного запроса необходимо полностью прочитать результат предыдущего.
К сожалению, при использовании с Django, SSCursor немедленно делает приложение совершенно не поддающимся отладке, и вот почему. Если при обработке очередной (не последней) строки view выбросит исключение, то вызовется стандартный обработчик исключений. Он постарается откатить транзакцию и отправить traceback куда надо. Только вот попытка откатить транзакцию проваливается: выбрасывается исключение ProgrammingError, так как SQL-запрос ROLLBACK пришел до того, как все строки прочитаны. В итоге первоначальное исключение теряется, а в лог и в почту попадает «безволосая черная дыра» в виде ProgrammingError. Собственно, я считаю это багом в Django: code.djangoproject.com/ticket/21777
Вот костыль для тех случаев, когда патчить Django запрещено.
C: используйте mysql_use_result()
PHP: выполняйте небуферизованные запросы с флагом MYSQLI_USE_RESULT, см. www.php.net/manual/en/mysqlinfo.concepts.buffering.php
Python: есть класс MySQLdb.cursors.SSCursor.
Во всех случаях перед формированием очередного запроса необходимо полностью прочитать результат предыдущего.
К сожалению, при использовании с Django, SSCursor немедленно делает приложение совершенно не поддающимся отладке, и вот почему. Если при обработке очередной (не последней) строки view выбросит исключение, то вызовется стандартный обработчик исключений. Он постарается откатить транзакцию и отправить traceback куда надо. Только вот попытка откатить транзакцию проваливается: выбрасывается исключение ProgrammingError, так как SQL-запрос ROLLBACK пришел до того, как все строки прочитаны. В итоге первоначальное исключение теряется, а в лог и в почту попадает «безволосая черная дыра» в виде ProgrammingError. Собственно, я считаю это багом в Django: code.djangoproject.com/ticket/21777
Вот костыль для тех случаев, когда патчить Django запрещено.
# Workaround against https://code.djangoproject.com/ticket/21777
# In short: gets meaningful tracebacks in apache log even if
# SSCursor is not completely consumed by the application when
# an exception occurs
from django.core.signals import got_request_exception, request_finished
from MySQLdb import DatabaseError
from MySQLdb.cursors import SSCursor
from threading import local
store = local()
class SafeSSCursor(SSCursor):
def execute(self, *args, **kwargs):
if not hasattr(store, 'cursors'):
store.cursors = []
if not self in store.cursors:
store.cursors.append(self)
return super(SafeSSCursor, self).execute(*args, **kwargs)
def expunge_cursors(**kwargs):
store.cursors = []
def consume_cursors(**kwargs):
if not hasattr(store, 'cursors'):
return
for cursor in store.cursors:
try:
for item in cursor:
pass
except DatabaseError:
pass
store.cursors = []
request_finished.connect(expunge_cursors)
got_request_exception.connect(consume_cursors)
+5
Извините, это у вас yii?
0
мне кажется, что в вашей ситуации нужно не active record на dao (или еще чтото) менять, а сам алгоритм
зачем грузить сразу все записи в память? — чтобы «быстрее работало» что ли?
у меня в системе порядка 1млн подписок, которые каждый день обрабатываются, обработка идет пачками по 1000 штук, германовские скрипты месяцами висят запущенными и не жрут память
зачем грузить сразу все записи в память? — чтобы «быстрее работало» что ли?
у меня в системе порядка 1млн подписок, которые каждый день обрабатываются, обработка идет пачками по 1000 штук, германовские скрипты месяцами висят запущенными и не жрут память
0
Пожалуй стоит в статье пометить (где-нибудь в начале) что тестировалась имплементация ar в yii.
Вообще все это всегда было понятно. А для уменьшения потребления памяти стоит применять штуки типа dataProviderIterator. И да, active record никогда не рассчитывалась на то, что придется работать одновременно с большим количеством объектов.
Вообще все это всегда было понятно. А для уменьшения потребления памяти стоит применять штуки типа dataProviderIterator. И да, active record никогда не рассчитывалась на то, что придется работать одновременно с большим количеством объектов.
+2
Как-то не очень корректно тестирование проведено, по-моему. Протестированы по одной неназванной реализации двух паттернов (если я вообще правильно понял о чём статья) на неизвестной выборке из неизвестной схемы. По личному опыту, универсальные ActiveRecord библиотеки с каким-то декларативным объявлением очень чутко реагируют на вид запросов, в частности на количество объединений — потребление памяти и процессора растёт на порядок с каждым объединением: скажем, из недавнего, на маппинге 30 строк Доктрина 1.2 вылетала с аут оф мемори — гигабайта не хватало. Убрал из выборки только одно объединение — порядка ста метров максимальное потребление. Если же маппинг захардкожен ручками, то всё намного лучше — рост линейный.
+1
Специально для этого в Yii придумали CDataProviderIterator он загружает CActiveRecord объекты из базы страницами (так же, как если бы вы выводили их постранично, используя CDataProvider), а не все сразу. А так, понятно, что DAO в любом случае будет быстрее, особенно на больших объёмах.
+1
Sign up to leave a comment.
ActiveRecord vs DAO – Тест потребления памяти и скорости выполнения запросов