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

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

```bash
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 クラスターがフェイルオーバーした後、読み取り専用エラーが発生するのはなぜですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/aurora-mysql-db-cluser-read-only-error/)

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

```yaml
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](https://www.prisma.io/docs/reference/api-reference/error-reference)

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

- PrismaClientKnownRequestError
- PrismaClientUnknownRequestError
- PrismaClientRustPanicError
- PrismaClientInitializationError
- PrismaClientValidationError

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

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

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

```typescript
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 の設定で、

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

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

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

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


おわり。