PL /プロキシを使用してPostgreSQLを水平にスケーリングします。

記事を書き始めるのは非常に難しいです。 つまり、最初の言葉を思い付くのは非常に困難です。 私は一度にすべてのことについて話したいです:)しかし、いいえ。 一貫性があります。

そもそも、最近、私が訪れたHighload ++ 2008が開催されました。

私はすぐに言わなければならない-イベントは最高クラスで開催され、多くのレポートがあり、すべてが非常に面白かった。

最も記憶に残るプレゼンテーションの1つは、SkypeのデータベースサーバーのインフラストラクチャについてのAsya Oyaによる講演でした。 講義では、このようなサーバーのパフォーマンスを実現するさまざまな手段に焦点を当てました。

Askoによれば、地球のすべての住民が一瞬でスカイプに接続したい場合でも、Skypeデータベースは存続します。



家に着くと、それをすべてライブで試してみたかったのです。 今から話します。 すぐに予約します。テストのデータベース構造は、開発者自身のWebサイトの例から取られたものであり、実際の読み込みとは関係ありません。

この記事では、ロードバランシングが既にホットになりベースが低下した後に行う必要があることを説明しますが、これは完全に真実ではありません。 この記事の助けを借りて、私は初心者と経験の浅い開発者を準備すると同時に、システムを設計するときにサーバー間の負荷分散の可能性を提供する必要があるという事実について考えさせたいと思います。 そして、これは、彼らがこれほど多くのことを書いたり、恐れたりする「時期尚早の最適化」とはみなされません。



UPD: descentspb habrayuzerが記事で正しく述べているように、迷惑なエラーがあります。 不注意の結果、プロキシとクライアントの間にPgBouncerをインストールする必要があると考えました。 しかし、判明したように、PgBouncerの助けを借りて解決した問題は、そのようにインストールしても解決されません。 ノードとプロキシの間にバウンサーをインストールすることをお勧めします。 さらに、これはまさにPL / Proxyウェブサイトの公式マニュアルで推奨されていることです。

いずれにせよ、図に示されているようにPgBouncerを使用すると、パフォーマンスが向上します。 (プロキシのアンロード)。







1.誰のせいですか?



そのため、開発者であり、大きくて非常に負荷の大きいものを作成すると、遅かれ早かれ、データベースが負荷に耐えられないという事実に気付くでしょう。 多くの要求があり、鉄は単にそれらに対処することができません。

この問題を解決する方法は何度も議論されてきましたが、最も効果的だと思われるもののリストのみを提供します。



-コードを最適化します。

-サーバーの能力を向上させます。

-キャッシュ(memcacheに関する記事をタグで検索します)。

-サーバー間で負荷を分散します。



最後の点について説明しましょう。



2.何をする?



そのため、コードは最適化され、サーバーはどこよりも涼しくなります;データベース全体がキャッシュ内にありますが、それでも単一のリクエストでクラッシュします。 水平方向のスケーリングを行うときです。

ああ、私はまだPostgreSQLに関するその記事について言及していません。 まだMySQLを使用していますか? その後、私たちはあなたに行きます:)

私の謙虚な意見では、プロジェクトが本当に深刻な場合、ベースはMySQLよりも少し深刻になるはずです。 さらに、Postgresにはスケーリングのためのすばらしいツールがあります。 (たぶんMySQL用のものがあるのでしょうか?私は応答記事を待っています:))。



3.そして、彼らは何を食べますか?



PL /プロキシは、PostgreSQLデータベースサーバー上の関数をリモートで呼び出したり、データを分割したりするための言語です。

作業のスキームは写真に示されています。 PgBouncerについては後述します。

画像






通常、アプリケーションはデータベースを照会するだけです。 この場合、アプリケーションはデータベースに対して通常のクエリも実行します。 純粋なSQLコードを呼び出すのではなく、事前に作成された関数を呼び出します。

次に、データベースは、必要なデータがどのノードにあるかを判断します。

そして、要求を目的のサーバーにリダイレクトします。

要求が実行され、メインサーバーに返された後、データがアプリケーションに返されます。



すべてがうまくいくように見えますが、リクエストの数が多いと、PL / Proxyは

ノードへの接続数。これにより、新しいPostgres(フォーク)プロセスが作成されますが、これはパフォーマンスにあまり影響しません。 この問題を解決するには、PgBouncerが必要です。

PgBouncerは... mm ...修正できないように言う方法...接続マルチプレクサ。 通常のPostgresプロセスのように見えますが、内部ではリクエストキューを管理しているため、サーバーの速度を向上させることができます。 PgBouncerが受信した数千のリクエストのうち、データベースに到達するのは数ダースだけです。

このすばらしいツールを使用することのボーナスを評価するには、PgBouncerをオンにする前後に2つのサイトにデータベースサーバーをロードするスケジュールを見てください。 写真はニコライ・サモクバロフ「パフォーマンス・ポストグレス」のプレゼンテーションから取られています。

画像






4.柔らかいフレンチロールをください



4.1 PgBouncerのインストール


インストールプロセスはまったく元のものではありません:

パッケージをダウンロードします(執筆時点では、最新バージョンは1.2.3でした)

pgfoundry.org/frs/?group_id=1000258

開梱:

#tar -xzvf pgbouncer-1.2.3.tgz

:

#cd pgbouncer-1.2.3

#./configure

#make

#make install








構成ファイルを作成します。

/etc/pgbouncer/pgbouncer.ini



[databases]

testdb = host=localhost port=5432 dbname=testdb



[pgbouncer]

listen_port = 6543

listen_addr = 127.0.0.1

auth_type = md5

auth_file = users.txt

logfile = /var/log/pgbouncer.log

pidfile = /var/run/pgbouncer/pgbouncer.pid

pool_mode = statement # PL/Proxy

admin_users = root







認証ファイルを作成します。

/etc/pgbouncer/users.txt

"testdb_user" "testdb_user_password"







以下を開始します。

/usr/local/bin/pgbouncer -d /etc/pgbouncer/pgbouncer.ini -u postgres





-dスイッチはデーモンモードで実行する必要があることを示し、-uスイッチはpgbouncerプロセスを起動するユーザーを示します。



gentooユーザーの場合、スタートアップスクリプトという形での驚きは次のとおりです。

/etc/init.d/pgbouncer

#!/sbin/runscript



depend() {

need postgresql

use pgsql

}

start() {

ebegin "Starting Pgbouncer"

start-stop-daemon --start --background --exec /usr/local/bin/pgbouncer --chdir /etc/pgbouncer/ -- -d pgbouncer.ini -u postgres

eend $? "Failed to start Pgbouncer"

}

stop() {

ebegin "Stopping Pgbouncer"

start-stop-daemon --pidfile /var/run/pgbouncer/pgbouncer.pid --stop

eend $? "Failed to stop Pgbouncer"

}









これで、アプリケーションのDSNとして、データベースに接続するポートを5432から6543に変更するだけで、前後のサーバー負荷の比較を開始できます。



4.2 Pl / Proxyのインストール


この実験には、3つのPostgresサーバーが必要です。

そのうちの1つは、プロキシと呼びましょう。他の2つにリクエストをプロキシします。

それらをnode1およびnode2と呼びましょう。

pl /プロキシを正しく動作させるには、2のべき乗に等しいノード数を使用することをお勧めします。

すでにPostgresがインストールされていると思います。

プロキシサーバーにPL /プロキシをインストールします。

pl /プロキシの最新バージョンをダウンロード: pgfoundry.org/frs/?group_id=1000207

いつものように:

#。/設定

#make

#make install

これは、Postgres自体を再起動する必要がある場所です。

そして今、楽しみが始まります。



テストのために、各ノードで新しいproxytestデータベースを作成します。

CREATE DATABASE proxytest

WITH OWNER = postgres

ENCODING = 'UTF8' ;








このベース内で、plproxyというスキーマを作成します。 この項目は公式の指示にはありませんでしたが、何らかの理由で、呼び出されたすべての関数がこの方法で呼び出されようとしました:plproxy.functioname()。

CREATE SCHEMA plproxy

AUTHORIZATION postgres;

GRANT ALL ON SCHEMA plproxy TO postgres;

GRANT ALL ON SCHEMA plproxy TO public ;










そして、それに1つのプレートを追加します:

CREATE TABLE plproxy.users

(

user_id bigint NOT NULL DEFAULT nextval( 'plproxy.user_id_seq' ::regclass),

username character varying (255),

email character varying (255),

CONSTRAINT users_pkey PRIMARY KEY (user_id)

)

WITH (OIDS= FALSE );

ALTER TABLE plproxy.users OWNER TO postgres;










次に、これらのテーブルにデータを追加する関数を作成します。

CREATE OR REPLACE FUNCTION plproxy.insert_user(i_username text, i_emailaddress text)

RETURNS integer AS

$BODY$

INSERT INTO plproxy.users (username, email) VALUES ($1,$2);

SELECT 1;

$BODY$

LANGUAGE 'sql' VOLATILE;

ALTER FUNCTION plproxy.insert_user(text, text) OWNER TO postgres;










ノードが終了しました。 サーバーをセットアップしましょう。

すべてのノードと同様に、データベースはメインサーバー(プロキシ)上に存在する必要があります。

CREATE DATABASE proxytest

WITH OWNER = postgres

ENCODING = 'UTF8' ;










対応するスキーマ:

CREATE SCHEMA plproxy

AUTHORIZATION postgres;

GRANT ALL ON SCHEMA plproxy TO postgres;

GRANT ALL ON SCHEMA plproxy TO public ;










次に、このデータベースがpl / proxyを使用して管理されていることをサーバーに伝える必要があります。

CREATE OR REPLACE FUNCTION plproxy.plproxy_call_handler()

RETURNS language_handler AS

'$libdir/plproxy' , 'plproxy_call_handler'

LANGUAGE 'c' VOLATILE

COST 1;

ALTER FUNCTION plproxy.plproxy_call_handler() OWNER TO postgres;

-- language

CREATE LANGUAGE plproxy HANDLER plproxy_call_handler;










また、サーバーがどこにどのノードを持っているかを知るには、pl / proxyが作業で使用する3つのサービス関数を作成する必要があります。

CREATE OR REPLACE FUNCTION plproxy.get_cluster_config( IN cluster_name text, OUT " key " text, OUT val text)

RETURNS SETOF record AS

$BODY$

BEGIN

-- lets use same config for all clusters

key := 'connection_lifetime' ;

val := 30*60; -- 30m

RETURN NEXT ;

RETURN ;

END ;

$BODY$

LANGUAGE 'plpgsql' VOLATILE

COST 100

ROWS 1000;

ALTER FUNCTION plproxy.get_cluster_config(text) OWNER TO postgres;










コードを修正する必要がある重要な関数。 その中で、DSNノードを指定する必要があります。

REATE OR REPLACE FUNCTION plproxy.get_cluster_partitions(cluster_name text)

RETURNS SETOF text AS

$BODY$

BEGIN

IF cluster_name = 'clustertest' THEN

RETURN NEXT 'dbname=proxytest host=node1 user=postgres' ;

RETURN NEXT 'dbname=proxytest host=node2 user=postgres' ;

RETURN ;

END IF ;

RAISE EXCEPTION 'Unknown cluster' ;

END ;

$BODY$

LANGUAGE 'plpgsql' VOLATILE

COST 100

ROWS 1000;

ALTER FUNCTION plproxy.get_cluster_partitions(text) OWNER TO postgres;










そして最後の1つ:

CREATE OR REPLACE FUNCTION plproxy.get_cluster_version(cluster_name text)

RETURNS integer AS

$BODY$

BEGIN

IF cluster_name = 'clustertest' THEN

RETURN 1;

END IF ;

RAISE EXCEPTION 'Unknown cluster' ;

END ;

$BODY$

LANGUAGE 'plpgsql' VOLATILE

COST 100;

ALTER FUNCTION plproxy.get_cluster_version(text) OWNER TO postgres;










実際、アプリケーションで直接呼び出される最も重要な関数は次のとおりです。

CREATE OR REPLACE FUNCTION plproxy.insert_user(i_username text, i_emailaddress text)

RETURNS integer AS

$BODY$

CLUSTER 'clustertest' ;

RUN ON hashtext(i_username);

$BODY$

LANGUAGE 'plproxy' VOLATILE

COST 100;

ALTER FUNCTION plproxy.insert_user(text, text) OWNER TO postgres;










関数コードに関する質問はコメントに含まれていますが、私はPostgresの第一人者ではなく、単なる学生であることを心に留めておいてください。



そして今、私たちはテストしています! :)



ポート6543でプロキシサーバーに接続します(すぐにPgBouncerを使用します)。

そして、データベースにデータを入力します。

SELECT insert_user( 'Sven' , 'sven@somewhere.com' );

SELECT insert_user( 'Marko' , 'marko@somewhere.com' );

SELECT insert_user( 'Steve' , 'steve@somewhere.com' );










これで、各ノードに接続でき、すべてを正しくエラーなく実行した場合、最初の2つのエントリはnode1に、3番目のエントリはnode2にあります。



データを抽出しようとしています。

これを行うには、新しいサーバー関数を作成します。

CREATE OR REPLACE FUNCTION plproxy.get_user_email(i_username text)

RETURNS SETOF text AS

$BODY$

CLUSTER 'clustertest' ;

RUN ON hashtext(i_username) ;

SELECT email FROM plproxy.users WHERE username = i_username;

$BODY$

LANGUAGE 'plproxy' VOLATILE

COST 100

ROWS 1000;

ALTER FUNCTION plproxy.get_user_email(text) OWNER TO postgres;











そして、それを呼び出してみてください:

select plproxy.get_user_email( 'Steve' );







Vopschem、すべてが私のためにうまくいった。



5.かつてこんなに頭が悪いのはなぜですか?



テスト例でわかるように、pl / proxyで複雑なことはありません。 しかし、この行を読むことができた人は誰でも、実生活ではすべてがそれほど単純ではないことをすでに認識していると思います。

16個のノードがあるとします。 何らかの方法で機能コードを同期する必要があります。 しかし、エラーが忍び込んだ場合はどうすればいいですか-すばやく修正する方法は?

この質問は会議でも尋ねられ、Askoは適切な資金はすでにSkype自体に実装されていたが、オープンソースコミュニティで法廷に持ち込むにはまだ準備ができていないと答えた。

この種のシステムを開発するときに神が禁止する2番目の問題は、クラスターに別のノードを追加したいときにデータを再配布する問題です。

すべてのサーバーを事前に準備し、データを入力してから、get_cluster_partitions関数コードを一度変更して、この大規模な操作を慎重に計画する必要があります。



6.追加資料



Skype開発者サイトのPlProxyおよびPgBouncerプロジェクト



Highload ++に関するAskoプレゼンテーション

パフォーマンスPostgres Nikolay Samokhvalov(Postgresmen)



7.丁寧なボーナス



記事を公開した後、1つのエラーと1つの欠陥が見つかりました。

すでに書かれた記事を編集するのは難しいため、ここで説明します。

1)テーブルでuser_id_seqというシーケンスを使用します。 ただし、そのためのSQLコードはどこにもありません。 したがって、誰かがコードを単純にコピー&ペーストしても、何も起こりません。 修正済み:

CREATE SEQUENCE plproxy.user_id_seq

INCREMENT 1

MINVALUE 0

MAXVALUE 9223372036854775807

START 1

CACHE 1;

ALTER TABLE plproxy.user_id_seq OWNER TO postgres;









2)データベースにデータを挿入すると、user_idフィールドのシーケンスが生成されます。 ただし、これらのシーケンスは2つあります。 そして、それぞれが独自のノードで動作します。 これにより、2人の異なるユーザーが同じuser_idを持つことになります。

したがって、新しいuser_idがノードではなくプロキシサーバーにあるシーケンスから取得されるように、insert_user関数を修正する必要があります。 これにより、user_idフィールドの重複を回避できます。



ZY: ソースコードHighliterですべてのSQLコードが強調表示されます



All Articles