Open8

データベースの基本

nukopynukopy

基礎用語

  • データベース (Database)
  • データベースファイル
    • データの実体
    • コンピュータ上で管理されるデータベースにおいては、そのデータベースのデータが書き込まれているファイル、データの実体のこと。
  • DBMS(Database Management System)
    • データベースファイルに対して読み書きを行うソフトウェア。MySQL、PostgreSQL、MongoDB、TiDB など。
  • (IT の文脈での)データベース (Database)
    • データベースファイル + DBMS で構成されるデータ管理を行うためのシステム。データベースファイルは DBMS の仕様で決まるため、使用する上では分けて考えることは少ない。

(以下、IT の文脈での「データベース」を使用する)

  • スキーマ (Schema)
    • データベースで管理されるデータの構造の定義。
  • クエリ (Query)
    • 特定の情報を取得するためにデータベースに送信する命令。

基礎用語 - RDBMS

  • RDBMS (Relational Database Management System)
    • DBMS の内、データをテーブルとその関係として構造化し、それを管理するソフトウェア。MySQL、PostgreSQL、Oracle Database、MariaDB などがこれにあたる。
  • テーブル (Table)
    • RDBMS において、。テーブルは行(レコード)と列(フィールド)から成るデータ構造。
    • レコード (Record): テーブル内の各行のこと。一連の関連データを表現する。
    • フィールド (Field): テーブル内の各列は、レコードの特定の属性を表現する。
  • SQL (Structured Query Language)
    • RDBMS のデータベースの構造とデータの操作(CRUD, Create, Read, Update, Delete)を行うための標準的な言語
  • インデックス (Index)
    • データの検索速度を高めるためのデータベース構造。インデックスは特定の列の値に基づいてテーブル内のデータの位置を参照する。BTree というデータ構造を仕様。
  • テーブル間の関係を定義するための概念
    • 主キー (Primary Key)
      • 各テーブル内で各レコードを一意に識別するフィールド。
    • 外部キー (Foreign Key)
      • あるテーブルのレコードが別のテーブルの特定のレコードを参照するためのフィールド。RDBMS でデータ同士の関係性を表現するための概念、機能である。
  • 正規化 (Normalization)
    • データの重複を避け、データの整合性を保つためにテーブル構造を変更すること。
  • トランザクション (Transaction)
    • データベースに対する一連の操作をまとめたもの。トランザクションは原子性、一貫性、分離性、耐久性(ACID特性)を持つ。

References

  • PostgreSQL チュートリアルでの PostgreSQL のアーキテクチャ(サーバ / クライアント型)の説明。「データベースファイル(database files)」への言及もある。

https://www.postgresql.org/docs/15/tutorial-arch.html

nukopynukopy

RDBMS - トランザクションと ACID 特性

トランザクション

  • トランザクション (transaction)
    • データベースに対する一連の操作のこと。データベースや分散システムにおいて、データの整合性、安全性を保つために必要な概念。例えば、ある特定のトランザクションを実行した際、一連の操作が全て成功するか、あるいは失敗した場合は操作が一つも実行されなかったかのように扱わうことで、データの整合性が保たれる。トランザクションには、ACID 特性というデータの整合性、信頼性などデータベースが正しく動作するために必要な性質を満たす必要がある。

ACID 特性

以下の 4 つの特性を全て満たすことで、データベーストランザクションの信頼性と整合性が維持されます。

  • ACID 特性
    • 原子性 (Atomicity)
      • トランザクション実行後のデータベースの状態が、トランザクションに含まれる一連の操作が「全て実行される」か「一つも実行されない」のどちらかの状態になるという性質。
      • ex) 銀行の送金処理において、送金元の残高、送金先の残高それぞれの更新はトランザクションとして実行される。
    • 一貫性 / 整合性 (Consistency)
      • トランザクションの実行前後でデータの整合性が保たれ、矛盾が無い状態が継続される性質。
      • ex) 銀行の送金トランザクションによって口座の残高が負になることがないようにする。
    • 隔離性 / 独立性 (Isolation)
      • 各トランザクションは互いに独立して実行され、他のトランザクションの実行中に発生する中間状態を見ることができないという性質。
      • (言い換え)トランザクション実行中の処理過程は外部から隠蔽され、他のトランザクションに影響を与えないようにする性質。
      • 隔離性により、複数のトランザクションが同時に行われるときでも、それぞれが互いに干渉せずに安全に実行できるようになる。
      • ex) 銀行の送金トランザクションにおいて、処理の途上である「出金は実施済みだが入金は未実施」といった状態を外部から読み出されないよう排他制御などを行う。
    • 耐久性 (Durability)
      • 成功したトランザクションの結果は永続的であることを保証する性質。つまり、一度トランザクションがコミット(確定)されれば、その結果はデータベースに永続的に保存され、システム障害が発生した場合でも失われることはないという性質を表す。

References

https://e-words.jp/w/ACID特性.html

nukopynukopy

コネクションプール connection pool

意味

  • コネクション connection
    • DBMS は一般にアプリケーションの背後でデータを管理するために存在する。アプリケーションはユーザが利用したい機能に合わせて DBMS へ問い合わせをし、必要なデータを取り出してユーザに返す。アプリケーションが DBMS へ問い合わせを行い、その問い合わせに対応するデータを受け取りたい場合、アプリケーションと DBMS の間にネットワーク的な接続が必要になる。一般にこの接続には TCP 接続が用いられる。このアプリケーションと DBMS の接続のことを「コネクション」と呼ぶ。
  • コネクションプーリング connection pooling
    • コネクションプーリングとは、プログラムがデータベース管理システム(DBMS)へアクセスする際、アクセス要求のたびに接続や切断を繰り返すのではなく、一度形成した接続窓口(コネクション)を維持し続けて使い回す手法。
  • コネクションプール connection pool
    • コネクションプーリングにおいて、使い回す複数の接続を保持しているもの。
    • 実体はオンメモリに保持し続けられている、TCP 接続が継続しているプロセス?

ポイントは以下の通り。

  • サーバーレスでは hogehoge
  • コネクションを新たに張るときに DBMS を動かしているインスタンス側は負荷がかかる
    • プロセスを作成し、メモリ、CPU などのリソースの確保を行う

コネクションの作成手順

データベースとのコネクション作成は通常、以下のような手順で行われる。ここではTCP/IPを通じての接続を例にとる。

  1. ソケットの作成
    • アプリケーションは、データベースサーバーへの通信路を作るために、ソケットを作成する。ソケットとは、ネットワーク上の 2 つのエンドポイント間の双方向通信リンクを提供するプログラムインターフェースである。
  2. サーバーへの接続
    • アプリケーションは、データベースサーバーの IP アドレス(またはホスト名)とポート番号を指定して、ソケットを通じてサーバーに接続する。一般にここでの接続のプロトコルとしては TCP(Transmission Control Protocol)が利用される。TCPは、信頼性が高く順序付けされたバイトストリームを提供するプロトコルで、通信が正確に行われることを保証する。
  3. TCP 3 way handshake
    • TCP 接続は、いわゆる 3 way handshake というプロセスを通じて確立される。まず、クライアント(ここではアプリケーション)は、接続要求とともに SYN パケットをサーバーに送信する。次に、サーバーは接続を認識し、SYN パケットに対する応答として SYN-ACK パケットをクライアントに送り返す。最後に、クライアントはこの SYN-ACK パケットに対する応答として ACK パケットをサーバーに送信する。これにより、TCP 接続が確立される。
  4. データベースへのログイン
    • TCP 接続が確立したら、アプリケーションはデータベースへのログイン要求を送信する。これには通常、ユーザー名とパスワード(または他の認証情報)が含まれる。データベースサーバーは、これらの認証情報を検証し、正しい場合はアクセスを許可する。
  5. コネクションプール
    • コネクションが確立され、使われなくなったとき、アプリケーションはこのコネクションを切断する代わりに、コネクションプールに戻すことができる。次にデータベース接続が必要になったとき、アプリケーションは新しい接続を作成するのではなく、コネクションプールから既存の接続を再利用することができる。これによりパフォーマンスが向上する。

このプロセス全体は、アプリケーションとデータベース間のコミュニケーションを可能にし、これによりデータの送受信を行うことができる。

コネクションプールのどのレイヤーの話?

コネクションプールは、アプリケーションレイヤーとデータベースレイヤーの間のリソース管理の概念である。

コネクションプールは、アプリケーションがデータベースへの接続を効率的に再利用するためのキャッシュまたはプールのことを指す。

ユーザからアプリケーションへのリクエスト → アプリケーションによるデータベース操作(ドライバー経由でクエリを送信しようとする操作)という処理を起点に、アプリケーションとデータベースのコネクションが新規作成される。このコネクションの新規作成処理には一定のオーバーヘッドが伴う。

大規模になると、リクエストのたびにデータベースへの接続を作成するため、サーバーのメモリ使用量が枯渇し、接続の最大許容数に達する可能性がある。解決策の 1 つは、より多くのメモリを購入することであり、もう 1 つはコネクションプールを使用することです。

このオーバーヘッドを避けるため、すでに開かれた(そして閉じられていない)接続をプール内に保持し、必要なときにそれを再利用する。これによりパフォーマンスが向上し、リソース利用が最適化される。

コネクションプールは、RDBMS 特有の概念ではなく、他の多くのネットワークサービスやプロトコルでも見られる。ただし、データベースアクセスにおいては特に重要となる。多くのアプリケーションが頻繁に小さなクエリを発行するため、コネクションの open / close のオーバーヘッドがパフォーマンスに大きな影響を与える可能性がある。

  • コネクションプーリング以前

  • コネクションプーリング以後

https://vercel.com/guides/connection-pooling-with-serverless-functions

各種プログラミング言語やフレームワークでは、このコネクションプールを管理するためのライブラリやツールが提供されている。これにより開発者はデータベース接続の再利用やリソース管理を簡単に行うことができる。

コネクションプールの実例

Supabase PgBouncer

https://supabase.com/blog/supabase-pgbouncer

PgBouncer は PostgreSQL のためのオープンソースのコネクションプーラ(connection pooler)

https://www.pgbouncer.org/

pgpool。元祖コネクションプーラ?

コネクションプールはデータベースへの新規接続を減らすために接続を保持して使いまわす仕組みです。新規接続の受け付けはデータベースサーバにとってコストが高い処理です。再利用を行うことで接続によるオーバーヘッドを低減できます。

PostgreSQL のクライアントが Tomcat のようなアプリケーションサーバであれば、アプリケーションサーバ側でコネクションプールを用意することができます。しかし、Webサーバ上の PHP や Perl では、各 HTTP セッションを処理するプロセスの間で共通に使用できるコネクションプールを作ることは簡単ではありません。例えば PHP 言語で「永続的」な pconnect の機能がありますが、あくまで、Webサーバの単一プロセス内で接続を保持するだけです。pgpool-II は独立したサーバプロセスとして提供されるため、このようなケースでも複数のセッションで共用できるコネクションプールを実現できます。

PostgreSQL では同時接続数を増やしていくと、あるところからトータル処理性能が低下していきます。各接続を処理するプロセスの使用メモリ量の合計が大きなものになり、また、処理の競合による待ちも増えていくためです。したがって、同時接続数を一定レベルに抑える必要があります。ところが PostgreSQL は設定した最大接続数を超えた接続要求に対してエラーを返す振る舞いをします。これではアプリケーションの配置に制約が生じます。Webサーバであれば、Web側で受け付ける同時接続数を PostgreSQL の同時接続数を下回るようにしなければいけません。

pgpool-II では、pgpool-II に設定した最大同時接続数を超えた接続要求に対して、エラーではなく待たせる動作をします。PostgreSQL とアプリケーションの間に pgpool-II を挟むことで、データベースサーバの同時接続数をエラーを出さずに一定数に抑えることができるようになります。

https://lets.postgresql.jp/node/126

References

Vercel 公式:コネクションプーリングとは?

https://vercel.com/guides/connection-pooling-with-serverless-functions#what-is-connection-pooling

Supabase 公式:コネクションプーリングとは?

https://supabase.com/blog/supabase-pgbouncer

Apache vs Nginx の話もしてくれていて、コネクションプールの理解に良さそう。

  • Database Connections: Less is More

https://kwahome.medium.com/database-connections-less-is-more-86c406b6fad

https://e-words.jp/w/コネクションプーリング.html

コネクションプールの実装には軽量プロセス(軽量スレッド)が大変良いのはなぜ?

https://twitter.com/voluntas/status/1627650571623878658?s=20

Go でのコネクションプールの実装

https://twitter.com/__syumai/status/1453228635322716163?s=20

pgpool について

https://twitter.com/soudai1025/status/375141873914216448?s=20

Rust のテスト書くときにコネクションプールを使い回せない

https://qiita.com/autotaker1984/items/d0ae2d7feb148ffb8989

事例

https://twitter.com/blackenedgold/status/1570491901593395202?s=20

https://twitter.com/blackenedgold/status/1570496041493073921?s=20

nukopynukopy

この Lambda のコネクションプールの問題は解決してた気がするけど、コネクションプールって何を理解するのに良さそう。→ Amazon RDS Proxy の登場により問題は解決してる。

https://www.keisuke69.net/entry/2017/06/21/121501

  • Amazon RDS Proxy あり / なしで負荷テストをやってる例

https://aws.amazon.com/jp/blogs/news/build-and-load-test-a-multi-tenant-saas-database-proxy-solution-with-amazon-rds-proxy/

  • エッジ環境におけるコネクションプーリングの問題とその解決方法。サーバーレス環境とコネクションプーリングの問題は共通。この記事の「サーバーレス環境とコネクションプーリング」の部分。2023/05 時点の記事。

https://zenn.dev/laiso/articles/542fdfac2acb6b

  • これも一緒に読むとはかどる。同じ筆者。2023/01 時点の記事。この時点では Amazon RDS Proxy や Cloud SQL Proxy のようなプラットフォームが提示する解決策がまだない状態だった。

https://zenn.dev/laiso/scraps/595d631fe19ef5

nukopynukopy

コネクションプーリングの理解に Vercel のガイドが良さげ

  • Connection Pooling with Serverless Functions
    • コネクションプーリングとはについては分かりやすく書いてある。

https://vercel.com/guides/connection-pooling-with-serverless-functions

https://github.com/MatteoGioioso/serverless-pg

Neon との Partnership についてちょこっと書いてある

https://vercel.com/docs/storage/vercel-postgres

https://vercel.com/docs/concepts/functions/serverless-functions

  • Cloudflare Workers とかのエッジ環境についてのイントロ

https://speakerdeck.com/chimame/etuzidehui-kuhurontoendo

nukopynukopy

データベースドライバ

https://future-architect.github.io/articles/20210916a/

とりあえず ChatGPT で雑に。

データベースドライバーは、アプリケーションとデータベース管理システム(DBMS)間の通信を担当するソフトウェアコンポーネントです。これは、アプリケーションがデータベースに対して操作(クエリの実行、データの取得等)を行うためのインターフェースを提供します。

具体的には、アプリケーションがデータベースに対する操作を行う際、その操作をデータベースが理解できる形式(多くの場合はSQL)に変換し、結果をアプリケーションが扱える形式に変換する役割を果たします。

データベースドライバーはデータベースの種類(MySQL、PostgreSQL、Oracleなど)や使用しているプログラミング言語(Java、Python、Goなど)によって異なります。これは各データベースが独自の通信プロトコルやデータ形式を持つため、それぞれに対応したドライバーが必要となるためです。

データベースドライバーは、JDBC(Java Database Connectivity)やODBC(Open Database Connectivity)のような標準化されたAPIを通じてアクセスされることが多く、これによりアプリケーション開発者はデータベースの種類に関わらず一貫したコードでデータベース操作を行うことが可能になります。

nukopynukopy

分散システムにおける概念

シャーディング

意味

データベースのシャーディング(Sharding)は、大量のデータを効果的に管理するための手法で、データベースを複数のパーツ(シャードと呼ばれます)に分割し、各シャードを異なるサーバーに配置する方法を指す。これにより、データベースのパフォーマンスを向上させ、大量のデータを処理できる能力を維持できる。

シャーディングは、それぞれのシャードが独立して動作するため、各シャードでの読み取り / 書き込みのパフォーマンスが向上する。これは、すべてのデータを単一のデータベースに保持して処理するよりも高速でスケーラブルです。そのため、大規模なデータセットや高負荷の状況を扱う必要があるアプリケーションにとっては非常に役立つ手法である。

シャーディングは、主に大規模な分散データベースシステムで使われる。特に、データ量が大きく、単一のデータベースサーバーで処理することが難しい場合や、読み書きのパフォーマンスを向上させる必要がある場合などに利用される。

シャーディングを行うことで、データを異なる物理サーバーに分散させることができる。これにより、負荷を複数のノードに分散させることが可能となり、システム全体のパフォーマンスを向上させることができる。また、特定のデータを特定のシャードに配置することで、データの取得を高速化することも可能。

ただし、シャーディングは適切な戦略と計画をもとに行わなければならず、一度にすべてのデータを探索する必要があるクエリ(例えば、シャードを跨いだデータの集約やジョインなど)のパフォーマンスが下がる可能性がある。また、データを分散させることでデータの整合性を保つのが難しくなるという課題もある。

References

https://qiita.com/hharu/items/15627d2058bffe1fadf0

Rust での簡易的なシャーディングの実装例 in Tokio Tutorial

https://zenn.dev/magurotuna/books/tokio-tutorial-ja/viewer/shared_state#bytes-クレートを依存に追加する