Pull to refresh

Comments 21

А на кой черт вам все сто тыщ записей в памяти? Запрашивайте кусочками по тысяче, всё равно же обрабатываете по одной.
Вот именно. Если вы зачем-то выбираете 100000 записей в приложении разом — вы что-то делаете не так. И уж тем более вам не нужно столько записей на одной странице сайта, так что заявка про «никто не будет ждать 52 секунды» тоже ни о чем.

Это, что характерно, не отменяет рассуждений о применимости тех или иных инструментов для тех или иных задач, но вот задачи у вас какие-то странные.
Согласен.
Но! В данном посте не алгоритм работы моих функций описывался, а результаты тестирования двух подходов в получении данных.
Благодаря тесту можно видеть, что даже при выборке пачками в 1 000 штук, DAO жрет в 6,37 раз меньше ресурсов.
Не подходов, всё-таки, а конкретных реализаций двух паттернов.
Не в 6.37 раз, а на 6МБ. То есть по текущим ценам где-то несколько центов.
а как вы проверяете, что между запросами данные не поменялись и вы выберите всё, без пропусков и дублирований?
Что-то не припомню ничего подобного в исходной постановке задачи. Хотите менять условия — распишите целиком, что вам хочется получить и насколько критично чего-то из перечисленного не достичь.
То есть в задаче «сделать рассылку по 100 000 адресам» вам надо отдельно указывать, что «не разослать по десятку из них или разослать дважды — плохо»?
Конечно надо. Хотите предсказуемый результат — учитесь ставить задачи.

Вообще, в данном случае, первое, что приходит на ум — нарезать диапазоны по id, завернуть все select-ы в одну транзакцию и дальше не париться. Разослать дважды не получится (неуникальность почтового адреса не рассматриваем), проблемы с неактуальностью могут возникнуть только для записей, которые появились или отредактировались во время рассылки (первым ничего не придёт, вторым придёт на старый адрес). Насколько вероятны такие ситуации и насколько это плохо — решать бизнесу.
> Хотите предсказуемый результат — учитесь ставить задачи.

Вообще то когда я хочу предсказуемый результат, то другое просто не оплачу. И здесь не важно умею ли я ставить задачи.
У меня на похожей задаче (выгрузка статистики пользовательских 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 запрещено.

# 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)

Прошу прощения за опечатку. Ошибкой является вызов mysql_store_result()
мне кажется, что в вашей ситуации нужно не active record на dao (или еще чтото) менять, а сам алгоритм
зачем грузить сразу все записи в память? — чтобы «быстрее работало» что ли?

у меня в системе порядка 1млн подписок, которые каждый день обрабатываются, обработка идет пачками по 1000 штук, германовские скрипты месяцами висят запущенными и не жрут память
В данном посте не алгоритм работы моих функций описывался, а результаты тестирования двух подходов в получении данных.
Благодаря тесту можно видеть, что даже при выборке пачками в 1 000 штук, DAO жрет в 6,37 раз меньше ресурсов.
Пожалуй стоит в статье пометить (где-нибудь в начале) что тестировалась имплементация ar в yii.

Вообще все это всегда было понятно. А для уменьшения потребления памяти стоит применять штуки типа dataProviderIterator. И да, active record никогда не рассчитывалась на то, что придется работать одновременно с большим количеством объектов.
Как-то не очень корректно тестирование проведено, по-моему. Протестированы по одной неназванной реализации двух паттернов (если я вообще правильно понял о чём статья) на неизвестной выборке из неизвестной схемы. По личному опыту, универсальные ActiveRecord библиотеки с каким-то декларативным объявлением очень чутко реагируют на вид запросов, в частности на количество объединений — потребление памяти и процессора растёт на порядок с каждым объединением: скажем, из недавнего, на маппинге 30 строк Доктрина 1.2 вылетала с аут оф мемори — гигабайта не хватало. Убрал из выборки только одно объединение — порядка ста метров максимальное потребление. Если же маппинг захардкожен ручками, то всё намного лучше — рост линейный.
Специально для этого в Yii придумали CDataProviderIterator он загружает CActiveRecord объекты из базы страницами (так же, как если бы вы выводили их постранично, используя CDataProvider), а не все сразу. А так, понятно, что DAO в любом случае будет быстрее, особенно на больших объёмах.
Only those users with full accounts are able to leave comments. Log in, please.

Information

Founded
Location
Россия
Website
autoweboffice.com
Employees
11–30 employees
Registered