The PostgreSQL Global Development Group에서 개발하는 오픈소스 ORDBMS이다. 전 세계 사용률은 상위 3개의 DB(Oracle DB, MySQL, Microsoft SQL)에 이어 4위인데, 꾸준히 상승하고 있다.
MySQL에 비해서 SQL 표준을 더 잘 지원하고 기능이 더 강력하며 쿼리가 복잡해질수록 성능이 더 잘 나오는 편이다. 특히 PostGIS를 통한 Geospatial query는 오라클마저 씹어삼키는 강력함을 자랑하며, Citus 익스텐션을 이용하면 그동안 약점으로 지적돼왔던 병렬 인덱싱도 손쉽게 처리 가능하다.
Docker
를 활용해서 PostgreSQL 데이터 베이스를 쉽게 set up 할 수 있다.
간단한 docker-compose 파일을 활용해 PostgreSQL 데이터 베이스 instance를 만들고 접근해 보자. docker-compose.yaml
파일을 만들고 아래 컨텐츠를 추가하자.
version: "3"
services:
postgres:
image: postgres:14.5
environment:
- POSTGRES_USER=닉네임
- POSTGRES_PASSWORD=비밀번호
- POSTGRES_DB=데이터베이스
ports:
- '5433:5432'
Database server에 접속하기 위해서 ":" 앞에 있는5234
번 port를 이용할 예정이다. docker-compose up
을 활용하여 database instance를 운용할 수 있다.
만약 데이터를 background process로 run하고 싶다면 docker-compose up -d
를 사용할 수 있다. background에서 run함으로써 로그를 생략하고 커맨드 창을 끄더라도 컨테이너가 정지하지 않도록 할 수 있다. docker-compose down
을 활용하여 모든 작업을 정지할 수 있다.
DB가 성공적으로 생성되었는지 확인해보자. docker exec -ti {이 부분은 Name이 들어가야 함} bash
명령어를 통해 PostgreSQL 컨테이너의 터미널에 접근할 수 있다. psql -d postgres -U {docker-compose.yaml 파일에 입력한 User}
명령어를 통해 명령어를 확인할 수 있다. \list
를 통해 db를 리스트화 할 수 있다.
With the database set up out of the way(DB 설정한 상태). 우리는 PostgreSQL DB에 연결하면서 Rust 플젝트를 시작할 수 있다. 그 전에 Cargo.toml
에 필요한 의존성을 추가하자.
[package]
name = "rust-postgresql-tutorial"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
postgres = "0.19"
main.rs
파일을 열고 아래 코드를 입력하자.
use postgres::{Client, Error, NoTls};
fn main() -> Result<(), Error> {
let mut client = Client::connect(
"postgresql://dboperator:operatorpass123@localhost:5243/postgres",
NoTls,
)?;
Ok(())
}
.env
파일을 활용해서 PostgreSQL 데이터 베이스와 연결할 수도 있다.
우리는 Rust code를 활용해서 PostgreSQL 데이터베이스에 table을 추가해보자. 우리는 이전 섹션에서 만든 client
객체를 활용할 것이고 batch_execute()
함수를 호출한다. batch_execute
함수는 semicolon";"으로 구분된 여러개의 statement를 실행할 수 있다. 이 함수는 database schema를 시작할 때 사용된다.
client.batch_execute(
"
CREATE TABLE IF NOT EXISTS app_user (
id SERIAL PRIMARY KEY,
username VARCHAR UNIQUE NOT NULL,
password VARCHAR NOT NULL,
email VARCHAR UNIQUE NOT NULL
)
",
)?;
우리는 위에서 말한 방법인 psql -d postgres -U {username}
와 \dt
커맨드를 활용해서 table 리스트를 만들 수 있다.
docker exec -it rust-postgresql-tutorial_postgres_1 bash
root@4dadd7fdf34b:/# psql -d postgres -U dboperator
psql (14.0 (Debian 14.0-1.pgdg110+1))
Type "help" for help.
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------+-------+------------
public | app_user | table | dboperator
(1 row)
postgres=#
이제 데이터에 table을 삽입할 수 있다.
이제 app_user table에 데이터를 삽입해보자.
client.execute(
"INSERT INTO app_user (username, password, email) VALUES ($1, $2, $3)",
&[&"user1", &"mypass", &"user@test.com"],
)?;
execute
command를 활용해 parameters를 담은 queries를 실행할 수 있게 된다. 이렇게 query들을 parameterize를 하게 되면 SQL injection hack을 방지할 수 있다. 또한 SQL 서버가 parameterized query를 cache하기 때문에 query executing 시간을 개선할 수 있다.
postgres=# SELECT * FROM app_user;
id | username | password | email
----+----------+----------+---------------
1 | user1 | mypass | user@test.com
(1 row)
만약 같은 쿼리를 또 추가한다면 colums를 UNIQUE
로 구성했기 때문에 error가 난다.
SELECT
query를 어떻게 실행하는지 알아보자. app_user
를 추가해보자
// client.execute(
// "INSERT INTO app_user (username, password, email) VALUES ($1, $2, $3)",
// &[&"user1", &"mypass", &"user@test.com"],
// )?;
client.execute(
"INSERT INTO app_user (username, password, email) VALUES ($1, $2, $3)",
&[&"user2", &"mypass2", &"use2@gmail.com"],
)?;
client.execute(
"INSERT INTO app_user (username, password, email) VALUES ($1, $2, $3)",
&[&"user3", &"anotherpass", &"mister3@test.com"],
)?;
이제 데이터를 query해보자.
for row in client.query("SELECT id, username, password, email FROM app_user", &[])? {
let id: i32 = row.get(0);
let username: &str = row.get(1);
let password: &str = row.get(2);
let email: &str = row.get(3);
println!(
"found app user: {}) {} | {} | {}",
id, username, password, email
);
위 코드에선 statement를 execute하고 resulting rows를 반환하는 query()
함수를 사용하고 있다. 우리는 아직 query를 위한 parameter를 갖고 있지 않기 때문에, 빈 parameter의 reference를 &[]
배열로 전해준다. for
문을 활용해 row를 처리했다.
이미 사용한 execute
를 사용해서 update를 할 수 있다.
client.execute(
"UPDATE app_user SET username=$1 WHERE id=$2",
&[&"jack1", &2],
)?;
app_user
테이블에서 id를 활용해서 삭제해보자.
client.execute("DELETE FROM app_user WHERE id=$1", &[&1])?;
client.execute("DELETE FROM app_user WHERE id=$1", &[&3])?;
위 프로그램을 실행하면 성공적으로 기록이 삭제된 것을 확인할 수 있다.
우리는 Docker 와 docker-compose를 활용해 PostgreSQL DB instance를 쉽게 생성하는 법에 대해 알아 보았다. 여기서는 raw SQL 타입을 입력해야 했지만, 큰 프로젝트에선 너무 불편하다. 이럴 때는 ORM 라이브러리를 활용하는게 더 유리하다.