Postgres/Extension

PostgreSQL의 PL/Proxy: 데이터베이스 샤딩과 원격 프로시저 호출을 위한 도구

moxie2ks 2025. 5. 24. 18:01
728x90
반응형

개요

PL/Proxy는 PostgreSQL 데이터베이스에서 원격 프로시저 호출과 수평적 샤딩을 가능하게 하는 절차적 언어 핸들러(Procedural Language handler)이다. 이 도구는 다수의 PostgreSQL 데이터베이스 간 함수 호출을 처리할 수 있으며, 데이터베이스 분산 환경에서 효율적인 데이터 관리와 처리를 지원한다. PL/Proxy를 사용하면 데이터베이스 클러스터 구성, 로드 밸런싱, 데이터 샤딩 등의 복잡한 작업을 상대적으로 간단하게 처리할 수 있다.

설명

PL/Proxy는 PostgreSQL의 확장 기능으로, 여러 데이터베이스 간의 통신을 효율적으로 처리하기 위한 메커니즘을 제공한다. 이 도구는 PostgreSQL 함수 호출을 원격 데이터베이스로 전달하고, 그 결과를 원래 함수 호출자에게 반환한다.

PL/Proxy의 핵심 기능은 다음과 같이 구분할 수 있다:

  1. 원격 프로시저 호출(RPC): 한 데이터베이스에서 다른 데이터베이스의 함수를 호출할 수 있게 한다.
  2. 수평적 샤딩(Horizontal Sharding): 특정 기준(해시 값 등)에 따라 데이터를 여러 데이터베이스에 분산 저장할 수 있다.
  3. SQL 복제(SQL Replication): 동일한 쿼리를 여러 데이터베이스에 동시에 실행할 수 있다.

PL/Proxy는 두 가지 주요 모드를 지원한다:

  1. CLUSTER 모드: 미리 정의된 데이터베이스 클러스터를 사용하여 여러 데이터베이스에 접근한다. 수평적 샤딩이나 SQL 복제에 유용하다.
  2. CONNECT 모드: 연결 문자열을 직접 지정하여 특정 데이터베이스에 접근한다. 간단한 원격 함수 호출에 적합하다.

실행 타입에 따라 쿼리 처리 방식도 다양하다:

1. RUN ON ALL: 클러스터의 모든 데이터베이스에 동시에 쿼리 실행

2. RUN ON ANY: 클러스터 내에서 무작위로 하나의 데이터베이스 선택

3. RUN ON : 해시 값을 기준으로 특정 데이터베이스에 매핑하여 실행

특징

PL/Proxy의 주요 특징은 다음과 같다:

  1. 확장성(Scalability): 데이터를 여러 데이터베이스에 분산하여 대규모 데이터 처리 능력을 향상시킨다.
  2. 유연성(Flexibility): 데이터베이스 클러스터 구성, 단일 원격 연결, 다양한 실행 모드 등 여러 옵션을 제공한다.
  3. 투명성(Transparency): 클라이언트 애플리케이션은 복잡한 분산 환경을 인식할 필요 없이 로컬 함수처럼 원격 함수를 호출할 수 있다.
  4. 분산 트랜잭션 지원 없음: PL/Proxy는 분산 트랜잭션을 지원하지 않으므로, 각 원격 함수 호출은 독립적인 트랜잭션으로 처리된다.
  5. 병렬 쿼리 실행: RUN ON ALL 모드를 사용하면 여러 데이터베이스에 쿼리를 병렬로 실행할 수 있어 성능 향상이 가능하다.
  6. 언어 확장성: PostgreSQL의 절차적 언어 시스템을 활용하여 구현되었으므로, SQL 뿐만 아니라 PL/pgSQL 등 다른 절차적 언어와도 함께 사용할 수 있다.
  7. 로드 밸런싱: RUN ON ANY 모드를 통해 클러스터 내 데이터베이스 간 부하 분산이 가능하다.

빌드 및 설치

  1. 빌드 이전에, Build를 완료한 Postgres가 필요하다. Postgres 설치시 config install을 아래와 같이 실행한다.
  2. make -C config install
  3. Makefile 수정
    • pl/proxy는 Bison 2.0 이상의 버전 사용을 선호하기 때문에, 그 이하의 버전에서는 일부 코드가 동작되지 않아 수정이 필요하다.
    • 아래의 수정을 통해 낮은 버전의 Bison 사용으로 인한 빌드 실패를 피할 수 있다.
    • /* %define api.prefix {plproxy_yy} */ /* For Bison 2.0+ */ %name-prefix="plproxy_yy" /* For under Bison 2.0 */
    • Build를 완료한 Postgres의 pg_config 경로를 설정하여 빌드 및 설치를 진행한다.
    • $ make PG_CONFIG=/path/to/pg_config $ make install PG_CONFIG=/path/to/pg_config

서버 구성 및 TEST

Background information

PL/프록시 플러그인은 다음 모드를 지원한다.

  • CLUSTER
  • 이 모드는 horizontal sharding 및 SQL replication을 지원한다.
  • CONNECT
  • 이 모드를 사용하면 SQL 요청을 지정된 데이터베이스로 라우팅할 수 있다.

Test 환경

아래 3개의 노드를 생성한다.

IP address Port Node type db name Username pg install path
127.0.0.1 1234 Proxy node postgres postgres $PG_INSTALL_HOME/plproxy-test-proxydb
127.0.0.1 5678 Data node pl_db0 postgres $PG_INSTALL_HOME/plproxy-test-pl_db0
127.0.0.1 8765 Data node pl_db1 postgres $PG_INSTALL_HOME/plproxy-test-pl_db1

$PG_INSTALL_HOME/plproxy-test-proxydb

./initdb $PG_DATA_HOME/plproxy-proxydb \
&& ./pg_ctl -D $PG_DATA_HOME/plproxy-proxydb -l logfile -o "-F -p 1234" start \
&& ./createuser -s -p 1234 postgres\
&& ./psql -p 1234 postgres postgres

$PG_INSTALL_HOME/plproxy-test-pl_db0

./initdb $PG_DATA_HOME/plproxy-pl_db0 \
&& ./pg_ctl -D $PG_DATA_HOME/plproxy-pl_db0 -l logfile -o "-F -p 5678" start \
&& ./createdb -p 5678 pl_db0 \
&& ./createuser -s -p 5678 postgres \
&& ./psql -p 5678 pl_db0 postgres

$PG_INSTALL_HOME/plproxy-test-pl_db1

./initdb $PG_DATA_HOME/plproxy-pl_db1 \
&& ./pg_ctl -D $PG_DATA_HOME/plproxy-pl_db1 -l logfile -o "-F -p 8765" start \
&& ./createdb -p 8765 pl_db1 \
&& ./createuser -s -p 8765 postgres \
&& ./psql -p 8765 pl_db1 postgres

PL/Proxy 플러그인 생성

아래 쿼리문을 실행하여 PL/Proxy 플러그인 생성.

create extension plproxy;

Create a PL/Proxy cluster

  1. [Proxy node] PL/Proxy cluster를 생성하고 인스턴스의 이름, IP 주소 및 포트를 지정.
  2. postgres=# CREATE SERVER cluster_srv1 FOREIGN DATA WRAPPER plproxy postgres-# OPTIONS ( postgres(# connection_lifetime '1800', postgres(# disable_binary '1', postgres(# p0 'dbname=pl_db0 host=127.0.0.1 port=5678', postgres(# p1 'dbname=pl_db1 host=127.0.0.1 port=8765' postgres(# ); CREATE SERVER
  3. [Proxy node] 생성된 PL/Proxy cluster에 대한 권한을 postgres 사용자에게 부여
  4. postgres=# grant usage on FOREIGN server cluster_srv1 to postgres; GRANT
  5. [Proxy node] user mapping 생성
  6. postgres=> create user mapping for postgres server cluster_srv1 options (user 'postgres'); CREATE USER MAPPING

테스트 테이블 만들기

[Data node] 각 Data node(pl_db0, pl_db1)에 users라는 테스트 테이블 생성

create table users(userid int, name text);

Test the CLUSTER mode

horizontal sharding test를 위해 아래 단계를 수행한다.

  1. [Data node] 각 Data node(pl_db0, pl_db1)에 user INSERT를 위한 함수 생성
     pl_db1=> CREATE OR REPLACE FUNCTION insert_user(i_id int, i_name text)
     pl_db1-> RETURNS integer AS $$
     pl_db1$>        INSERT INTO users (userid, name) VALUES ($1,$2);
     pl_db1$>        SELECT 1;
     pl_db1$> $$ LANGUAGE SQL;
     CREATE FUNCTION
  2. pl_db0=> CREATE OR REPLACE FUNCTION insert_user(i_id int, i_name text) pl_db0-> RETURNS integer AS $$ pl_db0$> INSERT INTO users (userid, name) VALUES ($1,$2); pl_db0$> SELECT 1; pl_db0$> $$ LANGUAGE SQL; CREATE FUNCTION
  3. [Proxy node] Proxy node(postgres)에 user INSERT를 위한 함수 생성. 이 함수는 각 Data node(pl_db0, pl_db1)에 데이터를 삽입하는 데 사용되는 함수와 이름이 동일하다.
  4. postgres=> CREATE OR REPLACE FUNCTION insert_user(i_id int, i_name text) postgres-> RETURNS integer AS $$ postgres$> CLUSTER 'cluster_srv1'; postgres$> RUN ON ANY; postgres$> $$ LANGUAGE plproxy; CREATE FUNCTION
  5. [Proxy node] Proxy node에 데이터를 읽는 데 사용되는 함수 생성
  6. postgres=> CREATE OR REPLACE FUNCTION get_user_name() postgres-> RETURNS TABLE(userid int, name text) AS $$ postgres$> CLUSTER 'cluster_srv1'; postgres$> RUN ON ALL ; postgres$> SELECT userid,name FROM users; postgres$> $$ LANGUAGE plproxy; CREATE FUNCTION
  7. [Proxy node] Proxy node에 10 test 레코드 INSERT
  8. SELECT insert_user(1001, 'Sven'); SELECT insert_user(1002, 'Marko'); SELECT insert_user(1003, 'Steve'); SELECT insert_user(1004, 'lottu'); SELECT insert_user(1005, 'rax'); SELECT insert_user(1006, 'ak'); SELECT insert_user(1007, 'jack'); SELECT insert_user(1008, 'molica'); SELECT insert_user(1009, 'pg'); SELECT insert_user(1010, 'oracle');
  9. [Data node] 각 Data node(pl_db0, pl_db1)를 확인한다. 데이터를 삽입하는 데 사용되는 함수에는 RUN ON ANY 문이 포함되어 있다. 이것은 Data node(pl_db0, pl_db1) 중 하나에 데이터를 무작위로 삽입한다. 아래의 쿼리문으로 두 개의 Data node(pl_db0, pl_db1)에서 데이터를 찾을 수 있다.
     pl_db1=> select * from users;
      userid |  name
     --------+--------
        1002 | Marko
        1010 | oracle
     (2 rows)
    위 쿼리 결과로 10개의 데이터 레코드가 두 Data node(pl_db0, pl_db1) 사이에 무작위로 분포 되어 있는것을 확인할 수 있다.
  10. pl_db0=> select * from users; userid | name --------+-------- 1001 | Sven 1003 | Steve 1004 | lottu 1005 | rax 1006 | ak 1007 | jack 1008 | molica 1009 | pg (8 rows)
  11. [Proxy node] Proxy node에서 데이터를 읽는 데 사용되는 함수를 호출한다. 이 함수에는 두 Data node(pl_db0, pl_db1) 모두에서 데이터를 읽는 RUN ON ALL 문이 포함되어 있다.
  12. postgres=> SELECT USERID,NAME FROM GET_USER_NAME(); userid | name --------+-------- 1001 | Sven 1003 | Steve 1004 | lottu 1005 | rax 1006 | ak 1007 | jack 1008 | molica 1009 | pg 1002 | Marko 1010 | oracle (10 rows)

SQL replication을 테스트 하려면 다음 단계를 수행한다.

  1. 각 노드에서 사용자 테이블의 레코드를 모두 삭제하는 함수 생성
     pl_db1=> CREATE OR REPLACE FUNCTION trunc_user()
     pl_db1-> RETURNS integer AS $$
     pl_db1$>        truncate table users;
     pl_db1$>        SELECT 1;
     pl_db1$> $$ LANGUAGE SQL;
     CREATE FUNCTION
     postgres=> CREATE OR REPLACE FUNCTION trunc_user()
     postgres-> RETURNS SETOF integer AS $$
     postgres$>     CLUSTER 'cluster_srv1';
     postgres$>     RUN ON ALL;
     postgres$> $$ LANGUAGE plproxy;
     CREATE FUNCTION
  2. pl_db0=> CREATE OR REPLACE FUNCTION trunc_user() pl_db0-> RETURNS integer AS $$ pl_db0$> truncate table users; pl_db0$> SELECT 1; pl_db0$> $$ LANGUAGE SQL; CREATE FUNCTION
  3. [Proxy node] Proxy node에서 테이블의 레코드를 모두 삭제하는 함수를 호출
  4. postgres=> SELECT TRUNC_USER(); trunc_user ------------ 1 1 (2 rows)
  5. [Proxy node] Proxy node에 데이터를 삽입하는 또다른 함수 생성
  6. postgres=> CREATE OR REPLACE FUNCTION insert_user_2(i_id int, i_name text) postgres-> RETURNS SETOF integer AS $$ postgres$> CLUSTER 'cluster_srv1'; postgres$> RUN ON ALL; postgres$> TARGET insert_user; postgres$> $$ LANGUAGE plproxy; CREATE FUNCTION
  7. [Proxy node] Proxy node에서 4개의 테스트 레코드를 삽입
  8. SELECT insert_user_2(1004, 'lottu'); SELECT insert_user_2(1005, 'rax'); SELECT insert_user_2(1006, 'ak'); SELECT insert_user_2(1007, 'jack');
  9. [Data node] 각 Data node(pl_db0, pl_db1)를 확인데이터는 각 Data node(pl_db0, pl_db1)에서 동일하다. 이는 SQL 복제가 성공했음을 나타낸다.
  10. pl_db0=> select * from users; userid | name --------+------- 1004 | lottu 1005 | rax 1006 | ak 1007 | jack (4 rows) pl_db1=> select * from users; userid | name --------+------- 1004 | lottu 1005 | rax 1006 | ak 1007 | jack (4 rows)
  11. [Proxy node] Proxy node에서 데이터를 확인 한다. 이때, 두 Data node 중 하나에서 데이터를 무작위로 읽는 RUN ON ANY 문을 사용하여 실행한다.
  12. postgres=> CREATE OR REPLACE FUNCTION get_user_name_2() postgres-> RETURNS TABLE(userid int, name text) AS $$ postgres$> CLUSTER 'cluster_srv1'; postgres$> RUN ON ANY ; postgres$> SELECT userid,name FROM users; postgres$> $$ LANGUAGE plproxy; CREATE FUNCTION postgres=> SELECT USERID,NAME FROM GET_USER_NAME_2(); userid | name --------+------- 1004 | lottu 1005 | rax 1006 | ak 1007 | jack (4 rows)

Test the CONNECT mode

[Proxy node] CONNECT 모드를 사용하면 Proxy node에서 다른 node에 액세스할 수 있다.

postgres=> CREATE OR REPLACE FUNCTION get_user_name_3()
postgres-> RETURNS TABLE(userid int, name text) AS $$
postgres$>     CONNECT 'dbname=pl_db0 host=127.0.0.1 port=5678';
postgres$> SELECT userid,name FROM users;
postgres$> $$ LANGUAGE plproxy;
CREATE FUNCTION

postgres=> SELECT USERID,NAME FROM GET_USER_NAME_3();
 userid | name
--------+-------
   1004 | lottu
   1005 | rax
   1006 | ak
   1007 | jack
(4 rows)

결론

PL/Proxy는 PostgreSQL 환경에서 데이터베이스 샤딩과 원격 프로시저 호출을 효과적으로 구현할 수 있는 강력한 도구이다. 데이터베이스 분산 환경에서 발생하는 복잡성을 추상화하고, 개발자가 보다 직관적으로 분산 시스템을 설계하고 구현할 수 있게 해준다.

특히 대규모 데이터 처리가 필요한 환경에서 수평적 확장성을 제공하고, 데이터 위치 투명성을 통해 애플리케이션 레벨에서의 복잡성을 줄일 수 있다. PL/Proxy를 활용하면 단일 데이터베이스의 성능 한계를 넘어서 확장 가능한 데이터베이스 아키텍처를 구축할 수 있다.

다만, 분산 트랜잭션을 지원하지 않는다는 제한이 있으므로, 강한 일관성이 요구되는 트랜잭션 처리에는 적합하지 않을 수 있다. 따라서 데이터 모델과 애플리케이션의 특성에 맞게 적절히 활용하는 전략이 필요하다.

PL/Proxy는 읽기/쓰기 분리, 지역 기반 데이터 분산, 대규모 데이터 처리 등 다양한 분산 데이터베이스 시나리오에서 유용하게 활용될 수 있는 PostgreSQL의 강력한 확장 기능이다.

참고 문헌

  1. Use the PL/Proxy plug-in for horizontal sharding - ApsaraDB RDS - Alibaba Cloud
  2. PL/Proxy - PostgreSQL wiki
  3. PL/Proxy 공식 사이트
  4. PL/Proxy GitHub 저장소
728x90
반응형