type holyshared = Engineer<mixed>

技術的なことなど色々

TypeORMでPostgreSQLのコンネクションプールの状態を知る方法

TypeORMでは QueryRunner のプロパティからpgのPool インスタンスにアクセスすることができます。
pgのPool インスタンスからはプールの状態を確認できるプロパティがあります。

  • totalCount - プールにあるコネクションの総数
  • idleCount - コネクションプールにある空きコネクション数
  • waitingCount - 接続待ちのコネクション数

このプロパティをログに出力しておくことで、プールのサイズを調整する目安にすることができます。
QueryRunnerは Repository、EntityManagerなどから参照できるため、ログの出力特に困らないはずです。

import { DataSource } from "typeorm";
import { Pool } from "pg";

const dataSource = new DataSource({
  type: "postgres",
  host: "localhost",
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  poolSize: 10,
});

const connectionPool = async () => {
  await dataSource.initialize();

  const c1 = await dataSource.createQueryRunner();
  await c1.connect();

  const c2 = await dataSource.createQueryRunner();
  await c2.connect();

  // Dirverインターフェースからmasterプロパティが参照できないので型をanyにして扱う
  const driver = c2.connection.driver as any;
  const pool = driver.master as Pool;

  // pg.Poolのインスタンスからプールの使用状況を計算する
  // https://node-postgres.com/apis/pool
  console.info("totalCount", pool.totalCount);
  console.info("idleCount", pool.idleCount);
  console.info("waitingCount", pool.waitingCount);

  await c1.release();
  await c2.release();

  await dataSource.destroy();
}

connectionPool().then(() => {
  console.info("done");
}).catch(console.error);

おすすめはQueryRunnerをオブジェクトでラップして、コネクションプールに返すタイミングでログに出力する方法です。

オブジェクトでラップしたQueryRunnerのライフサイクルはDIコンテナなどで管理すると、リクエストのレスポンスを返すまでのコネクションを同一のコネクションの固定しやすくなり、利用できるコネクションの枯渇などを防ぐとことができますし、コネクションをプールに返し忘れることも減らすことができます。

class QueryRunnerWrapper {
  constructor(
    private queryRunner: QueryRunner,
    private logger: { info: (...messages: any[]) => void }
  ) {}

  async connect() {
    this.logger.info("[CONN] connect");
    return this.queryRunner.connect();
  }

  async release() {
    const driver = this.queryRunner.connection.driver as any;
    const pool = driver.master as Pool;

    this.logger.info("[CONN] release", 
      "totalCount", pool.totalCount,
      "idleCount", pool.idleCount,
      "waitingCount", pool.waitingCount
    );
    return this.queryRunner.release();
  }
}

2023年の靴の着用回数を集計した

昨年は3足手放したみたいですが、3足増えているの全体では増減はないぽいです。
引っ越しを考えたりすると、30足くらいに減らしたい。

また集計結果をMarkdown形式で出力できるように機能を追加したので、来年からは集計が楽になるはず。

以前から履いている靴

No. モデル ブランド 2023年までの着用回数 2023年の着用回数
1 SX 78C02 MoonStar 48 38
2 TimberLoop EK Utility Boot Timberland 35 25
3 Tanker Pro Nicks 35 23
4 SKIPTON George Cox 48 26
5 ROOTS LOOP II 8.5 Rolling dub trio 80 33
6 Classical Horsehide Mesh Sandals Dapper’s 101 42
7 BRIGHTON Loake 55 23
8 Ghost14 BROOCKS 97 28
9 Overstone Hi Derby Crown Northampton 83 25
10 Neon Blue 8 Eye Derby Boot SOLOVAIR 67 21
11 6Eye Astronaut Boot SOLOVAIR 71 23
12 Solar Wave Leather/Fabric Mid Hiker Boots Timberland 82 22
13 飛鳥ホールカット KOTOKA 86 24
14 LAW NPS shoes 79 23
15 Harry BROTHER BRIDGE 99 24
16 6inch Boots Reverse Black Odessa Chippewa 84 21
17 BOYS KATSUYA TOKUNAGA 85 22
18 GARRISON TRAIL GORE-TEX HIKING SHOES Timberland 102 32
19 M-43 SERVICE SHOES JOHN LOFGREN 92 22
20 BERLIN BROTHER BRIDGE 103 25
21 CONISTON Crockett&Jones 102 23
22 Morgan BROTHER BRIDGE 98 21
23 Spider Scotch Grain 98 22
24 Custom Jobmaster 38LTT Wesco 99 21
25 442 MILITARY CAP TOE OXFORD RAMSEY 131 23
26 DOBULE MONK SANDAL Tokyo Sandals 466 98
27 ROOTS Rolling dub trio 148 26
28 Norwogian Split Pistorelo 112 21
29 Irish Setter Redwing 134 25
30 OB-8593GT 栃木レザープレーンミッドブーツ SLOW WEAR LION 122 23
31 OB-8593G オイルドレザープレーンMIDブーツ SLOW WEAR LION 119 22
32 6inch Service Boots Chippewa 120 22
33 McCLOUD BROTHER BRIDGE 125 22
34 JAMES BROTHER BRIDGE 132 23
35 6inch HotShot Nicks 118 20

新しく購入した靴

No. モデル ブランド 2023年の着用回数
1 Skipton Zip Boot George Cox 7
2 Clearcut m trippen 42
3 Strike Norman Walsh 27

手放した靴

No. モデル ブランド 2023年までの着用回数 2023年の着用回数
1 Evan+ Southgate 37 15
2 ASAHI M020 ASAHI 74 16
3 HI BASKET W MoonStar 87 16

TypeORM 0.3.0の変更点

TypeORM 0.3.0では破壊的な変更がいくつか含まれています。

ConnectionからDataSource

ConnectionからDataSource、ConnectionOptionsからDataSourceOptionsにクラス名が変更になっています。
またcreateConnectionなどのユーティリティ関数も軒並みDeprecatedになっているので、しばらくは大丈夫なのですが、一気に変えてしまった方がよさそうです。

また接続処理の部分は別途変更する必要があります。

この変更はtsで接続設定の型指定が効くので、typoや間違った設定をしにくくなるのでいい変更だと思います。

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./model/entity/User"
import { resolve } from "path"

const migrationsPath = resolve(__dirname, "model/migration")

export const AppDataSource = new DataSource({
  type: "postgres",
  host: "localhost",
  port: 5432,
  username: "example",
  password: "example",
  database: "example",
  entities: [User],
  synchronize: false,
  logging: false,
  migrations: [`${migrationsPath}/*{.js,.ts}`]
})

ormconfigの廃止

データベースの接続情報をormconfig.jsonで持てるようになっていましたが、それが廃止になる予定です。
代わりにDataSourceを使います。

複数のデータベースを扱っている場合は、データベース単位でDataSourceを定義する必要がありそうです。
なぜならマイグレーションを実行する時のデータベース指定がファイルを指定するようになっているためです。

マイグレーションの指定方法

typeorm migration:run -d ./dist/data-source1.js # データソース1
typeorm migration:run -d ./dist/data-source2.js # データソース2

一応、一つのDataSourceでも複数のDBに永続化はできる見たいですが、ドキュメント見る限りエンティティ毎にデータソースを指定する必要があるので面倒なのと、ローカルで検証用のDBとテスト用のを分けてテストしたい時に困りそうです。

AbstractRepositoryを使用したカスタムリポジトリの廃止

カスタムリポジトリを扱いたい場合、AbstractRepositoryを継承することで独自のリポジトリを定義できるようになっていて、比較的自由な実装ができるようになっていましたがそれができなくなりました。

代わりにextendを使用して独自のメソッドを定義できるのみなっています。

import { Repository } from "typeorm"
import { AppDataSource } from "./data-source"
import { User } from "./model/entity/User"

type CustomUserRepository = Repository<User> & {
  example(name: string) : Promise<User>
}

export const UserRepository = AppDataSource.getRepository(User).extend({
  // thisのコンテキストがわかるように型を指定する
  async example(this: CustomUserRepository, name: string) {
    return this.createQueryBuilder().getOne()
  }
})

AbstractRepositoryを継承しなくて良くなった点はいいのですが、設計の自由度が減ってしまいました。
DDDが割と実践しやすかった点がなくなってしまっています。

まとめ

カスタムリポジトリがカスタムとは?という感じになってしまっていましたが、割と健全な方な修正で良かったかなと思います。

R2のファイルアップロード用の署名付きURL

R2の署名付きURLが利用できるか調べていました。
S3互換と言いつつ、使えないものもあるだろうと思っていましたが最低限のAPIが揃っているので困ることはなさそうです。

今回はask-sdkのv3を使用しました。 v2まではS3Client単体で作成できたみたいですが、パッケージが分割されていて必要なものだけでインストールすればOKぽいです。

R2の利用方法

R2を利用するには、Cloudflareにログインして、R2を有効にする必要があります。
R2を有効にした後は、R2のAPIトークンとバケットを作成します。

また、アカウントIDも必要になるのでダッシュポードに掲載されているAccount IDをメモしておきます。

署名付きURLを発行する

署名付きURLを発行するには下記のコードを利用します。
S3Clientのオプションにクレデンシャルとリージョンを指定してクライアントを初期化します。

その後はgetSignedUrl関数を利用してPutObjectに対しての署名付きURLを作成します。
署名の有効期限はexpiresInで指定すればOKのようです。

const s3 = require('@aws-sdk/client-s3')
const presigner = require('@aws-sdk/s3-request-presigner')

const accountId = process.env.R2_ACCOUNT_ID || 'example'
const bucket = process.env.R2_BUCKET || 'example'

const client = new s3.S3Client({
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  },
  region: 'auto',
  endpoint: `https://${accountId}.r2.cloudflarestorage.com`
})

exports.presignedURL = {
  async of(path) {
    const command = new s3.PutObjectCommand({
      Bucket: bucket,
      Key: path,
      ContentType: "image/jpeg",
    })
    return presigner.getSignedUrl(client, command, { expiresIn: 3600 });
  }
}

署名付きURLにファイルをアップロードする

署名付きURLにPUTでファイルを指定すると、ファイルをアップロードできます。
Content-Typeヘッダーは指定しておいた方がよさそうです。

curl -XPUT -H "content-type:image/jpeg" --data-binary @example.jpg [URL]

気になったところ

署名付きURLにはX-Amz-CredentialにAccessKeyIdが含まれるので、S3の場合はセキュリティのために署名付きURLを作成する際はAWS Security Token Serviceで一時的なクレデンシャルを作成し、署名付きURLを作成していました。

この方法の場合、X-Amz-Credentialが一時的なクレデンシャルのものになるので安心でした。
R2の場合はこの方法が取れないので、URLの期限をできるだけ短くするなどしか対策が取れないかもしれないです。

最後に

今回の具体的なコードはここにおいて置いました。 github.com

六本木でFish & Chips食べてきた

先週ぐらいから休んでいるので、ちょこちょこ外で食事をしている。
Fish & Chipsが無性に食べたくなったので、六本木まで行って食べてきた。

ラージサイズを頼んだので、お腹がいっぱいになった。
魚のフライは塩で味付けされていて、ソースつけて食べるよりもいいなと思った。

ソースがいくつか選べたので、今度はカレーソースを試して見たい。

家に帰った後は、近くの酒屋でクラフトビールを買って飲んでいた。
BREWDOG の PUNK IPAとELVIS JUICEを飲んだけど、ELVIS JUICEの方が味的には好みだった。

HAZY JANEがまだ残っているので、明日飲む。

OCaml5.0.0の並列処理モジュールを試す

5.0.0がリリースされてから触ってなかったので、新しく追加された並列処理用のモジュールを試しました。

v2.ocaml.org

環境の更新

まず初めに開発環境を最新にしました。
opamを最新の2.1.4にし、opam switch create 5.0.0で5.0.0に切り替えます。
ライブラリはまだ何も入っていないので、とりあえず下記のものをインストールしました。

ビルド、REPL、VS Code用にLanguage Serverを入れておきます。

  • dune
  • utop
  • ocaml-lsp-server
  • ocamlformat-rpc
  • merlin

まずはDomainsから

DomainはOCamlにおける並列処理の単位らしいです。
ドキュメントではspawn関数でDomainを生成して、joinで処理が終了するまで待つという単純なコードになってます。

コマンドライン引数で整数を受け取って、フィボナッチ数列を並列で処理するものです。

let n = try int_of_string Sys.argv.(1) with _ -> 1

let rec fib n =
  if n < 2 then 1 else begin
    let d1 = Domain.spawn (fun _ -> fib (n - 1)) in
    let d2 = Domain.spawn (fun _ -> fib (n - 2)) in
    Domain.join d1 + Domain.join d2
  end

let main () =
  let r = fib n in
  Printf.printf "fib(%d) = %d\n%!" n r

let _ = main ()

並列処理ライブラリを使う

Domainsは低レベルな機能を提供するシンプルなものでしたが、domainslibという並列処理モジュールを使用すると高度な並列処理を行うことができるようです。

opam install domainslibでインストールすることができます。
現時点のバージョンは0.5.0です。

ドキュメントのサンプルはDomainsと同じく、フィボナッチ数列を計算するものですが、代わりにDomainプールを使用して、並列処理を行うようになっています。

setup_pool関数でDomainの数を指定して、プールを初期化し、run関数でプールと、実行したいタスクを指定すると、処理が完全に終了するまで待機します。

run関数の代わりに、async関数を使用すると、非同期のタスクを返してくれます。
非同期タスクはawait関数を使用して処理が終了するまで待機してくれます。

モジュールのシグニチャを見ると同期的なタスクは'a Task.task 、非同期タスクは'a Task.promiseみたいなので、型レベルで区別されていてわかりやすいです。

タスクの型定義はunit -> 'aなので、引数なしの何かしらの値を返す関数として認識しておくとよさそうです。

let num_domains = int_of_string Sys.argv.(1)

let n = int_of_string Sys.argv.(2)

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

module Task = Domainslib.Task

let rec fib_par pool n =
  if n > 20 then begin
    let a = Task.async pool (fun _ -> fib_par pool (n-1)) in
    let b = Task.async pool (fun _ -> fib_par pool (n-2)) in
    Task.await pool a + Task.await pool b
  end else fib n

let main () =
  let pool = Task.setup_pool ~num_domains:(num_domains - 1) () in
  let res = Task.run pool (fun _ -> fib_par pool n) in
  Task.teardown_pool pool;
  Printf.printf "fib(%d) = %d\n" n res

let _ = main ()

感想

まだ追加されたばかりなので、仕様が変わるかもしれないですが思いのほかよかったです。
試してみたコードはGitHubにあげてあります。

github.com