2012-02-24 6 views
3

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

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

Как я уже сказал, мне интересно, что я делаю что-то неправильно (например, партии для фиксации слишком велики?), Слишком много внешних ключей? ...), или я должен просто уйти от моделей Django для эту функцию и напрямую использовать DB 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() 
+0

Посмотрите на этот недавний ответ, который может иметь полезные полезные советы. http://stackoverflow.com/questions/9407442/optimise-postgresql-for-fast-testing/9407940#comment11914305_9407940 –

+0

Вторая часть вопроса была разделена на [следующий вопрос здесь] (http: // stackoverflow. com/q/9447506/939860) –

ответ

3

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

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

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

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

Или, если вы хотите вставить/обновить несколько строк:

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

SET LOCAL temp_buffers='128MB'; 

Представление в памяти занимает несколько больше места, чем представление данных в формате .disc. Итак, для 100-мегабайтного JSON-файла .. минус накладные расходы 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 (должны принимать секунды, а не часов):

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

Оттуда INSERT/UPDATE с простым старым SQL. Как вы планируете сложный запрос, вы даже можете добавить индекс или два на временную таблицу и запустить 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; 

Или он автоматически упал в го конец сеанса.

+0

Спасибо, 'COPY' кажется определенно интересным! Что касается вставки одной строки за раз, я бы подумал, что '@ transaction.commit_manually' будет заботиться о пакетной вставке при вызове' transaction.commit() '. В любом случае исходные файлы JSON действительно доступны для сервера, и я могу легко преобразовать их в CSV, чтобы я мог использовать его. Howerver, прочитав документацию 'COPY', я не знаю, как я могу использовать' COPY' и простой SQL для репликации функциональности того, что я делаю в цикле, в частности, добавляя значение внешнего ключа для 'template 'в таблицу «перевод». –

+0

Я мог бы подумать: 1) импортировать все данные во временную таблицу с помощью 'COPY' 2). Используя отдельный запрос SQL для копирования данных, не связанных с внешним ключом, из таблицы temp в основную таблицу« перевод », и, находясь на нем, узнайте внешний ключ «template» из «sourcepackage», «template_name», «translation_domain» в таблице temp и напишите соответствующий объект «template» в основную таблицу. Я все еще не уверен, как написать SQL-запрос на 2), но звучит ли это как разумный подход? –

+0

Да, конечно. Должно быть возможным в SQL-заявлении с JOIN. Я добавил дополнительную информацию для оптимизации производительности. –

Смежные вопросы