reapersuper14
@reapersuper14
Python-программист-студент. Учусь делать круто.

Оптимизация objects.all() для огромной БД. Как получить все и не зависнуть на N минут?

Доброго времени суток, друзья.

Есть база данных PostgreSQL с двумя таблицами. В первой - около 3 миллионов записей (ссылки в интернет). Так уж вышло, что для получения второй таблицы, нужно пройтись по всем строкам из первой таблицы, взять ссылку, сделать некие манипуляции в интернете и записать результат (для каждой записи в первой таблице соответственно 200+ записей во второй).

Суть проблемы:
Самый очевидный подход:
for i in Item.objects.all():
    doSomething(i)

На тестовых данных в 10 тысяч записей этот подход работал на ура - данные очень быстро оказывались в моих руках, но на реальных данных компьютер просто подвисает на неопределенный срок и мне не остается ничего, кроме нажатия на кнопку reset.

Читал на Хабре, что этот подход абсолютно неверный, т.к. он создает N+1 запросов к базе данных.
Исправил вот так:
items = list(Item.objects.all())
for i in items:
    doSomething(i)

Но поведение компьютера не изменилось - пришлось опять ресетить.

Прошу, подскажите, пожалуйста, выход из ситуации. Возможно, можно получать данные из БД пачками меньших размеров (придется переписывать много кода, если это - единственный вариант)? Или, возможно, стоит попробовать вручную составить запрос к БД, не пользуясь Django ORM?

Возможно, я что-то кардинально делаю не так, но мне необходимо хранить все эти данные на сервере, чтобы пользователи имели к ним максимально быстрый доступ.
  • Вопрос задан
  • 715 просмотров
Решения вопроса 1
@marazmiki
Укротитель питонов
При итерировании кверисет целиком загружается в память, отсюда и проблема. Решение, которое предложил Александр Втюрин, хоть и несколько топорное, будет работать: идея там верная. Несколько лет тому эта проблема стояла очень остро, поэтому даже появился широко известный в узких кругах Сниппет #1949, сделанный именно на этом принципе.

Но начиная с Django версии, если не ошибаюсь, 1.4, появилось штатное средство, предназначенное для аналогичных целей — метод iterator() у кверисета.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
angru
@angru
есть подозрение, что дело не в .all(), а в doSomething(3кк раз залезть в инет и сохранить что-то в базу). для проверки можете выполнить такой код:
for i, item in enumerate(Item.objects.all()):
    x = i + i


если проблема не в .all(), то должно отработать довольно быстро. В этом случае займитесь оптимизацией doSomething, посмотрите в сторону celery, для джанги небось и батарейки есть, nope, уже из коробки поддерживается.
Ответ написан
reapersuper14
@reapersuper14 Автор вопроса
Python-программист-студент. Учусь делать круто.
Загуглил такое решение:
while True:
    items = Item.objects.filter(pk__gte=i*1000, pk__lt=(i+1)*1000)
    try:
        for j in items:
            doSomething(j)
    except Item.DoesNotExist:
        break

    i += 1


Как и всегда, стоило правильно составить поисковый запрос и готово.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы