2012-02-24 7 views
3

私は毎日いくつかの大きなjsonファイルからデータをインポートする必要があるDjango 1.1アプリを持っています。アイデアを出すために、これらのファイルの1つは100 Mb以上で、Postgresqlデータベースにインポートされる90,000のエントリを持っています。PostgresqlデータベースのパフォーマンスをDjangoで最適化しますか?

私が抱えている問題は、データをインポートするのに実際には時間がかかり、つまり数時間かかることです。その数のエントリをデータベースに書き込むのにある程度の時間がかかることは予想されましたが、それほど長くはないので、私が本質的に間違ったことをしていると思います。私は同様のスタックエクスチェンジに関する質問を読んでおり、提案されている解決策は、私がすでにやっている.save()の代わりにtransaction.commit_manuallyまたはtransaction.commit_on_successのデコレータをバッチでコミットすることを提案しています。

私は何か間違っている(バッチが大きすぎる、外来キーが多すぎるなど...)か、私はちょうど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

質問の2番目の部分は[フォローアップの質問]に分割されました(http:// stackoverflow。 com/q/9447506/939860) –

答えて

3

アプリを通って行くと、すべての単一の行を処理して、直接データたくさん遅く積載されているサーバーに。最適化されたコードでさえも。また、一度に1行ずつ挿入/更新すると、一度にすべてを処理するよりも遅く、が多く、が多くなります。

インポートファイルがサーバーにローカルで使用できる場合は、COPYを使用できます。そうでなければ、標準インタフェースpsqlのメタコマンド\copyを使用できます。 JSONと言いますが、これが機能するには、データをCSVのような適切なフラット形式に変換する必要があります。

あなただけのテーブルに新しい行を追加したい場合は、次の

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

それとも、/ UPDATEいくつかの行を挿入する場合:

まずオフ:(少なくとも一時的temp_buffersのための使用に十分なRAM 、可能であれば)、tempテーブルをディスクに書き込む必要はありません。 セッションの一時テーブルにアクセスする前にこれを行う必要があることに注意してください。

SET LOCAL temp_buffers='128MB'; 

メモリ内の表示には、データのon.disc表示よりも多少のスペースが必要です。したがって、100 MBのJSONファイルでは、JSONオーバーヘッドにPostgresのオーバーヘッドを加えて128 MBで十分でないかもしれません。しかし、あなただけのテスト実行を行うと、それを測定、推測する必要はありません。

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

それとも、単に既存のテーブルの構造を複製する:

select pg_size_pretty(pg_total_relation_size('tmp_x')); 

は一時テーブルを作成します。

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

コピー値(、ない時間を取る必要があります)

そこからプレーンな古いSQLでINSERT/UPDATEを実行します。 idにマッチした、例えば

ANALYZE tmp_x; 

、既存の行を更新するために:あなたは、複雑なクエリを計画しているとして、あなたも一時テーブルの上にインデックスまたは2を追加し、ANALYZEを実行することをお勧めします

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

最後に、一時テーブルをドロップ:

DROP TABLE tmp_x; 

それとも目で自動的に下落していますeセッションの終了。

+0

ありがとうございます、「COPY」は間違いなく面白そうですね!一度に1つの行を挿入することについては、 'transaction.commit_manually'が' transaction.commit() 'を呼び出すときに挿入をバッチ処理すると考えていたでしょう。いずれにしても、ソースJSONファイルは実際にサーバーで使用でき、簡単にCSVファイルに変換できるため、使用することができます。 HOWERVERは、 'COPY'のドキュメントを読んだ後、私がループ内でやっていることの機能を複製するために' COPY'とプレーンなSQLをどうやって使うことができないのか、特に 'template 'を'翻訳 'テーブルに追加します。 –

+0

私は考えることができます:1)すべてのデータを 'COPY'で一時テーブルにインポートする2)別のSQLクエリを使用して、一時テーブルから主な '変換'テーブルに外部キー以外のデータをコピーし、それと同時に、 'sourcepackage'、 'template_name'、 'translation_domain'の値をtempテーブルで検索し、適切な 'template'オブジェクトをメインテーブルに書き込みます。私は2)のSQLクエリを書く方法はまだ分かりませんが、それは賢明なアプローチのようですか? –

+0

はい、そうです。 JOINを持つSQL文で可能である必要があります。パフォーマンスを最適化するための情報を追加しました。 –

関連する問題