Оптимизация производительности записи базы данных Postgresql в Django? - PullRequest
4 голосов
/ 24 февраля 2012

У меня есть приложение Django 1.1, которое должно ежедневно импортировать данные из некоторых больших файлов JSON. Чтобы дать представление, один из этих файлов имеет размер более 100 Мб и содержит 90 тыс. Записей, которые импортируются в базу данных Postgresql.

Проблема, с которой я сталкиваюсь, заключается в том, что импорт данных занимает очень много времени, т. Е. Порядка нескольких часов. Я ожидал бы, что потребуется некоторое время, чтобы записать это количество записей в базу данных, но, конечно, не так долго, что заставляет меня думать, что я делаю что-то по своей сути неправильно. Я читал похожие вопросы об обмене стеками, и предлагаемые решения предлагают использовать декораторы transaction.commit_manually или transaction.commit_on_success для фиксации в пакетном режиме вместо каждого .save(), что я уже делаю.

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

Вот основные модели, с которыми я имею дело при импорте данных (для простоты я удалил некоторые поля в исходном коде)

class Template(models.Model):
    template_name = models.TextField(_("Name"), max_length=70)
    sourcepackage = models.TextField(_("Source package"), max_length=70)
    translation_domain = models.TextField(_("Domain"), max_length=70)
    total = models.IntegerField(_("Total"))
    enabled = models.BooleanField(_("Enabled"))
    priority = models.IntegerField(_("Priority"))
    release = models.ForeignKey(Release) 

class Translation(models.Model):
    release = models.ForeignKey(Release)
    template = models.ForeignKey(Template)
    language = models.ForeignKey(Language)
    translated = models.IntegerField(_("Translated"))

А вот фрагмент кода, который, кажется, требует много времени для завершения:

@transaction.commit_manually
def add_translations(translation_data, lp_translation):

    releases = Release.objects.all()

    # There are 5 releases
    for release in releases:

        # translation_data has about 90K entries
        # this is the part that takes a long time
        for lp_translation in translation_data:
            try:
                language = Language.objects.get(
                    code=lp_translation['language'])
            except Language.DoesNotExist:
                continue

            translation = Translation(
                template=Template.objects.get(
                            sourcepackage=lp_translation['sourcepackage'],
                            template_name=lp_translation['template_name'],
                            translation_domain=\
                                lp_translation['translation_domain'],
                            release=release),
                translated=lp_translation['translated'],
                language=language,
                release=release,
                )

            translation.save()

        # I realize I should commit every n entries
        transaction.commit()

        # I've also got another bit of code to fill in some data I'm
        # not getting from the json files

        # Add missing templates
        languages = Language.objects.filter(visible=True)
        languages_total = len(languages)

        for language in languages:
            templates = Template.objects.filter(release=release)

            for template in templates:
                try:
                    translation = Translation.objects.get(
                                    template=template,
                                    language=language,
                                    release=release)
                except Translation.DoesNotExist:
                    translation = Translation(template=template,
                                              language=language,
                                              release=release,
                                              translated=0,
                                              untranslated=0)
                    translation.save()

            transaction.commit()

1 Ответ

5 голосов
/ 24 февраля 2012

Работа с приложением и обработка каждой отдельной строки много медленная загрузка данных непосредственно на сервер. Даже с оптимизированным кодом. Кроме того, вставка / обновление по одной строке за раз намного медленнее, чем обработка всего за один раз.

Если файлы импорта доступны локально для сервера, вы можете использовать COPY. В противном случае вы можете использовать мета-команду \copy в стандартном интерфейсе psql. Вы упомянули JSON, чтобы это работало, вам необходимо преобразовать данные в подходящий плоский формат, такой как CSV.

Если вы просто хотите добавить новые строки в таблицу:

COPY tbl FROM '/absolute/path/to/file' FORMAT csv;

Или, если вы хотите ВСТАВИТЬ / ОБНОВИТЬ несколько строк:

Прежде всего: используйте достаточно оперативной памяти для temp_buffers (по крайней мере, временно, если можете), чтобы временная таблица не записывалась на диск. Имейте в виду, что это необходимо сделать перед доступом к любым временным таблицам в этом сеансе.

SET LOCAL temp_buffers='128MB';

Представление в памяти занимает несколько больше места, чем представление данных на диске. Таким образом, для файла JSON размером 100 МБ. Минус издержки JSON плюс некоторые издержки Postgres, 128 МБ может быть или не быть достаточным. Но вам не нужно угадывать, просто выполните тестовый прогон и измерьте его:

select pg_size_pretty(pg_total_relation_size('tmp_x'));

Создать временную таблицу:

CREATE TEMP TABLE tmp_x (id int, val_a int, val_b text);

Или, чтобы просто продублировать структуру существующей таблицы:

CREATE TEMP TABLE tmp_x AS SELECT * FROM tbl LIMIT 0;

Копирование значений (должно занять секунд , а не часов):

COPY tmp_x FROM '/absolute/path/to/file' FORMAT csv;

Оттуда INSERT / UPDATE с простым старым SQL. Поскольку вы планируете сложный запрос, вы можете даже добавить index или два к временной таблице и запустить ANALYZE:

ANALYZE tmp_x;

Например, чтобы обновить существующие строки, соответствующие id:

UPDATE tbl
SET    col_a = tmp_x.col_a
USING  tmp_x
WHERE  tbl.id = tmp_x.id;

Наконец, удалите временную таблицу:

DROP TABLE tmp_x;

Или он был автоматически удален в конце сеанса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...