【Unity】FirebaseのRealtime DatabaseにアクセスするとUnity Editorがcrashする

はじめに

こんにちは、SHOJIです。

UnityからFirebaseを使用した際にEditorが落ちる事象に遭遇しました。 根本解決はしていないのですが、とりあえずの対処方法だけ残しておきます。

Realtime Databaseを使用しようとするとEditorがcrashする

具体的にはFirebaseDatabase.DefaultInstance.RootReferenceをするとEditorがcrashします。

そのときのUnityとFirebase Unity SDKのバージョンがこちら。

  • Unity : 2021.3.1f1
  • Firebase Unity SDK : 9.0.1

エラーログを見てもcrashした以上の情報がなく、コードを見直してもおかしくないので何らかの不具合の線が濃いなと考えました。実際、異なるバージョンの組合せではありますが、Unity Editorがcrashするという報告自体はFirebaseのGithubに上がっていました。

なので、Firebaseを順番にダウングレードしたところ、9.0.0は変わらずエラーが出ますが、8.10.1だとエラーが出なくなりました。

どうも現時点(2022.6.23)の最新版のSDKに問題があるようです。 調べている過程で最新のFirebase SDKはトラブルが起きがちだという記事も見つけたので、もしかしたら少し前のバージョンを使った方がトラブルは起きにくいかもしれません。

以上です。

【Django】get_or_createしているのに一意制約違反が発生する

はじめに

こんにちは、SHOJIです。

Djangoで発生したエラー対応の備忘録です。

公式ドキュメントを読む大切さをあらためて教えてくれたので戒めとして記事にします。

get_or_createしても同時に複数のリクエストを受けると一意制約違反が発生

複数テーブルのデータを一括更新する処理があり、最初に管理テーブルから一意のIDを採番して、採番したIDをキーとして各テーブルのデータを更新するという流れにしていました。更新はデータの状態によって、既存データの更新新規データの追加の2パターンあるため、get_or_createを使って既存データの有無に関わらず一意のIDを取得して後続処理に進むようにしていました。

ユニットテストでは正しく動いているように見えましたが、同時アクセスのテストで一意制約違反(UNIQUE constraint failed)が発生しました。

一意制約違反ということは、get_or_createのcreateのタイミングできっとこういうことが起きているはずです。

  1. [Aプロセス] ID:Xが存在するか確認

  2. [Bプロセス] ID:Xが存在するか確認

  3. [Aプロセス] ID:Xが存在しないため追加

  4. [Bプロセス] ID:Xが存在しないため追加 ← すでにAプロセスがID:Xを追加しているので一意制約違反

get_or_createの意味がまるでない……。

これは何かおかしいぞと思って調べたところ、StackOverFlowにこのような投稿がありました。

stackoverflow.com

ベストアンサーを読むに、キー項目以外を指定すると質問の例のように重複した値であっても機能しないとのこと。 言われるがままにキー項目以外の指定をせずに再度テストを実行してみると、たしかに一意制約違反は出なくなりました。


エラーは出なくなりましたが、重複した値でも動作しない仕様というのがしっくり来ず、公式ドキュメントを見に行くと思いっきりWarningとして書かれていました笑

docs.djangoproject.com

そもそもの仕様として良い形であるかは議論の余地がありそうですが(なぜそうしているのか分からないので否定もできませんが)、get_or_createの仕様としては、キー項目のみを指定しない場合は複数行の追加が発生しうるとのことです。

おわりに

「はじめに」で書いたとおり、名前から効果を予測しやすいメソッドでもあっても、ちゃんと公式ドキュメントを読んで使わないといけないですね。


……と言いたいところですが、全てのドキュメントを一から十まで読む時間があれば苦労はないわけで。 チームで機能ごとのスペシャリストを立てるとか(DjangoはAさんが詳しく、Django REST FrameworkはBさんが詳しいといったような)、テストをしっかり行うことでカバーするとか、「ドキュメントを読め!」以外の対策を考えておかないとどこかで破綻するだろうなと思います。

【CentOS】Dockerを使用してApacheからDjangoを起動する(mod_wsgi版)

はじめに

こんにちは、SHOJIです。


前回、ApacheDjangoの構成をuWSGIを使用して構築しました。今回はmod-wsgiを使用してApacheからDjangoアプリケーションを起動します。


前回の記事はこちら。

deadline-driven-developer.hatenablog.com

ApacheコンテナにPython実行環境を追加する

作成するファイルはこちら。

.
├── Dockerfile
├── docker-compose.yml
├── httpd.conf
└── requirements.txt


  1. Dockerfileを作成する。 ApacheイメージをベースにPythonの実行環境をインストールする。

    FROM httpd:2.4
    RUN apt-get update
    RUN apt-get install -y python3
    RUN apt-get install -y python3-pip
    RUN apt-get install -y apache2-dev
    RUN apt-get install -y libpq-dev
    WORKDIR /code
    COPY requirements.txt /code/
    RUN pip install -r requirements.txt
    COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
    
  2. docker-compose.ymlを作成する。 データベースは前回同様PostgreSQLを使用する。

     version: "3.9"
    
     services:
       db:
         image: postgres
         volumes:
           - ./data/db:/var/lib/postgresql/data
         environment:
           - POSTGRES_DB=postgres
           - POSTGRES_USER=postgres
           - POSTGRES_PASSWORD=postgres
       web:
         build: .
         volumes:
           - ./app:/code
         ports:
           - "8080:80"
    
         environment:
           - POSTGRES_NAME=postgres
           - POSTGRES_USER=postgres
           - POSTGRES_PASSWORD=postgres
         depends_on:
           - db
    
  3. requirements.txtを作成する。ここにはインストールするPython packageを記載する。

    Django>=3.0,<4.0
    psycopg2>=2.8
    mod-wsgi>=4.9
    mod-wsgi-httpd>=2.4
    
  4. Apacheイメージからhttpd.confを取得し格納する。

    docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > ./httpd.conf
    

Djangoプロジェクトを生成する

  1. Djangoプロジェクト用のディレクトリとファイルを生成する。

    docker-compose run --rm web django-admin startproject composeexample .
    
  2. 生成したファイルの所有者を変更する。

    sudo chown -R $USER:$USER ./app
    
  3. composeexample/settings.pyを編集する。

    # settings.py
    import os
    [...]
    ALLOWED_HOSTS = ['*'] # アクセス可能なipアドレスを指定する。アスタリスクはセキュリティの面で非推奨。
    [...]
    DATABASES = {
      'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('POSTGRES_NAME'),
        'USER': os.environ.get('POSTGRES_USER'),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
        'HOST': 'db',
        'PORT': 5432,
      }
    }
    

mod-wsgiの設定を追加する

  1. httpd.confにLoadModuleWSGIPythonHomeを追加する。 これらの設定はコマンドで出力した結果をコピー&ペーストする。下記のコマンドは、出力結果の改行コードを変更して直接ファイルに書き込む。

    docker-compose run --rm web /usr/local/bin/mod_wsgi-express module-config | tr '\r\n' '\n' | tee -a ./httpd.conf
    
  2. httpd.confの末尾に連携するDjangoプロジェクトの定義を追加する。

    LoadModule wsgi_module "/usr/local/lib/python3.9/dist-packages/mod_wsgi/server/mod_wsgi-py39.cpython-39-x86_64-linux-g
    nu.so"
    WSGIPythonHome "/usr"
    
    # 以下、手動で追加
    WSGIScriptAlias / "/code/composeexample/wsgi.py"
    WSGIPythonPath "/code"
    
    <Directory /code/composeexample>
      <Files wsgi.py>
        Require all granted
      </Files>
    </Directory>
    # EOL
    
  3. バックグラウンドでサービスを起動する。

    docker-compose up -d
    
  4. ローカル環境で編集したhttpd.confをコンテナへコピーする。 $(docker-compose ps -q web)とすることで、docker-composeで定義したwebサービスのコンテナIDを取得することができる。

    docker cp ./httpd.conf $(docker-compose ps -q web):/usr/local/apache2/conf/httpd.conf
    

サービスを再起動し、mod-wsgiの設定を有効にする。

  1. ブラウザでhttp://[CentOS IP address]:8080にアクセスする。 「It works!」と表示されることを確認する。

  2. バックグラウンドで起動中のサービスを停止する。

    docker-compose stop
    
  3. サービスを起動する。これにより、mod-wsgiの設定が反映される。

    docker-compose up
    
  4. ブラウザでhttp://[CentOS IP address]:8080にアクセスする。
    Djangoの画面「The install worked successfully!」が表示されれば完了。

おわりに

今回はApacheDjangoの連携をmod-wsgiを使って行いました。


このやり方だと、ApacheコンテナとPythonコンテナが分かれないのでdockerを使うメリットが薄い感じがしますね。dockerというかApacheイメージを使うメリットが薄い、が正しいでしょうか。複数の役割を一つのコンテナに持たせてよいのなら、Apacheイメージではなく何らかのOSイメージをベースにした方が手っ取り早い気がします。


まあ、前回もPython環境にuWSGIインストールしているので同じといえば同じかもしれません。

【CentOS】Dockerを使用してApacheからDjangoを起動する

はじめに

こんにちは、SHOJIです。


今回は、前回作成したDjango & PostgreSQLの環境にApacheを追加します。Apacheは前回に引き続きDockerのコンテナとして追加します。


前回の記事はこちら。

deadline-driven-developer.hatenablog.com

Apacheコンテナを追加してDjangoと連携する

ディレクトリ構成を変更する

前回、このような構成だったディレクトリを

# 前回の構成(runコマンド実行前)
.
├── Dockerfile
├── docker-compose.yml
└── requirements.txt

# 前回の構成(コンテナ起動後)
.
├── Dockerfile
├── composeexample
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── data
│   └── db
│       └── ...
├── docker-compose.yml
├── manage.py
└── requirements.txt

次のように変更する。変更する理由は、Apache用のDockerfileを一つ追加したいのと、各種ファイルがどのイメージのために存在するのかを分かりやすくするため。

# 前回の構成(runコマンド実行前)
.
├── app
│   ├── Dockerfile (Python)
│   └── requirements.txt
├── db
├── docker-compose.yml
└── web
    ├── Dockerfile (Apache)
    └── httpd.conf

# 前回の構成(コンテナ起動後)
.
├── app
│   ├── Dockerfile
│   ├── composeexample
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── manage.py
│   └── requirements.txt
├── db
│   └── data
│       └── ...
├── docker-compose.yml
└── web
    ├── Dockerfile
    └── httpd.conf
  1. 前回の構成(runコマンド実行前)の状態にする。

  2. appディレクトリを作成する。

  3. Dockerfileとrequirements.txtをappディレクトリに移動する。

  4. dbディレクトリを作成する。

  5. webディレクトリを作成する。

    # 現在のディレクトリの状態
    .
    ├── app
    │   ├── Dockerfile (Python)
    │   └── requirements.txt
    ├── db
    ├── docker-compose.yml
    └── web
    

Python実行環境のインストール対象にuWSGIを追加する

  1. app/requirements.txtを編集する。

    Django>=3.0,<4.0
    psycopg2>=2.8
    uWSGI>=2.0.20
    

webディレクトリにApache用ファイルを作成する

  1. webディレクトリ内にDockerfileを作成する。

    このDockerfileをビルドすると、①ローカル環境のhttpd.confでコンテナ内のhttpd.confを上書き、が行われる。

    FROM httpd:2.4
    COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
    
  2. webディレクトリ内にApacheイメージから取得したhttpd.confを格納する。

    docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > ./web/httpd.conf
    
  3. web/httpd.confを編集する。

    Apacheで受けたアクセスをuWSGIへ受け渡すため、リバースプロキシの設定を有効にする。 ProxyPassにはコンテナを動かしているCentOSIPアドレスを設定する。今回はdocker-compose.ymlで定義したIPアドレスを使用するべく、環境変数を設定している。

    # httpd.conf
    [...]
    # コメントアウトを解除する
    LoadModule proxy_module modules/mod_proxy.so
    LoadModule proxy_http_module modules/mod_proxy_http.so
    [...]
    ProxyRequests Off
    ProxyPass / http://${IPADDRESS}:8000/
    ProxyPassReverse / http://${IPADDRESS}:8000/
    # EOL
    
    # 現在のディレクトリの状態
    .
    ├── app
    │   ├── Dockerfile (Python)
    │   └── requirements.txt
    ├── db
    ├── docker-compose.yml
    └── web
        ├── Dockerfile (Apache)
        └── httpd.conf
    

docker-compose.ymlの設定

  1. docker-compose.ymlを編集する。

    このdocker-composeをアップすると

    version: "3.9"
    services:
      db:
        image: postgres
        volumes:
          # - ./data/db:/var/lib/postgresql/data
          - ./db/data:/var/lib/postgresql/data
        environment:
          - POSTGRES_DB=postgres
          - POSTGRES_USER=postgres
          - POSTGRES_PASSWORD=postgres
      # web:
      app:
        # build: .
        build:
          context: ./app
          dockerfile: ./Dockerfile    
        command: uwsgi --http 0.0.0.0:8000 --chdir /code/ --wsgi-file ./composeexample/wsgi.py
        volumes:
          # - .:/code
          - ./app:/code
        ports:
          - "8000:8000"
        environment:
          - POSTGRES_NAME=postgres
          - POSTGRES_USER=postgres
          - POSTGRES_PASSWORD=postgres
        depends_on:
          - db
      # 以下、Apache用の設定を追加
      web:
        build:
          context: ./web
          dockerfile: ./Dockerfile
        ports:
          - "8080:80"
        environment:
          - IPADDRESS
        depends_on:
          - app
    

Djangoプロジェクトの生成とサービスの起動

  1. 環境変数「IPADDRESS」にCentOSIPアドレスを設定する。

    export IPADDRESS=***.***.***.***
    
  2. Djangoプロジェクト用のディレクトリとファイルを生成する。

    docker-compose run app django-admin startproject composeexample .
    
  3. 生成したファイルの所有者を変更する。

    sudo chown -R $USER:$USER ./app/composeexample
    
  4. composeexample/settings.pyを編集する。

    # settings.py
    import os
    [...]
    ALLOWED_HOSTS = ['*'] # アクセス可能なipアドレスを指定する。アスタリスクはセキュリティの面で非推奨。
    [...]
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.environ.get('POSTGRES_NAME'),
            'USER': os.environ.get('POSTGRES_USER'),
            'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
            'HOST': 'db',
            'PORT': 5432,
        }
    }
    
  5. サービスを起動する。

    docker-compose up
    
  6. ブラウザでhttp://[CentOS IP address]:8080にアクセスできれば完了。
    uWSGIも起動しているため、http://[CentOS IP address]:8000からもアクセスできる。この場合はApacheを介さないアクセスになる。

おわりに

今回はApacheコンテナを追加してDjangoと連携するところまで行いました。


ここからさらにDjango REST FrameworkとReactを導入してSPAの環境構築もやりたかったのですが、キリがいいので一旦終わりにしようと思います。また気持ちが乗ったタイミングで再開します。

【CentOS】CentOSでDockerを使用しDjango環境を構築する

はじめに

こんにちは、SHOJIです。


今回は、前回Hyper-Vに追加したCentOS7にDockerをインストールしてDjangoのイメージを動かします。 Dockerは公式ドキュメントがしっかりしているので、手順にしたがって順番に進めれば難しくありません。 セキュリティを考慮してベストプラクティスを追及すると難易度が上がりますが、今回はシンプルに動く環境を作ることを目指します。


前回の記事はこちら。

deadline-driven-developer.hatenablog.com

CentOS7にDockerをインストールする

Docker ドキュメントを参照するのが早いです。

docs.docker.com

# 古いDocker関連ソフトウェアの削除
sudo yum remove docker \
                docker-client \
                docker-client-latest \
                docker-common \
                docker-latest \
                docker-latest-logrotate \
                docker-logrotate \
                docker-engine
sudo yum install -y yum-utils
sudo yum-config-manager \
  --add-repo \
  https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io
# sudoなしでdockerコマンドを使えるようにする
sudo usermod -aG docker ${USER}

CentOS7にDocker Composeをインストールする

docs.docker.com

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Django & PostgreSQLのDockerイメージを動かす

docs.docker.com

  1. 空のディレクトリを作成する。

  2. Dockerfileを作成する。

    このDockerfileをビルドすると、①Pythonコンテナに/codeディレクトリを作成、②ローカル環境のrequirements.txtを/codeディレクトリにコピー、③requirements.txtに定義されたPython Packageのインストール、が行われる。

    # syntax=docker/dockerfile:1
    FROM python:3
    ENV PYTHONDONTWRITEBYTECODE=1
    ENV PYTHONUNBUFFERED=1
    WORKDIR /code
    COPY requirements.txt /code/
    RUN pip install -r requirements.txt
    COPY . /code/
    
  3. requirements.txtを作成する。

    「2」で作成したDockerfileでインストールするPython Packageを定義する。

    Django>=3.0,<4.0
    psycopg2>=2.8
    
  4. docker-compose.ymlを作成する。

    このdocker-composeをアップすると

    version: "3.9"
    services:
      db:
        image: postgres
        volumes:
          - ./data/db:/var/lib/postgresql/data
        environment:
          - POSTGRES_DB=postgres
          - POSTGRES_USER=postgres
          - POSTGRES_PASSWORD=postgres
      web:
        build: .
        command: python manage.py runserver 0.0.0.0:8000
        volumes:
          - .:/code
        ports:
          - "8000:8000"
        environment:
          - POSTGRES_NAME=postgres
          - POSTGRES_USER=postgres
          - POSTGRES_PASSWORD=postgres
        depends_on:
          - db
    
  5. 「4」で定義したwebサービスPythonコンテナ)上でDjangoのコマンドを実行する。これにより、PythonコンテナのcodeディレクトリにDjangoプロジェクト用のディレクトリとファイルが生成される。

    Pythonコンテナのcodeディレクトリはローカルの作業ディレクトリをバインドしているため、ローカルにもDjangoプロジェクト用のディレクトリとファイルが生成されるが、生成はあくまでPythonコンテナ側で行われる。

    docker-compose run web django-admin startproject composeexample .
    
  6. 生成したファイルの所有者を変更する。

    sudo chown -R $USER:$USER composeexample
    
  7. composeexample/settings.pyを編集する。 settings.pyはDjangoの設定ファイル。Djangoで使用するデータベースをデフォルトのSqlite3からPostgreSQLに変更する。

    # settings.py
    import os
    [...]
    ALLOWED_HOSTS = ['*'] # アクセス可能なipアドレスを指定する。アスタリスクはセキュリティの面で非推奨。
    [...]
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.environ.get('POSTGRES_NAME'),
            'USER': os.environ.get('POSTGRES_USER'),
            'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
            'HOST': 'db',
            'PORT': 5432,
        }
    }
    
  8. サービスを起動する。webのdepends_onにdbを指定しているため、起動はdb→webの順序になる。

    docker-compose up
    
  9. ブラウザでhttp://[CentOS IP address]:8000にアクセスできれば完了。

おわりに

今回はDjangoPostgreSQLのDockerイメージからコンテナを作成し、DjangoからPostgreSQLに接続するよう設定を追加しました。 次回はApacheコンテナを追加して、ApacheからDjangoアプリケーションを動かすようにしたいと思います。

【Windows】Windows Server 2016で動いていたPytestがWindows10で動かない

はじめに

こんにちは、SHOJIです。

Windows Server 2016で動いていたPytestがWindows10で動かなかったときの対応です。 今後、この二つの環境で何かを作ることはなさそうですが、トラブルの原因と解決方法は出来る限り残しておけという教えを受けているので残します。

ImportError: DLL load failed

Pytest実行時、Windows10環境で発生したエラーがこちらです。 (以前に発生したエラー画像から手打ちしているのでだいぶ短く抜粋してます)

from numba.core.typeconv import _typeconv
ImportError: DLL load failed

Numba モジュールが上手く読み込めていないようです。 いくつかのキーワードで検索すると、NumbaのIssueに同事象の報告が挙がっていました。

github.com

こちらはWindows7で発生したようですが、おそらく同じ原因でしょう。 会話を追っていくとNumbaのバージョンによって動く場合と動かない場合があるとの報告があります。

バージョンが0.47.0 や 0.48.0 であれば動くようですが、残念ながら僕の環境は他のライブラリとの兼ね合いで0.48.0に下げることはできない状態でした。

そのため、他のライブラリに影響しない最低バージョンに落としてから順番にバージョンアップをしていったところ、0.51.2のバージョンでエラーが解消されました。

おわりに

Numbaの今現在のGithubのスターが7.4k、オープンなIssueが1.3k、やっぱり有名ライブラリはすごいですね。 これだけの支持を受けて開発するのって想像できないほどのプレッシャーだと思うのですが、中の人たちがどのようにプレッシャーと折り合いをつけて開発を進めているのか気になります。

まあでも、OSSかそうでないかが違うだけで、ユーザ数の多いサービスの開発チームにはどこも相当プレッシャーかかってるから、そのあたりの対処の仕方は同じなのかな。不具合は仕組みで抑えるようにして、責任はチームで負って、なるべく個人の負荷を緩和するような体制でやっているんでしょうね。

【Apache】Apache&Djangoの構成のときのみアプリケーションが応答しなくなる

はじめに

こんにちは、SHOJIです。

Djangoで開発をしていたところ、Apacheと連携したときにだけアプリケーションが応答しなくなるトラブルが発生しました。本記事はその解決方法と原因です。

Apacheからmod_wsgi経由で実行すると応答しない

発生したのは Python manage.py runserver コマンドで起動すると正常にアクセスできるURLに、Apacheからmod_wsgi経由でアクセスすると応答が返ってこないという事象です。この手の障害は先駆者がいないと解決まで苦労することが多く、目にしたとき「うわ、嫌だな〜……」と思いました。

ですが、恐る恐るググってみると同じ事象に出会っている方がいました。けっこうメジャーなトラブルのようです。

serverfault.com

解決方法はシンプルで、Apache側の設定ファイルであるhttpd.confWSGIApplicationGroup %{GLOBAL}と設定すればいいとのこと。 指示通り設定するとたしかに動くようになりました。

WSGIApplicationGroupってなんですか?

こちらがmod_wsgiのWSGIApplicationGroupの説明です。 分かったような分からないような……。うーん、やっぱり分からない。

modwsgi.readthedocs.io

誰か分かりやすく説明していないかな?と探したら素晴らしい記事を見つけました。

tech-blog.monotaro.com

モノタロウさんのTech Blogなのですが、図解がめちゃくちゃ分かりやすいです。 読んでいて思いましたが、そもそも僕はmod_wsgiの動きを理解せずに使っていましたね。 前提を理解できていないのだから、WSGIApplicationGroupの説明を読んでもピンとこないのは当然でした。

おわりに

繰り返しになりますが、最後に紹介したモノタロウさんのTech Blog めちゃくちゃ分かりやすいですね。 会社自体は有名なのでもちろん知っていましたが、Tech Blogがここまで充実していることは知りませんでした。 他の記事も拝見しましたが、どれもクオリティがとても高いので時間を見つけて色々読ませていただこうと思います。