Neon Postgres 無料枠の region 変更（とその経緯）

# はじめに

[Neon](https://neon.tech/)という Serverless な Postgres を提供しているサービスのリージョンを変更することがあったので、その手順を備忘録として残しておきます。
移行自体は macOS で進めていきます。

一応、以下のドキュメントにもリージョンの変更手順は書かれているのですが、無料プランではプロジェクトを同時に1つしか持てないため、危ない橋を渡る必要があります。
- [Move project data to a new region | Regions - Neon Docs](https://neon.tech/docs/introduction/regions#move-project-data-to-a-new-region)

また、経緯として個人開発の技術構成とその制約に基づいた元のリージョン構成を見て、さらに新しいリージョン構成とのラウンドトリップタイム（RTT）の概算を出し、簡単な比較を行っています。
こちらだけ見たい方は、 [↓ おまけ： リージョン変更の経緯](#おまけ-リージョン変更の経緯)をご覧ください。

# おおまかな移行手順

以下の手順で進めていきます。

1. 各種ツールのインストール
2. 現在のデータをすべて export 
3. (optional) ローカルで確認
4. プロジェクト削除・作成
5.  export したデータを import & 確認

## 1. 各種ツールのインストール

import/export （および確認）のために以下のツールを Mac にインストールしていきます。

```bash
$ brew install libpq
$ brew install postgresql@16 # (optional)
$ echo 'export PATH="/usr/local/opt/libpq/bin:$PATH"' >> ~/.zshrc
$ souce ~/.zshrc
```

`psql` コマンドが動くことを確認します。

```bash
$ psql -V
psql (PostgreSQL) 16.2
```

## 2. 現在のデータをすべて export 

Neon コンソール画面にて Project Dashboard の Connection Details 内にあるテキストエリア上のプルダウンから `psql` を選択しその値をコピーしておきます。

- 例： `postgresql://user:pass@example.ap-southeast-1.aws.neon.tech/example?sslmode=require`

:::note warn
このとき、テキストエリア内のテキストを選択してコピーするのではなく、テキストエリア右下のコピーアイコンをクリックしてコピーしてください。
そうしないと、パスワード部分がマスクされたままになっており、接続確認時にエラーが出てしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3676724/bf4b2e43-e856-88d0-1fb8-eb4436f4766d.png)
:::


そちらをターミナルに貼り付けて接続できることを確認します。

```
$ psql 'postgresql://user:pass@example.ap-southeast-1.aws.neon.tech/example?sslmode=require'
psql (16.2)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

example=>
```

接続が確認できたら、 `pg_dump` コマンドで実際に export をしていきます。

```
$ pg_dump 'postgresql://user:pass@example.ap-southeast-1.aws.neon.tech/example?sslmode=require' > export.sql
```

export できたら中身を `cat` コマンドやお使いのエディタなどで一応確認します。

```sql
--
-- PostgreSQL database dump
--

-- Dumped from database version 16.2
-- Dumped by pg_dump version 16.2

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;

...
```

## 3. (optional) ローカルで確認

こちらは念の為にローカルで動くかを確認したいひと向けです。

はじめにローカルで postgres を立ち上げます。
docker などを使用してもよいですが、今回は簡便化のため `brew` コマンドで立ち上げて中に入ります。

```bash
$ brew services start postgresql@16
$ psql
```

中に入れたら、DBとユーザーの作成をします。

```sql
-- 任意のデータベース名を指定
create database "example";
-- 任意のユーザー名・パスワードを指定
create user user with encrypted password 'pass';
exit
```

作成できたら、以下のコマンドで作成した DB にデータを入れ込みます。

```bash
$ psql example < export.sql
```

この時点で開発中のアプリの DB 向き先をローカルに向けて動作確認します。

## 4. プロジェクト削除・作成

データの export （とローカルでの動作確認）が取れたら、プロジェクトを削除していきます。
プロジェクトの削除自体は Neon コンソールの Project settings -> Delete から「Delete project」ボタンクリックでできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3676724/6743cabb-936d-d892-bac5-9cb895adb475.png)

削除できたら、 Neon の UI の指示にしたがって再度プロジェクトを作成します。
このときに、変更先のリージョンを選んでください。

## 5. export したデータを import & 確認

作成できたら、ステップ 2 と同じ手順で `psql` を選択して表示される値をコピーします。
コピーした値をそのままターミナルに貼り付けて以下のようなコマンドで `export.sql` をリダイレクトで流し込んであげます。

```bash
$ psql 'postgresql://user:pass@example.ap-southeast-1.aws.neon.tech/example?sslmode=require' < export.sql
```

流し込めたら、開発中のアプリのDB向き先を新しい Neon の DB に向けてあげて確認すれば完了です。

# おまけ： リージョン変更の経緯

現在筆者が作っている個人開発プロダクトでは以下の技術構成を取っています。

- フロントエンド： Next.js (Vercel)
- バックエンド： NestJS (Railway)
- データベース： Postgres (Neon)

しかし、 Vercel は東京(ap-northeast-1)、 Railway はオレゴン(us-west-2)、 Neon はシンガポール(ap-southeast-1)となっており、 RTT が非常に長くなってしまっていました。

## そもそもなんでこんなリージョンバラバラなんだよ！
1. ローカル開発
    - 「まずは DB は Neon にデプロイするか〜」
    - 「東京リージョンはないから一番近いシンガポールリージョンにしとこ...」
2. フロント・バックエンドのデプロイ
    - 「フロントエンドは手っ取り早く Vercel に乗せるとして、東京リージョンがあるな、、、よし！」
    - 「バックエンドは...前に使ったことある Railway が良さそうだからコレにしよう！」
    - 「あれ、、、リージョンはオレゴン固定か、、、まあいいか」
3. 実際に自分で使ってみる
    - 「めちゃくちゃ重い...」

というわけで、行き当たりばったりにリージョンを決めていったらこの結果になりました。

## じゃあどうする？

個人開発サービスは日本からのアクセスしか想定していないので、できれば東京リージョンに全部まとめたいなあ、と思っていました。
しかし、 Neon は上述のとおり東京リージョンがなく、さらに Railway はそもそも Pro プランでしかリージョンを選ぶことができず、デフォルトでオレゴン固定になってしまいます。

https://neon.tech/docs/introduction/regions

https://docs.railway.app/reference/regions

というわけで一旦すべてのサービスをオレゴンに集結させることになりました。

## 結果どうなった？

体感でいうと1秒ぐらい速くなった気がします。（ちゃんとデータとして取っていないのでなんとも言えないですが...）

せっかくなので、以下の記事を参考にリージョンを概算してみます。

||東京(ap-northeast-1)|オレゴン(us-west-2)|シンガポール(ap-southeast-1)|
|---|:---:|:---:|:---:|
|東京(ap-northeast-1)|1.76ms|108.51ms|73.38ms|
|オレゴン(us-west-2)|98.61ms|4.69ms|216.69ms|
|シンガポール(ap-southeast-1)|72.44ms|159.89ms|1.84ms|

https://dev.classmethod.jp/articles/monitor-aws-region-network-latency/

ということで大体のリージョン間のレイテンシがわかったので、次にサービスのリクエストの流れを見てみます。

```mermaid
sequenceDiagram
    User->>Vercel: Webページリクエスト
    Vercel->>Railway: APIリクエスト
    Railway->>Neon: DBリクエスト
    Neon->>Railway: DBレスポンス
    Railway->>Vercel: APIレスポンス
    Vercel->>User: Webページレスポンス
```

これに元のリージョンでのレイテンシを当てはめて見てみます。
ココでは東京を apne1 、 オレゴンを usw2 、 シンガポールを apse1 とします。

```mermaid
sequenceDiagram
    User(apne1)->>Vercel(apne1): Webページリクエスト(1.76ms)
    Vercel(apne1)->>Railway(usw2): APIリクエスト(108.51ms)
    Railway(usw2)->>Neon(apse1): DBリクエスト(216.69ms)
    Neon(apse1)->>Railway(usw2): DBレスポンス(159.89ms)
    Railway(usw2)->>Vercel(apne1): APIレスポンス(98.61ms)
    Vercel(apne1)->>User(apne1): Webページレスポンス(1.76ms)
```

次にすべてを集結させたリージョンに当てはめてみます。

```mermaid
sequenceDiagram
    User(apne1)->>Vercel(usw2): Webページリクエスト(108.51ms)
    Vercel(usw2)->>Railway(usw2): APIリクエスト(4.69ms)
    Railway(usw2)->>Neon(usw2): DBリクエスト(4.69ms)
    Neon(usw2)->>Railway(usw2): DBレスポンス(4.69ms)
    Railway(usw2)->>Vercel(usw2): APIレスポンス(4.69ms)
    Vercel(usw2)->>User(apne1): Webページレスポンス(98.61ms)
```

というわけで、これを合計して比較してみると...

- 元リージョン構成： 約 587.22 ms
- 新リージョン構成： 約 225.88 ms

倍近くRTTを削減できていることがわかります。

元の RTT 値自体が各リージョンの Lambda -> 各リージョンの DynamoDB ping endpoint の RTT を取得しているらしいです。

https://github.com/mda590/cloudping.co?tab=readme-ov-file

今回の筆者のサービスでいうと各サービス間のレイテンシはもっと遅いはずです。
したがって、単純計算はできませんがおよそ体感の「1秒ぐらい速くなった」のは妥当な気もします。

# まとめ

複数のサービスを組み合わせるときは、よりいっそうリージョンを意識しよう！！！（自戒）