Docker를 활용해 Rust 와 PostgreSQL를 연결하는 법

고승우·2023년 7월 3일
0
post-thumbnail

PostgreSQL

The PostgreSQL Global Development Group에서 개발하는 오픈소스 ORDBMS이다. 전 세계 사용률은 상위 3개의 DB(Oracle DB, MySQL, Microsoft SQL)에 이어 4위인데, 꾸준히 상승하고 있다.
MySQL에 비해서 SQL 표준을 더 잘 지원하고 기능이 더 강력하며 쿼리가 복잡해질수록 성능이 더 잘 나오는 편이다. 특히 PostGIS를 통한 Geospatial query는 오라클마저 씹어삼키는 강력함을 자랑하며, Citus 익스텐션을 이용하면 그동안 약점으로 지적돼왔던 병렬 인덱싱도 손쉽게 처리 가능하다.

Setting up the database with docker-compose

Docker를 활용해서 PostgreSQL 데이터 베이스를 쉽게 set up 할 수 있다.

Docker-compose file

간단한 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을 활용하여 모든 작업을 정지할 수 있다.

Checking to see if the database is running

DB가 성공적으로 생성되었는지 확인해보자. docker exec -ti {이 부분은 Name이 들어가야 함} bash 명령어를 통해 PostgreSQL 컨테이너의 터미널에 접근할 수 있다. psql -d postgres -U {docker-compose.yaml 파일에 입력한 User} 명령어를 통해 명령어를 확인할 수 있다. \list를 통해 db를 리스트화 할 수 있다.

Set up the Rust project

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"

Connecting to PostgreSQL with Rust

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 데이터 베이스와 연결할 수도 있다.

Creating a table in the database

우리는 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을 삽입할 수 있다.

Inserting values into a 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가 난다.

Querying the database and processing the results

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를 처리했다.

Updating a record in the app_user table

이미 사용한 execute를 사용해서 update를 할 수 있다.

client.execute(
        "UPDATE app_user SET username=$1 WHERE id=$2",
        &[&"jack1", &2],
    )?;

Deleting a record from a table

app_user 테이블에서 id를 활용해서 삭제해보자.

client.execute("DELETE FROM app_user WHERE id=$1", &[&1])?;
client.execute("DELETE FROM app_user WHERE id=$1", &[&3])?;

위 프로그램을 실행하면 성공적으로 기록이 삭제된 것을 확인할 수 있다.

Conclusion

우리는 Docker 와 docker-compose를 활용해 PostgreSQL DB instance를 쉽게 생성하는 법에 대해 알아 보았다. 여기서는 raw SQL 타입을 입력해야 했지만, 큰 프로젝트에선 너무 불편하다. 이럴 때는 ORM 라이브러리를 활용하는게 더 유리하다.

profile
٩( ᐛ )و 

0개의 댓글