1
관계형 데이터베이스 연동
관계형 데이터베이스는 데이터를 행과 열로 이루어진 테이블 형태로 저장하는 데이터베이스 시스템입니다. Rust에서 관계형 데이터베이스에 연결하려면 별도의 크레이트를 사용해야 합니다. 가장 대표적인 크레이트는 러스트-포스그레스 크레이트와 러스트-mysql 크레이트입니다.
PostgreSQL 연동
PostgreSQL은 오픈 소스 객체-관계형 데이터베이스 관리 시스템(ORDBMS)입니다. 러스트-포스그레스 크레이트를 이용하면 Rust 애플리케이션에서 PostgreSQL 데이터베이스에 쉽게 연결할 수 있습니다.
extern crate postgres;
use postgres::{Connection, TlsMode};
fn main() {
let conn = Connection::connect(
"postgresql://username:password@localhost/database_name",
TlsMode::None,
).unwrap();
// 쿼리 실행
conn.execute("CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
email TEXT NOT NULL
)", &[]).unwrap();
// 데이터 삽입
conn.execute("INSERT INTO users (username, email) VALUES ($1, $2)",
&[&"alice", &"alice@example.com"]).unwrap();
// 데이터 조회
let rows = conn.query("SELECT id, username, email FROM users", &[]).unwrap();
for row in rows {
let id: i32 = row.get(0);
let username: String = row.get(1);
let email: String = row.get(2);
println!("id: {}, username: {}, email: {}", id, username, email);
}
}
Connection::connect 함수를 사용하여 PostgreSQL 데이터베이스에 연결합니다. 연결 URL은 postgresql://username:password@host/database_name 형식을 따릅니다.
execute 메서드를 사용하여 SQL 문을 실행할 수 있습니다. 매개변수로 SQL 문과 바인딩할 값의 배열을 전달합니다. 위 예제에서는 테이블을 생성하고, 데이터를 삽입하는 SQL 문을 실행합니다.
query 메서드를 사용하여 SELECT 쿼리를 실행하고 결과 행을 반복합니다. 각 행의 열 값은 get 메서드를 통해 인덱스로 접근할 수 있습니다.
러스트-포스그레스 크레이트는 PostgreSQL 데이터베이스와 통신할 수 있는 강력한 기능을 제공합니다. 트랜잭션 관리, 프리페어드 문, 비동기 쿼리 등 다양한 기능을 지원합니다.
MySQL 연동
MySQL은 오픈 소스 관계형 데이터베이스 관리 시스템입니다. 러스트-mysql 크레이트를 이용하면 Rust 애플리케이션에서 MySQL 데이터베이스에 연결할 수 있습니다.
extern crate mysql;
use mysql::*;
use mysql::prelude::*;
fn main() {
let url = "mysql://username:password@localhost/database_name";
let pool = Pool::new(url).unwrap();
let mut conn = pool.get_conn().unwrap();
// 쿼리 실행
conn.query_drop(
"CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username TEXT NOT NULL,
email TEXT NOT NULL
)"
).unwrap();
// 데이터 삽입
conn.exec_drop(
"INSERT INTO users (username, email) VALUES (:username, :email)",
params! {
"username" => "alice",
"email" => "alice@example.com"
},
).unwrap();
// 데이터 조회
let selected_users = conn
.query_map(
"SELECT id, username, email FROM users",
|(id, username, email)| {
println!("id: {}, username: {}, email: {}", id, username, email);
},
)
.unwrap();
}
Pool::new 함수를 사용하여 MySQL 데이터베이스 연결 풀을 생성합니다. 연결 URL은 mysql://username:password@host/database_name 형식을 따릅니다.
get_conn 메서드를 호출하여 연결 풀에서 연결을 가져옵니다. 그런 다음 query_drop, exec_drop, query_map 등의 메서드를 사용하여 SQL 문을 실행할 수 있습니다.
위 예제에서는 테이블을 생성하고, 데이터를 삽입하며, SELECT 쿼리를 실행하여 결과를 출력합니다.
러스트-mysql 크레이트는 MySQL 데이터베이스와 통신할 수 있는 다양한 기능을 제공합니다. 트랜잭션 관리, 프리페어드 문, 비동기 쿼리 등의 기능을 지원합니다. 또한 연결 풀링 및 로드 밸런싱과 같은 고급 기능도 제공합니다.
관계형 데이터베이스는 데이터 무결성과 일관성을 보장하며, 안정적인 데이터 저장 및 검색을 제공합니다. 그러나 대규모 데이터를 다룰 때는 성능 문제가 발생할 수 있습니다. 이러한 경우 NoSQL 데이터베이스를 고려해볼 수 있습니다.
NoSQL 데이터베이스 연동
NoSQL(Not only SQL) 데이터베이스는 관계형 데이터베이스와 달리 데이터를 키-값, 문서, 열 등의 형태로 저장합니다. 대규모 데이터 처리와 확장성에 강점이 있습니다.
Redis 연동
Redis는 오픈 소스 인메모리 키-값 데이터베이스입니다. 고성능과 확장성을 제공하며, 캐싱, 메시지 브로커, 세션 관리 등 다양한 용도로 활용됩니다. redis-rs 크레이트를 이용하면 Rust 애플리케이션에서 Redis에 연결할 수 있습니다.
extern crate redis;
use redis::Commands;
fn main() {
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
let mut con = client.get_connection().unwrap();
// 데이터 추가
let _: () = redis::cmd("SET")
.arg("key")
.arg("value")
.query(&mut con)
.unwrap();
// 데이터 조회
let value: String = redis::cmd("GET")
.arg("key")
.query(&mut con)
.unwrap();
println!("value: {}", value);
}
redis::Client::open 함수를 사용하여 Redis에 연결할 클라이언트를 생성합니다. 호스트와 포트를 지정할 수 있습니다.
get_connection 메서드를 호출하여 Redis 연결을 가져옵니다. 그런 다음 redis::cmd 함수를 사용하여 Redis 명령어를 실행할 수 있습니다.
위 예제에서는 "SET" 명령어를 사용하여 키-값 쌍을 저장하고, "GET" 명령어를 사용하여 값을 조회합니다.
Redis는 다양한 데이터 구조와 명령어를 제공합니다. 문자열, 해시, 리스트, 집합, 정렬 집합 등의 데이터 유형을 지원합니다. 또한 Pub/Sub 기능, 트랜잭션, Lua 스크립팅 등의 기능도 제공합니다.
MongoDB 연동
MongoDB는 오픈 소스 문서 지향 NoSQL 데이터베이스입니다. 데이터를 JSON과 유사한 BSON 형식으로 저장합니다. mongodb 크레이트를 이용하면 Rust 애플리케이션에서 MongoDB에 연결할 수 있습니다.
extern crate bson;
extern crate mongodb;
use bson::{doc, Document};
use mongodb::{Client, options::ClientOptions};
fn main() {
let client_options = ClientOptions::parse("mongodb://localhost:27017").unwrap();
let client = Client::with_options(client_options).unwrap();
let db = client.database("mydb");
let coll = db.collection("users");
// 데이터 추가
let doc = doc! {
"username": "alice",
"email": "alice@example.com"
};
coll.insert_one(doc, None).unwrap();
// 데이터 조회
let filter = doc! {};
let cursor = coll.find(filter, None).unwrap();
for doc in cursor {
let doc = doc.unwrap();
println!("{}", doc);
}
}
ClientOptions::parse 함수를 사용하여 MongoDB 연결 옵션을 생성합니다. 연결 URL은 mongodb://host:port 형식을 따릅니다.
Client::with_options 함수를 호출하여 MongoDB 클라이언트를 생성합니다. 그런 다음 database 메서드와 collection 메서드를 사용하여 데이터베이스와 컬렉션을 선택할 수 있습니다.
insert_one 메서드를 호출하여 문서를 추가할 수 있습니다. find 메서드를 사용하여 문서를 조회할 수 있습니다. 반환된 커서를 반복하여 문서를 출력합니다.
bson 크레이트를 활용하면 BSON 데이터를 쉽게 생성하고 처리할 수 있습니다. doc! 매크로를 사용하면 JSON 형식으로 BSON 문서를 작성할 수 있습니다.
MongoDB는 스키마가 없는 문서 지향 데이터베이스로, 유연성과 확장성이 뛰어납니다. 그러나 데이터 무결성을 보장하지 않기 때문에 적절한 설계와 관리가 필요합니다.
NoSQL 데이터베이스는 대규모 데이터 처리와 확장성이 필요한 경우에 유용합니다. 그러나 관계형 데이터베이스와 다른 특성을 가지고 있기 때문에, 사용 사례에 따라 적절한 데이터베이스를 선택해야 합니다.
ORM(Object-Relational Mapping) 사용
ORM(Object-Relational Mapping)은 객체 지향 프로그래밍 언어에서 관계형 데이터베이스를 사용할 때 발생하는 패러다임 불일치 문제를 해결하기 위한 기술입니다. ORM 라이브러리를 사용하면 SQL 코드를 직접 작성하지 않고도 데이터베이스와 상호작용할 수 있습니다.
Rust에서 가장 많이 사용되는 ORM 라이브러리는 디젤(Diesel) 입니다. 디젤은 PostgreSQL, MySQL, SQLite 데이터베이스를 지원하며, 다양한 기능과 강력한 쿼리 빌더를 제공합니다.
디젤 ORM 사용하기
디젤을 사용하려면 먼저 Cargo.toml 파일에 디펜던시를 추가해야 합니다.
[dependencies]
diesel = { version = "2.0.0", features = ["postgres"] }
여기서는 PostgreSQL 데이터베이스를 사용하므로 features = ["postgres"] 옵션을 추가했습니다.
다음으로 모델 구조체와 데이터베이스 테이블을 매핑해야 합니다. 이를 위해 #[derive(Queryable, Insertable)] 어트리뷰트를 사용합니다.
#[derive(Queryable, Insertable)]
#[table_name = "users"]
pub struct User {
pub id: i32,
pub username: String,
pub email: String,
}
Queryable 트레이트는 데이터베이스에서 데이터를 가져올 때 사용됩니다. Insertable 트레이트는 데이터베이스에 데이터를 삽입할 때 사용됩니다.
디젤 CLI 를 사용하여 마이그레이션 파일을 생성할 수 있습니다. 마이그레이션 파일은 데이터베이스 스키마를 관리하는 데 사용됩니다.
diesel migration generate create_users_table
생성된 마이그레이션 파일에서 up.sql 파일을 편집하여 테이블 생성 쿼리를 작성합니다.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR NOT NULL,
email VARCHAR NOTNULL UNIQUE
)
그런 다음 down.sql 파일에 테이블 삭제 쿼리를 작성합니다.
DROP TABLE users
이제 Rust 코드에서 디젤을 사용하여 데이터베이스와 상호작용할 수 있습니다.
extern crate diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;
fn main() {
let connection = PgConnection::establish("postgres://username:password@localhost/database_name")
.expect("Error connecting to database");
// 데이터 삽입
let new_user = User {
id: 0,
username: "alice".to_string(),
email: "alice@example.com".to_string(),
};
diesel::insert_into(users::table)
.values(&new_user)
.execute(&connection)
.expect("Error inserting new user");
// 데이터 조회
let results = users::table
.limit(5)
.load::<User>(&connection)
.expect("Error loading users");
println!("Displaying {} users", results.len());
for user in results {
println!("{}", user.username);
}
}
PgConnection::establish 함수를 사용하여 PostgreSQL 데이터베이스에 연결합니다.
insert_into 메서드를 호출하여 새로운 사용자를 삽입할 수 있습니다. values 메서드에 모델 객체를 전달하면 디젤이 자동으로 해당 필드를 매핑합니다.
load 메서드를 사용하여 데이터를 조회할 수 있습니다. 쿼리 빌더를 통해 다양한 조건을 추가할 수 있습니다.
디젤은 기본 키 자동 증가, 관계 매핑, 트랜잭션 관리, 마이그레이션, 실행 계획 검사 등 많은 기능을 제공합니다. 또한 디젤이 생성하는 쿼리는 정적으로 검사되므로 런타임 에러를 방지할 수 있습니다.
비동기 ORM
디젤은 기본적으로 동기식 입출력을 사용합니다. 즉, 데이터베이스 작업이 완료될 때까지 프로그램이 블로킹됩니다. 이는 단일 스레드 애플리케이션에서는 문제가 되지 않지만, 멀티 스레드 또는 비동기 프로그래밍에서는 성능 병목 현상이 발생할 수 있습니다.
이번에는 Rust의 비동기 프로그래밍 모델인 async/await과 함께 ORM을 사용하는 방법에 대해 알아보겠습니다. 비동기 ORM을 사용하면 프로그램의 응답성과 확장성을 향상시킬 수 있습니다.
Rust에서 가장 많이 사용되는 비동기 ORM 라이브러리는 씨쿼엘(SeaQuery) 입니다. 씨쿼엘은 PostgreSQL, MySQL, SQLite 데이터베이스를 지원하며, async/await 기반의 비동기 쿼리 실행 기능을 제공합니다.
use sea_query::{Query, tests_cfg};
#[derive(Queryable, Insertable)]
struct User {
id: i32,
name: String,
email: Option<String>,
}
let db = Database::new("postgres://user:pass@localhost/db").await?;
let new_user = User {
id: 0,
name: "Alice".to_owned(),
email: Some("alice@example.com".to_owned()),
};
// 데이터 삽입
let insert_res = Query::insert()
.into_table(User::Table)
.values(&new_user)
.build()
.execute(&db)
.await?;
// 데이터 조회
let select_res = Query::select()
.from(User::Table)
.filter(User::Name.eq("Alice"))
.build()
.execute(&db)
.await?;
for user in select_res {
println!("{:?}", user);
}
씨쿼엘은 쿼리 빌더 스타일의 API를 제공합니다. Query::insert, Query::select 등의 메서드를 사용하여 쿼리를 구성할 수 있습니다.
execute 메서드에 await 키워드를 사용하여 비동기 실행을 합니다. 이를 통해 프로그램은 데이터베이스 작업이 완료될 때까지 대기하지 않고 다른 작업을 수행할 수 있습니다.
씨쿼엘은 디젤과 유사한 기능을 제공하며, 마이그레이션과 관계 매핑도 지원합니다. 또한 씨쿼엘은 타입 안전성과 컴파일 타임 검사를 제공하여 런타임 에러를 방지합니다.
비동기 ORM을 사용하면 높은 응답성과 확장성을 얻을 수 있습니다. 특히 많은 수의 동시 연결을 처리해야 하는 웹 애플리케이션이나 마이크로서비스에서 유용합니다. 그러나 비동기 프로그래밍은 코드 복잡성이 증가할 수 있으므로, 프로젝트의 요구사항과 개발자의 숙련도를 고려하여 적절한 ORM 라이브러리를 선택해야 합니다.
실시간 데이터 스트림 처리
실시간 데이터 스트림은 연속적으로 생성되는 대량의 데이터를 말합니다. 예를 들어, 센서 데이터, 로그 데이터, 금융 거래 데이터 등이 있습니다. 이러한 데이터를 효율적으로 처리하고 분석하는 것은 매우 중요합니다.
Rust에서는 벡터나 스트림 라이브러리를 사용하여 실시간 데이터 스트림을 처리할 수 있습니다. 이번에는 토커(Tokio) 와 벡터 라이브러리를 활용하는 방법에 대해 알아보겠습니다.
토커를 이용한 실시간 데이터 스트림 처리
토커는 Rust의 비동기 프로그래밍을 위한 런타임입니다. 이벤트 드리븐 방식으로 동작하며, 고성능의 비동기 I/O 작업을 지원합니다.
다음은 TCP 소켓을 통해 실시간 데이터 스트림을 처리하는 예제입니다.
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpListener,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = vec![0; 1024];
loop {
let n = match socket.read(&mut buf).await {
Ok(n) if n == 0 => return,
Ok(n) => n,
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
return;
}
};
// 데이터 스트림 처리 로직
process_data(&buf[..n]);
if let Err(e) = socket.write_all(&buf[..n]).await {
eprintln!("failed to write to socket; err = {:?}", e);
return;
}
}
});
}
}
fn process_data(data: &[u8]) {
// 실시간 데이터 스트림 처리 로직 구현
// ...
}
TcpListener::bind를 사용하여 TCP 소켓을 열고 수신 대기합니다. 새로운 연결이 수신되면 tokio::spawn을 통해 별도의 작업을 생성합니다.
각 작업에서는 socket.read를 호출하여 데이터 스트림을 읽어옵니다. 읽어온 데이터는 process_data 함수에서 처리됩니다.
process_data 함수에서는 실시간 데이터 스트림을 처리하는 로직을 구현해야 합니다. 예를 들어, 데이터 필터링, 집계, 변환 등의 작업을 수행할 수 있습니다.
벡터를 이용한 실시간 데이터 스트림 처리
벡터는 Rust의 표준 라이브러리에 포함된 스트림 처리 라이브러리입니다. 데이터 변환, 필터링, 집계 등의 작업을 수행할 수 있습니다.
다음은 벡터를 사용하여 실시간 데이터 스트림을 처리하는 예제입니다.
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let data_stream = Arc::new(Mutex::new(Vec::new()));
// 데이터 생성 스레드
let data_stream_clone = Arc::clone(&data_stream);
let producer = thread::spawn(move || {
loop {
let mut stream = data_stream_clone.lock().unwrap();
stream.push(rand::random::<u64>());
thread::sleep(Duration::from_millis(100));
}
});
// 데이터 처리 스레드
let consumer = thread::spawn(move || {
loop {
let stream = data_stream.lock().unwrap();
if !stream.is_empty() {
let processed_data: Vec<u64> = stream
.iter()
.filter(|&x| x % 2 == 0) // 짝수 필터링
.map(|x| x * 2) // 값 두 배로 변환
.collect();
println!("Processed data: {:?}", processed_data);
}
thread::sleep(Duration::from_secs(1));
}
});
producer.join().unwrap();
consumer.join().unwrap();
}
이 예제에서는 두 개의 스레드를 생성합니다. 하나는 데이터를 생성하는 생산자 스레드이고, 다른 하나는 데이터를 처리하는 소비자 스레드입니다.
Arc 와 Mutex 를 사용하여 스레드 간에 안전하게 데이터 스트림을 공유합니다.
생산자 스레드는 무작위로 데이터를 생성하여 벡터에 추가합니다.
소비자 스레드는 벡터에서 데이터를 가져와 filter 와 map 과 같은 벡터 연산을 수행합니다. 이 예제에서는 짝수만 필터링하고, 값을 두 배로 변환합니다.
filter, map, collect 등의 메서드를 조합하여 다양한 데이터 처리 로직을 구현할 수 있습니다.
벡터는 iterator 트레이트를 구현하여 다양한 연산을 제공합니다. 또한 rayon 과 같은 병렬 처리 라이브러리와 결합하여 성능을 더욱 향상시킬 수 있습니다.
실시간 데이터 스트림 처리는 IoT 센서 데이터 분석, 로그 모니터링, 금융 거래 처리 등 다양한 분야에서 활용됩니다. 토커나 벡터 라이브러리를 사용하면 Rust에서 효율적이고 안전한 실시간 데이터 스트림 처리가 가능합니다.
참고 자료
- The Rust Programming Language by Steve Klabnik and Carol Nichols
- Rust by Example
- Rust Postgres Driver Documentation
- Rust MySQL Driver Documentation
- Redis-rs Documentation
- MongoDB Rust Driver Documentation
- Diesel ORM Documentation
- SeaQuery ORM Documentation
- Tokio Runtime Documentation
- Rust Standard Library Documentation
한 고대 문서 이야기
여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오
gospel79.tistory.com
유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법
올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을
stock79.tistory.com
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
'IT > Rust 기초 완전 정복' 카테고리의 다른 글
Rust 프로젝트 구조와 모듈화 (28) (0) | 2024.04.25 |
---|---|
Rust 컴파일러와 빌드 도구 이해하기 (26) (1) | 2024.04.25 |
Rust 패키지 관리자 활용법 (27) (0) | 2024.04.25 |
Rust 입출력과 파일 시스템 다루기 (22) (1) | 2024.04.25 |
Rust 웹 프로그래밍과 프레임워크 (24) (0) | 2024.04.25 |
댓글