개요
PL/Proxy는 PostgreSQL 데이터베이스에서 원격 프로시저 호출과 수평적 샤딩을 가능하게 하는 절차적 언어 핸들러(Procedural Language handler)이다. 이 도구는 다수의 PostgreSQL 데이터베이스 간 함수 호출을 처리할 수 있으며, 데이터베이스 분산 환경에서 효율적인 데이터 관리와 처리를 지원한다. PL/Proxy를 사용하면 데이터베이스 클러스터 구성, 로드 밸런싱, 데이터 샤딩 등의 복잡한 작업을 상대적으로 간단하게 처리할 수 있다.
설명
PL/Proxy는 PostgreSQL의 확장 기능으로, 여러 데이터베이스 간의 통신을 효율적으로 처리하기 위한 메커니즘을 제공한다. 이 도구는 PostgreSQL 함수 호출을 원격 데이터베이스로 전달하고, 그 결과를 원래 함수 호출자에게 반환한다.
PL/Proxy의 핵심 기능은 다음과 같이 구분할 수 있다:
- 원격 프로시저 호출(RPC): 한 데이터베이스에서 다른 데이터베이스의 함수를 호출할 수 있게 한다.
- 수평적 샤딩(Horizontal Sharding): 특정 기준(해시 값 등)에 따라 데이터를 여러 데이터베이스에 분산 저장할 수 있다.
- SQL 복제(SQL Replication): 동일한 쿼리를 여러 데이터베이스에 동시에 실행할 수 있다.
PL/Proxy는 두 가지 주요 모드를 지원한다:
- CLUSTER 모드: 미리 정의된 데이터베이스 클러스터를 사용하여 여러 데이터베이스에 접근한다. 수평적 샤딩이나 SQL 복제에 유용하다.
- CONNECT 모드: 연결 문자열을 직접 지정하여 특정 데이터베이스에 접근한다. 간단한 원격 함수 호출에 적합하다.
실행 타입에 따라 쿼리 처리 방식도 다양하다:
1. RUN ON ALL: 클러스터의 모든 데이터베이스에 동시에 쿼리 실행
2. RUN ON ANY: 클러스터 내에서 무작위로 하나의 데이터베이스 선택
3. RUN ON : 해시 값을 기준으로 특정 데이터베이스에 매핑하여 실행
특징
PL/Proxy의 주요 특징은 다음과 같다:
- 확장성(Scalability): 데이터를 여러 데이터베이스에 분산하여 대규모 데이터 처리 능력을 향상시킨다.
- 유연성(Flexibility): 데이터베이스 클러스터 구성, 단일 원격 연결, 다양한 실행 모드 등 여러 옵션을 제공한다.
- 투명성(Transparency): 클라이언트 애플리케이션은 복잡한 분산 환경을 인식할 필요 없이 로컬 함수처럼 원격 함수를 호출할 수 있다.
- 분산 트랜잭션 지원 없음: PL/Proxy는 분산 트랜잭션을 지원하지 않으므로, 각 원격 함수 호출은 독립적인 트랜잭션으로 처리된다.
- 병렬 쿼리 실행: RUN ON ALL 모드를 사용하면 여러 데이터베이스에 쿼리를 병렬로 실행할 수 있어 성능 향상이 가능하다.
- 언어 확장성: PostgreSQL의 절차적 언어 시스템을 활용하여 구현되었으므로, SQL 뿐만 아니라 PL/pgSQL 등 다른 절차적 언어와도 함께 사용할 수 있다.
- 로드 밸런싱: RUN ON ANY 모드를 통해 클러스터 내 데이터베이스 간 부하 분산이 가능하다.
빌드 및 설치
- 빌드 이전에, Build를 완료한 Postgres가 필요하다. Postgres 설치시 config install을 아래와 같이 실행한다.
make -C config install
- 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
- [Proxy node] PL/Proxy cluster를 생성하고 인스턴스의 이름, IP 주소 및 포트를 지정.
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
- [Proxy node] 생성된 PL/Proxy cluster에 대한 권한을 postgres 사용자에게 부여
postgres=# grant usage on FOREIGN server cluster_srv1 to postgres; GRANT
- [Proxy node] user mapping 생성
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를 위해 아래 단계를 수행한다.
- [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
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
- [Proxy node] Proxy node(postgres)에 user INSERT를 위한 함수 생성. 이 함수는 각 Data node(pl_db0, pl_db1)에 데이터를 삽입하는 데 사용되는 함수와 이름이 동일하다.
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
- [Proxy node] Proxy node에 데이터를 읽는 데 사용되는 함수 생성
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
- [Proxy node] Proxy node에 10 test 레코드 INSERT
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');
- [Data node] 각 Data node(pl_db0, pl_db1)를 확인한다. 데이터를 삽입하는 데 사용되는 함수에는 RUN ON ANY 문이 포함되어 있다. 이것은 Data node(pl_db0, pl_db1) 중 하나에 데이터를 무작위로 삽입한다. 아래의 쿼리문으로 두 개의 Data node(pl_db0, pl_db1)에서 데이터를 찾을 수 있다.
위 쿼리 결과로 10개의 데이터 레코드가 두 Data node(pl_db0, pl_db1) 사이에 무작위로 분포 되어 있는것을 확인할 수 있다.pl_db1=> select * from users; userid | name --------+-------- 1002 | Marko 1010 | oracle (2 rows)
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)
- [Proxy node] Proxy node에서 데이터를 읽는 데 사용되는 함수를 호출한다. 이 함수에는 두 Data node(pl_db0, pl_db1) 모두에서 데이터를 읽는 RUN ON ALL 문이 포함되어 있다.
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을 테스트 하려면 다음 단계를 수행한다.
- 각 노드에서 사용자 테이블의 레코드를 모두 삭제하는 함수 생성
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
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
- [Proxy node] Proxy node에서 테이블의 레코드를 모두 삭제하는 함수를 호출
postgres=> SELECT TRUNC_USER(); trunc_user ------------ 1 1 (2 rows)
- [Proxy node] Proxy node에 데이터를 삽입하는 또다른 함수 생성
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
- [Proxy node] Proxy node에서 4개의 테스트 레코드를 삽입
SELECT insert_user_2(1004, 'lottu'); SELECT insert_user_2(1005, 'rax'); SELECT insert_user_2(1006, 'ak'); SELECT insert_user_2(1007, 'jack');
- [Data node] 각 Data node(pl_db0, pl_db1)를 확인데이터는 각 Data node(pl_db0, pl_db1)에서 동일하다. 이는 SQL 복제가 성공했음을 나타낸다.
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)
- [Proxy node] Proxy node에서 데이터를 확인 한다. 이때, 두 Data node 중 하나에서 데이터를 무작위로 읽는 RUN ON ANY 문을 사용하여 실행한다.
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의 강력한 확장 기능이다.
참고 문헌
'Postgres > Extension' 카테고리의 다른 글
Libsodium 라이브러리: 현대적인 암호화의 솔루션 (4) | 2025.05.25 |
---|---|
pgcrypto의 단방향 암호화 알고리즘 (1) | 2025.05.21 |
pgvector Extension의 설치 및 환경구성 (2) | 2025.02.16 |