[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

DjangoをsqliteからPostgreSQLに切り替えた(dumpdata/loaddata)

デフォルトのsqlite3をバックエンドDBにしたまま勢い良く作り始めたら、気がついたら結構データがたまって 動きが鈍くなったアプリがありました。

基本PostgreSQLが好きなのでSQLiteからSQL引っこ抜いてPostgreSQLにINSERTしないといけないと思っていたら Djangomanage.py dumpdatamanage.py loaddataで対応できそうだったので試してみました。

環境

移行前のsettings.py

なんとなくDB名だけは変えていてhh.dbという名前で作っていました。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'hh.db'),
    }
}

データを引っこ抜く

dumpdataコマンドで現在のモデルのデータを抜けるようです。基本JSONみたいですね。--indentで人が読みやすい形で出るので見てみます。

(django110) > python manage.py dumpdata --indent 4|head -n 20
[
{
    "model": "qq.category",
    "pk": 1,
    "fields": {
        "category_name": "xxxxxx",
        "color": "#FF4444"
    }
},
{
    "model": "qq.category",
    "pk": 2,
    "fields": {
        "category_name": "xxxxx",
        "color": null
    }
},
{
    "model": "qq.category",
    "pk": 3,
:

まぁloadする時にインデントはいらないのでインデントを付けずにファイルに書き出します。

(django110) > python manage.py dumpdata > dump.json

DBの設定変更

loadする前にsettings.pyを変更し新しいDBを見るようにします。

(django110) > diff settings.py settings_before.py
81,86c81,82
<         'ENGINE': 'django.db.backends.postgresql_psycopg2',
<         'NAME': 'djangodb',
<         'USER': 'p961',
<         'PASSWORD': '',
<         'HOST': '192.168.99.155',
<         'PORT': '9601'
---
>         'ENGINE': 'django.db.backends.sqlite3',
>         'NAME': os.path.join(BASE_DIR, 'hh.db'),
88a85
>

データを流し込む

準備ができたのでloaddataでデータを流し込みます。

(django110) > python manage.py loaddata dump.json
Traceback (most recent call last):
:
:
  File "C:\Program Files\Anaconda2\envs\django110\lib\site-packages\django\db\backends\utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: Problem installing fixture 'C:\ProgramData\gitrepos\XXXXX\XXXXXXX\dump.json': Could not load qq.Category(pk=1): relation "qq_category" does not exist
LINE 1: UPDATE "qq_category" SET "category_name" = 'XXXX', "color"...
               ^

relation "qq_category" does not existなので表がないって怒られました。先にmigrateしないとだめですね。

(django110) > python manage.py migrate

気を取り直してもう一度

(django110) > python manage.py loaddata dump.json
:
:
  File "C:\Program Files\Anaconda2\envs\django110\lib\site-packages\django\db\utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "C:\Program Files\Anaconda2\envs\django110\lib\site-packages\django\db\backends\utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: Problem installing fixture 'C:\ProgramData\gitrepos\XXXXXX\XXXXXX\dump.json': Could not load contenttypes.ContentType(pk=1): duplicate key value violates unique constraint "django_content_type_app_label_76bd3d3b_uniq"
DETAIL:  Key (app_label, model)=(admin, logentry) already exists.

また怒られました。。。duplicate key value violatesなのでユニークキー制約(django_content_type_app_label_76bd3d3b_uniq)で違反ですね。
app_label, modelのセットについている制約みたいです。django_content_typeテーブルはこんな定義でした。

djangodb=# \d django_content_type
                                  Table "public.django_content_type"
  Column   |          Type          |                            Modifiers
-----------+------------------------+------------------------------------------------------------------
 id        | integer                | not null default nextval('django_content_type_id_seq'::regclass)
 app_label | character varying(100) | not null
 model     | character varying(100) | not nullやま
Indexes:
    "django_content_type_pkey" PRIMARY KEY, btree (id)
    "django_content_type_app_label_76bd3d3b_uniq" UNIQUE CONSTRAINT, btree (app_label, model)
Referenced by:
    TABLE "auth_permission" CONSTRAINT "auth_permiss_content_type_id_2f476e4b_fk_django_content_type_id" FOREIGN KEY (content_type_id) REFERENCES django_content_type(id) DEFERRABLE INITIALLY DEFERRED
    TABLE "django_admin_log" CONSTRAINT "django_admin_content_type_id_c4bce8eb_fk_django_content_type_id" FOREIGN KEY (content_type_id) REFERENCES django_content_type(id) DEFERRABLE INITIALLY DEFERRED

PostgreSQL側でlog_min_duration=0をいれてSQLを眺めたところ以下のUPDATEが失敗しているようです。

ERROR:  duplicate key value violates unique constraint "django_content_type_app_label_76bd3d3b_uniq"
DETAIL:  Key (app_label, model)=(admin, logentry) already exists.
STATEMENT:  UPDATE "django_content_type" SET "app_label" = 'admin', "model" = 'logentry' WHERE "django_content_type"."id" = 1

manage.py migrateした直後にdjango_content_typeを見てみたところ既に(admin, logentry)のエントリがありました。

djangodb=# select * from "django_content_type";
 id |  app_label   |          model
----+--------------+--------------------------
  1 | qq           | category
  2 | qq           | message
  3 | qq           | multianswerset
  4 | qq           | question
  5 | qq           | answerchoose
  6 | qq           | testbase
  7 | qq           | multianswersettargettest
  8 | qq           | testcategorylist
  9 | qq           | answerformultichoose
 10 | qq           | answer
 11 | qq           | answerset
 12 | qq           | answerformulti
 13 | qq           | questionchoice
 14 | admin        | logentry
 15 | auth         | permission
 16 | auth         | user
 17 | auth         | group
 18 | contenttypes | contenttype
 19 | sessions     | session

なぜこんなことに・・・と探していると

stackoverflow.com

どうやらdumpdata時点でいくつかexcludeしないと行けない模様

(django110) > python manage.py dumpdata --exclude auth.permission --exclude contenttypes > dump.json

で再度migrateしてからloaddataしました

(django110) > python manage.py loaddata dump.json
Installed 563 object(s) from 1 fixture(s)

無事にいきました(問題がある表を抜いたので当たり前な気がしますが) そのまま起動して確認した感じ無事に動いているようです。

(django110) > python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
September 06, 2017 - 22:31:36
Django version 1.10, using settings 'HyakuMonHyakutou.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

これで無事にPostgreSQLに切り替えることができました。