1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Prisma で AWS Aurora/MySQL がフェイルオーバーした時の処理

Posted at

Failover 時の Prisma の問題

DB に問題が発生した際に Writer instance と Reader instance がパチっと切り替わる。
切り替わった後、書き込めないエラーが発生した。

PrismaClientUnknownRequestError:
Error occurred during query execution:

ConnectorError(ConnectorError {
  user_facing_error: None,
  kind: QueryError(Server(ServerError {
    code: 1792,
    message "Cannot execute statement in a READ ONLY transaction.",
    state: "25006"
  }))
})

こんなようなエラーが出る。
API などの基本コネクションを貼り続けるようなアプリケーションでは、DB 側で Failover が行われると、コネクションは、元 Writer instance・新 Reader instance に繋がりっぱなしで、書き込みエラーが発生する。Prisma は自動でコネクションを貼り直してくれたりしない模様。

Failover を検知し、disconnect して上げる必要がある。

ローカルで問題を再現する

Amazon Aurora DB クラスターがフェイルオーバーした後、読み取り専用エラーが発生するのはなぜですか?

ちょっとエラーは違うんですが、read_only または innodb_read_only で MySQL を立ち上げれば、PrismaClientUnknownRequestError を発生させることができる。Docker の場合は、例えばこんな感じ。

version: '3.8'
services:
  db:
    image: mysql:5.7
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASS}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
      TZ: 'Asia/Tokyo'
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./mysql/sql:/docker-entrypoint-initdb.d
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --innodb_read_only
    container_name: ${CONTAINER_NAME_DB}

Prisma のエラーを判別する

Error message reference

Prisma のエラーは、5種類ある。

  • PrismaClientKnownRequestError
  • PrismaClientUnknownRequestError
  • PrismaClientRustPanicError
  • PrismaClientInitializationError
  • PrismaClientValidationError

全てのエラーがエラーコードを返してくれれば話しは単純だが、エラーコードを返してくれるのは、PrismaClientKnownRequestError, PrismaClientInitializationError だけだ。

Prisma で発生したエラーの Error instance のプロパティで、PrismaClientUnknownRequestError が取得できればそれが良いが、出来ない。

という訳で、以下のように判別する

if (err instanceof Prisma.PrismaClientUnknownRequestError) {
  prisma.$disconnect();
}

他のエラーも同様に認識できる。

5つのエラーのうち、PrismaClientRustPanicError は、コネクションを貼り直してもダメそうだが、それで復旧できるならラッキーくらいでこれも díconnect しておく。PrismaClientUnknownRequestError は、エラーメッセージから、MySQL のエラーコード等で絞り込んであげた方が、本当は良いが、UnknownError が何通りあるか分からないし、今回は disconnect するだけなので、ちょっと乱暴だが、全部 disconnect する。エラーメッセージは一見 object のようで string なので、扱いやすくはないし。

PrismaClientKnownRequestError, PrismaClientInitializationError のうち、P1XXX, P5XXX のコードのエラーは、そもそもコネクションできていなさそうなので、disconnect する必要があるのか分からないが、念の為。

なお、PrismaClientInitializationError は、MySQL を立ち上げた状態で、API を起動し、MySQL を落とすことで再現できる。
再現してみると、errorCode: undefined …… エラーコードなし……………

そんなような事に留意して、処理を書けば良い。
都度切断すれば、この問題は起きないが、API のように都度切断する必要がないものは、パフォーマンスが低下するので、このような処理を追加する必要がある。

落とし穴

この処理を先に実装してしまうと、発見が遅れそうな問題が実はひとつある。
Amazon Aurora Global Database などの、Secondary cluster で、write forwarding を使っているような場合。
Secondary cluster には、Read instance しかないので、エラーとしては、前述の UnknownError になるはずだが、disconnect しても意味がない。

この場合は、MySQL の変数として、aurora_replica_read_consistency を設定してやる必要があるが、Prisma の設定で、

DATABASE_URL=mysql://${user}:${password}@${host}:3306/${dbname}?aurora_replica_read_consistency=session

などとして、設定することができない。
どこでどう設定するのが正解なのか分からないが、どこかで、

await prisma.$executeRaw`set aurora_replica_read_consistency = 'session';`
  .catch((err) => handlePrismaError(err));

こんなようなことをしてあげる必要がある。もっとスマートな方法があったら、誰か教えてほしい。

おわり。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?