TypeScript | TypeORM で Replication を指定して DB に接続する
はじめに
AWS Aurora PostgreSQL のリードレプリカを有効活用できるように、
TypeORM のデータソースで Replication を指定してみたいと思います。
Replication setup を試す
ドキュメントを参考に、検証していきます。
データベースの作成
事前に以下のデータベースを作成しておきます。
-
test_db
: ライターインスタンスを想定 -
test_db_ro
: リーダーインスタンスを想定
プロジェクトを作成する
Quick Start を参考に、検証用のプロジェクトを作成します。
まずは、以下のコマンドを実行します。
npx typeorm init --name TypeormProject --database postgres
実行すると、以下のような構成でプロジェクトが作成されます。
% tree TypeormProject/ -I node_modules
TypeormProject/
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── data-source.ts
│ ├── entity
│ │ └── User.ts
│ ├── index.ts
│ └── migration
└── tsconfig.json
import { AppDataSource } from "./data-source"
import { User } from "./entity/User"
AppDataSource.initialize().then(async () => {
console.log("Inserting a new user into the database...")
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await AppDataSource.manager.save(user)
console.log("Saved a new user with id: " + user.id)
console.log("Loading users from the database...")
const users = await AppDataSource.manager.find(User)
console.log("Loaded users: ", users)
console.log("Here you can setup and run express / fastify / any other framework.")
}).catch(error => console.log(error))
データソースを変更する
test_db
に接続できるように、データソースを変更します。
import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "test_db",
synchronize: true,
logging: false,
entities: [User],
migrations: [],
subscribers: [],
})
npm start
を実行して、動作するか確認してみます。
% npm start
> TypeormProject2@0.0.1 start
> ts-node src/index.ts
Inserting a new user into the database...
Saved a new user with id: 1
Loading users from the database...
Loaded users: [ User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 } ]
Here you can setup and run express / fastify / any other framework.
正常に動作することが確認できました。
replication を指定する
正常に動作することが分かったので、
data-source.ts を変更し、replication 設定を加えます。
master
と slaves
を、それぞれ指定すれば OK です。
上述した通り、以下のような構成を想定しています。
-
test_db
: ライターインスタンスを想定 -
test_db_ro
: リーダーインスタンスを想定
import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"
export const AppDataSource = new DataSource({
type: "postgres",
- host: "localhost",
- port: 5432,
- username: "postgres",
- password: "postgres",
- database: "test_db",
+ replication: {
+ master: {
+ host: "localhost",
+ port: 5432,
+ username: "postgres",
+ password: "postgres",
+ database: "test_db",
+ },
+ slaves: [{
+ host: "localhost",
+ port: 5432,
+ username: "postgres",
+ password: "postgres",
+ database: "test_db_ro",
+ }],
+ },
synchronize: true,
logging: false,
entities: [User],
migrations: [],
subscribers: [],
})
この状態で npm start
を実行すると、エラーが発生します。
% npm start
> TypeormProject2@0.0.1 start
> ts-node src/index.ts
Inserting a new user into the database...
Saved a new user with id: 2
Loading users from the database...
QueryFailedError: relation "user" does not exist
Saved a new user with id: 2
というログが出力されたこと、そして QueryFailedError: relation "user" does not exist
というログが出力されたことから、INSERT は master に、SELECT は slave に接続されたことが予想されます。
ここで、test_db
を復元して test_db_ro
を再作成し接続してみます。
% npm start
> TypeormProject@0.0.1 start
> ts-node src/index.ts
Inserting a new user into the database...
Saved a new user with id: 3
Loading users from the database...
Loaded users: [
User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 2, firstName: 'Timber', lastName: 'Saw', age: 25 }
]
Here you can setup and run express / fastify / any other framework.
test_db_ro
には復元した時点のデータが 2 件なので、
id: 3
のデータがないことが分かります。
今回は 同期していない 異なるデータベースなので、このような結果になりますが、
Aurora のリードレプリカであれば、期待した結果が得られるはずです。
これで読み出しの負荷をリードレプリカに逃がすことで、
データベースサーバーの負荷を軽減できるでしょう🙌🎉
自動接続の仕様
All schema update and write operations are performed using master server. All simple queries performed by find methods or select query builder are using a random slave instance. All queries performed by query method are performed using the master instance.
スキーマの更新と書き込み操作はすべてマスター・サーバを使用して実行される。find メソッドや select クエリビルダで実行される単純なクエリはすべて、ランダムなスレーブインスタンスを使用します。クエリメソッドで実行されるすべてのクエリは、マスタインスタンスを使用して実行されます。
Multiple data sources, databases, schemas and replication setup
自動で切り替わるのは非常に便利ですね👀✨
明示的に master/slave を指定する
上記コードでは、自動で接続先が切り替わったことが分かると思いますが、
もちろん、明示的に接続先を指定することも可能です。
import { AppDataSource } from "./data-source"
import { User } from "./entity/User"
AppDataSource.initialize().then(async () => {
console.log("Inserting a new user into the database...")
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await AppDataSource.manager.save(user)
console.log("Saved a new user with id: " + user.id)
console.log("Loading users from the database...")
// master に接続する
// ライターインスタンスへの接続を想定
const masterQueryRunner = AppDataSource.createQueryRunner("master")
try {
const usersFromMaster = await AppDataSource
.createQueryBuilder(User, "user", masterQueryRunner)
.setQueryRunner(masterQueryRunner)
.getMany()
console.log("Loaded users from master: ", usersFromMaster)
} finally {
// 必ず release する
await masterQueryRunner.release()
}
// slave に接続する
// リーダーインスタンスへの接続を想定
const slaveQueryRunner = AppDataSource.createQueryRunner("slave")
try {
const usersFromSlave = await AppDataSource
.createQueryBuilder(User, "user", slaveQueryRunner)
.setQueryRunner(slaveQueryRunner)
.getMany()
console.log("Loaded users from slave: ", usersFromSlave)
} finally {
// 必ず release する
await slaveQueryRunner.release()
}
console.log("Here you can setup and run express / fastify / any other framework.")
}).catch(error => console.log(error))
master が 4 件で、slave が 2 件という期待通りの結果が得られました。
% npm start
> TypeormProject@0.0.1 start
> ts-node src/index.ts
Inserting a new user into the database...
Saved a new user with id: 4
Loading users from the database...
Loaded users from master: [
User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 2, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 3, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 4, firstName: 'Timber', lastName: 'Saw', age: 25 }
]
Loaded users from slave: [
User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 2, firstName: 'Timber', lastName: 'Saw', age: 25 }
]
まとめ
リーダーインスタンスとして見立てたデータベースを使って、
TypeORM のデータソースで Replication を指定できることが確認できました。
Replication を活用し、データベースサーバーの負荷を分散させていきましょう🥳🥳
注意事項
再接続できない問題があるようです。
必要に応じて対策するようにしましょう。
Discussion