반응형

◆ 서버에 부하가 발생하는 과정

 1. 서버의 물리적인 메모리보다 더 많은 양의 메모리를 할당

 2. 많은 양의 메모리 스와핑 발생

 3. 프로그램 실행 속도의 저하

 4. 메모리의 할당량이 증가

 5. 메모리 할당 함수가 null을 반환

 6. 메모리 할당 함수가 반환하는 null에 적절한 처리가 없을경우 비정상적인 종료가 발생

서버 부하 도식화


◆ 데이터베이스에 부하가 발생하는 과정

 1. 디스크의 최대 처리가능한 속도를 넘는 디스크 I/O 명령(DB 질의)가 쌓임

 2. 메모리 사용량이 증가

 3. 메모리 할당 함수가 null을 반환

 4. 메모리 할당 함수가 반환하는 null에 적절한 처리가 없을경우 비정상적인 종료가 발생

반응형

'Programming Language' 카테고리의 다른 글

[Network] 네트워크 부하로 발생하는 현상  (0) 2022.12.13
반응형

■ 스마트 포인터

스마트 포인터는 메모리의 주소 값을 갖는 변수로 알고 있는 포인터처럼 동작합니다. 또한 빌리기만 하는 참조자와 달리 가리키고 있는 데이터를 소유하며, 추가적인 메타데이터와 기능들을 가지고 있는 데이터 구조입니다.

 

스마트 포인터는 기본적으로 구조체(Struct) 형으로 정의하며, Deref(std::ops::Deref) 와 Drop(std::ops::Drop) 트레잇을 구현해야 합니다. Deref 트레잇은 스마트 포인터의 인스턴스가 참조자처럼 동작하도록하고, Drop 트레잇은 스마트 포인터의 인스턴스가 스코프 밖으로 벗어났을 때 스마트 포인터의 메모리와 소유권을 정리하고 원하는 로직을 커스터마이징 할 수 있도록 합니다.

 

자세한 사용 예시는 Box<T> 와 동일한 스마트 포인터를 만들어보면서 설명하겠습니다.


◆ Box<T>타입과 유사한 커스텀 스마트 포인터 활용 예시

use std::ops::Deref;
use std::ops::Drop;

struct MyBox<T>(T);

impl<T>MyBox<T>{
    fn new(x : T) -> MyBox<T>{
       MyBox(x)
    }
}

impl<T>Deref for MyBox<T>{
    type Target = T;

    fn deref(&self) -> &T {
       println!("Smart Pointer deref call !!");
       &self.0
    }
}

impl<T>Drop for MyBox<T>{
    fn drop(&mut self) {
       println!("Smart Pointer drop !!")
    }
}

#[cfg(test)]
mod tests {
    use crate::MyBox;

    #[test]
    fn my_box_test() {
        let my_box = MyBox::new(10);
        assert_eq!(10, *my_box); // deref 메소드가 호출됨
    }// my_box_test 함수가 종료되면서 drop 메소드가 호출됨
}

■ Box<T>

러스트는 스택에 데이터를 저장할 때 해당 데이터의 사이즈를 알 수 없으면, 컴파일 타임에서 에러로 표시합니다. 데이터의 사이즈를 확인할 수 없는 경우는 크게 트레잇 오브젝트를 활용할 때나 재귀적 타입을 활용할 때를 예시로 들 수 있습니다.

이런 경우 박스를 활용할 수 있는데, 박스는 컴파일 타임에서 사이즈를 알 수 없는 타입의 데이터를 힙 공간에 저장하고,  포인터를 스택에 저장함으로써 해당 데이터를 사용할 수 있도록 합니다.

또한 방대한 양의 데이터의 소유권을 옮길 때 러스트는 스택에 데이터를 복사 하는데 이는 메모리상에 부하를 야기하고, 성능을 저하시킬 수 있습니다. 그래서 박스는 방대한 양의 데이터를 힙 공간에 저장하고 스택에서 포인터를 저장함으로써 소유권이 옮겨졌을 때 스택안의 포인터만 복사되어 성능을 높일 수 있습니다.


◆ 사이즈를 알 수 없는 재귀적 타입의 Box<T> 활용 예시

// 재귀적 타입인 List 열거형을 Box로 정의하지 않으면, 무한 재귀로 데이터의
// 사이즈를 알 수 없기 때문에 컴파일 타임에서 오류를 표시한다.
#[derive(Debug)]
enum List{
	Cons(i32, Box<List>),
	Nil,
}

fn main() {
    let list = List::Cons(1, 
		Box::new(List::Cons(2,
			Box::new(List::Nil))));
    println!("list : {:?}", list);

    // 출력 결과
    // list : Cons(1, Cons(2, Nil))
}
반응형
반응형

■ anyhow

anyhow는 std::error::error 트레잇의 바운드 작업을 통해 에러를 처리하기 때문에 Rust 응용 프로그램에서 발생할 수 있는 관용적인 오류를 쉽게 처리할 수 있으며, 다양한 기능을 제공합니다.


◆ anyhow!

anyhow 메크로는 인자로 &str, String, std::error::error 트레잇에서 취급하는 모든 에러 타입을 사용할 수 있습니다. 반환값은 anyhow::Error 타입입니다.

use anyhow::{anyhow, Error};

// &str 사용 예시
fn anyhow_test() -> Result<(), Error>{
	return Err(anyhow!("test error"));
}

// String 사용 예시
fn anyhow_test() -> Result<(), Error>{
	return Err(anyhow!(String::from("test error")));
}

// std::io::Error 사용 예시
fn anyhow_test() -> Result<(), Error>{
	return Err(anyhow!(std::io::Error::from(std::io::ErrorKind::NotFound)));
}

◆bail!

bail 메크로는 anyhow 메크로와 동일하게 인자로 &str, String, std::error::error 트레잇에서 취급하는 모든 에러 타입을 사용할 수 있습니다. 반환값은 return Err(anyhow!(...)) 와 같습니다. 따라서 bail 메크로를 쓰는 스코프는 반환값이 Result<_, anyhow::Error> 이어야 합니다.

use anyhow::{anyhow, bail, Error};

// &str 사용 예시
fn bail_test() -> Result<(), Error>{
	bail!("test error");

	// * 동일한 결과
	// return Err(anyhow!("test error"));
}

// String 사용 예시
fn bail_test() -> Result<(), Error>{
	bail!(String::from("test error"));

	// * 동일한 결과
	// return Err(anyhow!(String::from("test error")));
}

// std::io::Error 사용 예시
fn bail_test() -> Result<(), Error>{
	bail!(std::io::Error::from(std::io::ErrorKind::NotFound))

	// * 동일한 결과
	// return Err(anyhow!(std::io::Error::from(std::io::ErrorKind::NotFound)));
}

ensure!

ensure 메크로는 첫번째 인자로 bool 타입을 사용할 수 있으며, 두번째 인자로 다른 메크로와 동일하게 &str, String, std::error::error 트레잇에서 취급하는 모든 에러 타입을 사용할 수 있습니다.

첫번째 인자가 true 일 경우 어떠한 값도 반환하지 않고 종료되며, false 일 경우 두번째 인자에 해당하는 에러를 return Err(anyhow!(...)) 와 같이 반환합니다. 따라서 ensure 메크로를 쓰는 스코프는 반환값이 Result<_, anyhow::Error> 이어야 합니다.

use anyhow::{anyhow, ensure, Error};

// &str 사용 예시
fn ensure_test() -> Result<(), Error>{
	ensure!(false, "test error");
	// * 첫번째 인자가 false일 경우 아래와 동일한 결과
	// return Err(anyhow!("test error"));

	// * 첫번째 인자가 true일 경우를 위해 컴파일러는 Result의 반환을 유도합니다.
	Ok(())
}

// String 사용 예시
fn ensure_test() -> Result<(), Error>{
	ensure!(false, String::from("test error"));
	// * 첫번째 인자가 false일 경우 아래와 동일한 결과
	// return Err(anyhow!(String::from("test error")));

	// * 첫번째 인자가 true일 경우를 위해 컴파일러는 Result의 반환을 유도합니다.
	Ok(())
}

// std::io::Error 사용 예시
fn ensure_test() -> Result<(), Error>{
	ensure!(false, std::io::Error::from(std::io::ErrorKind::NotFound));
	// * 첫번째 인자가 false일 경우 아래와 동일한 결과
	// return Err(anyhow!(std::io::Error::from(std::io::ErrorKind::NotFound)));

	// * 첫번째 인자가 true일 경우를 위해 컴파일러는 Result의 반환을 유도합니다.
	Ok(())
}

◆ context method

Context trait 에서 제공하는 context 메서드는 메서드를 호출한 주체의 Result 에 따라 구분 작업을 합니다.

주체의 Result 가 Ok 일 경우 context의 Result 도 Ok를 반환하고, Err 일 경우 context에 인자로 넣은 Error를 반환합니다. 또한 인자로 &str, String, std::error::error 트레잇에서 취급하는 모든 에러 타입을 사용할 수 있으며, 반환값은 Result<_, anyhow::Error> 입니다.

use anyhow::{bail, Context, Error};

// 에러를 반환하는 예시 함수
fn bail_test() -> Result<(), Error>{
	bail!("bail error")
}

// &str 사용 예시
fn context_test() -> Result<(), Error>{
	bail_test().context("context error")
    
    // * bail_text() 반환값에 따른 구분 작업
    // 1. Ok(()) 일 경우
    // Ok(()) 반환
    
    // 2. Err(Error) 일 경우
    // Err(anyhow!("context error")) 반환
}

// String 사용 예시
fn context_test() -> Result<(), Error>{
	bail_test().context(String::from("context error"))
    
    // * bail_text() 반환값에 따른 구분 작업
    // 1. Ok(()) 일 경우
    // Ok(()) 반환
    
    // 2. Err(Error) 일 경우
    // Err(anyhow!(String::from("context error"))) 반환
}

// std::io::Error 사용 예시
fn context_test() -> Result<(), Error>{
	bail_test().context(std::io::Error::from(std::io::ErrorKind::NotFound))
    
    // * bail_text() 반환값에 따른 구분 작업
    // 1. Ok(()) 일 경우
    // Ok(()) 반환
    
    // 2. Err(Error) 일 경우
    // Err(anyhow!(std::io::Error::from(std::io::ErrorKind::NotFound))) 반환
}

반응형
반응형

■ 트레잇

트레잇은 타입들이 공통적으로 갖는 동작에 대해 추상화하도록 해줍니다. 이는 Java의 Interface에서 추상화 함수를 정의함으로써 강제력을 제공하는것과 유사합니다. 트레잇을 제네릭 파라미터의 타입으로 사용하는 상황에서 트레잇 바운드를 통해 서로 다른 구조체에 연관성을 제공할 수 있습니다.


◆ 트레잇 구현

Animal 트레잇 타입으로 구현한 Dog는 추상화된 custom_bark 메소드와 구현된 common_bark 메소드 두가지 메소드를 갖습니다. custom_bark 메소드는 Dog 구현부(impl)에서 강제적으로 정의가 되어야 하지만, common_bark 메소드는 정의하지 않고 Animal 트레잇 타입으로 구현한 모든 구조체에서 사용할 수 있습니다.

trait Animal{
    // 선언부만 정의
    fn custom_bark(&self);

    // 선언부,구현부 정의
    fn common_bark(&self) {
        println!("Common Bark");
    }
}

struct Dog{
    Pomeranian : String,
    Poodle : String,
    Dashshund : String,
}

impl Animal for Dog {
    // common_bark 는 Animal 트레잇에서 구현되어 있기 때문에 Dog에서 따로 구현하지 않는다.

    fn custom_bark(&self){
        println!("Pomeranian Bark : {}", self.Pomeranian);
        println!("Poodle Bark : {}", self.Poodle);
        println!("Dashshund Bark : {}", self.Dashshund);
    }
}

fn main(){
    let dog = Dog{
        Pomeranian : String::from("낑낑"),
        Poodle : String::from("멍멍"),
        Dashshund : String::from("댕댕")
    };

    dog.common_bark();
    dog.custom_bark();
}

▶ 출력 결과

Common Bark
Pomeranian Bark : 낑낑
Poodle Bark : 멍멍
Dashshund Bark : 댕댕

◆ 트레잇 바운드

트레잇 바운드는 서로 다른 구조체가 동일한 트레잇을 구현하고 있는 상황에서 어떤 함수에 제네릭의 타입을 트레잇으로 하고 있으면 동일한 트레잇을 구현하고 있는 서로 다른 구조체를 파라미터로 갖을 수 있습니다. 따라서 다양성의 특징을 보여줍니다.

trait Animal{
    // 선언문만 정의
    fn custom_bark(&self);

    // 선언문,구현문 정의
    fn common_bark(&self) {
        println!("Common Bark");
    }
}

struct Dog{
    Pomeranian : String,
    Poodle : String,
    Dashshund : String,
}

struct Cat{
    Ragdoll : String,
    RussianBlue : String,
    Manx : String,
}

impl Animal for Dog {
    // common_bark 는 Animal 트레잇에서 구현되어 있기 때문에 Dog에서 따로 구현하지 않는다.

    fn custom_bark(&self){
        println!("Dog Bark");
        println!("Pomeranian Bark : {}", self.Pomeranian);
        println!("Poodle Bark : {}", self.Poodle);
        println!("Dashshund Bark : {}", self.Dashshund);
    }
}

impl Animal for Cat {
    // common_bark 는 Animal 트레잇에서 구현되어 있기 때문에 Cat에서 따로 구현하지 않는다.

    fn custom_bark(&self){
        println!("Cat Bark");
        println!("Ragdoll Bark : {}", self.Ragdoll);
        println!("RussianBlue Bark : {}", self.RussianBlue);
        println!("Manx Bark : {}", self.Manx);
    }
}

// 트레잇 바운드 예시 함수
fn animal_bark<T : Animal>(animal : T){
    animal.custom_bark();
}

fn main(){
    let dog = Dog{
        Pomeranian : String::from("낑낑"),
        Poodle : String::from("멍멍"),
        Dashshund : String::from("댕댕")
    };

    let cat = Cat{
        Ragdoll : String::from("냐옹"),
        RussianBlue : String::from("캬악"),
        Manx : String::from("꾹꾹")
    };

    animal_bark(dog);
    animal_bark(cat);
}

▶ 출력 결과

Dog Bark
Pomeranian Bark : 낑낑
Poodle Bark : 멍멍
Dashshund Bark : 댕댕
Cat Bark
Ragdoll Bark : 냐옹
RussianBlue Bark : 캬악
Manx Bark : 꾹꾹
반응형
반응형

■ 제네릭

제네릭은 컨셉의 복제를 효율적으로 다루기 위한 도구로서, 구체화된 타입이나 다른 속성들에 대하여 추상화된 대리인의 역할을 수행합니다. 제네릭은 함수, 구조체, 열거형, 메소드를 정의할 때 사용할 수 있습니다.


◆ 제네릭 함수 활용

제네릭을 함수에 활용함으로써 다양한 타입의 파라미터를 받아 작업을 수행할 수 있습니다.

 

예시) T는 [i32], [f32], [&str] 타입의 파라미터를 받고 있으며, 파라미터를 그대로 반환하는 show 함수를 통해 함수에서의 제네릭 활용법을 확인할 수 있습니다.

fn show<T>(data : T) -> T {
    data
}

fn main(){
    println!("i32 data : {}", show(29));
    println!("f32 data : {}", show(0.2));
    println!("&str data : {}", show("Hyunmin"));
}

▶ 출력 결과

i32 data : 29
f32 data : 0.2
&str data : Hyunmin

◆ 제네릭 구조체 활용

제네릭을 구조체에 활용함으로써 필드의 타입을 유동적으로 정의하고, 생산성을 높일 수 있습니다.

 

예시) Location 구조체의 x 필드는 T 타입, y 필드는 U 타입으로 정의되었기 때문에 서로 같거나 다른 다양한 타입의 필드를 갖는 Location 인스턴스를 생성할 수 있습니다.

#[derive(Debug)]
struct Location<T,U>{
    x : T,
    y : U
}

fn main(){
    // x : i32, y : i32
    let a_loc = Location {x : 130, y : 100};
    // x : i32, y : f32
    let b_loc = Location {x : 135, y : 35.7};

    println!("a location : {:?}", a_loc);
    println!("b location : {:?}", b_loc);
}

▶ 출력 결과

a location : Location { x: 130, y: 100 }
b location : Location { x: 135, y: 35.7 }

◆ 제네릭 열거형 활용

구조체에서의 활용과 유사하게 열거형 항목의 타입을 유동적으로 정의함으로써 생산성을 높일 수 있습니다.

 

예시) Color 열거형을 생성하는데 Red 항목은 [&str] 와 [tuple] 타입의 데이터를 갖을 수 있도록 생성할 수 있습니다.

#[derive(Debug)]
enum Color<T>{
    Red(T)
}

fn main(){
    let rgb_str_red = Color::Red("255,0,0");
    let rgb_tuple_red = Color::Red((255, 0, 0));
    println!("rgb str red : {:?}", rgb_str_red);
    println!("rgb tuple red : {:?}", rgb_tuple_red);
}

▶ 출력 결과

rgb str red : Red("255,0,0")
rgb tuple red : Red((255, 0, 0))

◆ 제네릭 메소드 활용

제네릭 타입으로 구조체 또는 열거형의 메소드를 구현함으로써 함수의 활용과 같이 다양한 타입을 갖는 파라미터를 받아 작업을 수행할 수 있습니다.

 

예시) impl 문법은 구조체와 동일하게 제네릭을 설정합니다. Location 구조체의 show 메소드는 파라미터로 Location 구조체를 받을 수 있는데, 타입은 show를 호출한 인스턴스와 다른 타입인 V, W 로 설정할 수 있습니다.

use std::fmt::Debug;

#[derive(Debug)]
struct Location<T,U>{
    x : T,
    y : U
}

impl<T,U> Location<T,U>
    where T : Debug,
          U : Debug
{
    fn show<V,W>(&self, other : Location<V,W>)-> &T
        where V : Debug,
              W : Debug
    {
        println!("Self : {:?}", self);
        println!("Other : {:?}", other);
        &self.x
    }
}

fn main(){
    // T : i32, U : i32
    let a_loc = Location {x : 130, y : 100};
    // V : &str, W : f32
    let b_loc = Location {x : "135", y : 35.7};

    a_loc.show(b_loc);
}

▶ 출력 결과

Self : Location { x: 130, y: 100 }
Other : Location { x: "135", y: 35.7 }
반응형
반응형

■ 해쉬맵

HashMap은 키(key) 와 값(value)가 쌍으로 하는 데이터 구조를 갖고 있으며, 키와 값은 각각 동일한 타입으로 설정해야 한다. HashMap은 키를 이용하여 값을 조회할 수 있습니다.


◆ 해쉬맵 생성

해쉬맵을 생성하는 방법은 두가지입니다.

 

첫번째, HashMap 구조체의 new 함수를 활용한 생성

fn main(){
    use std::collections::HashMap;

    // HashMap 생성
    let mut hash_ages : HashMap<String, u32> = HashMap::new();
    // 데이터 삽입
    hash_ages.insert(String::from("Hyunmin Han"), 29);
    
    println!("HashMap : {:?}", hash_ages);
}

▶ 출력 결과

HashMap : {"Hyunmin Han": 29}

두번째, collect 메소드를 활용한 생성, iter 함수 앞 벡터가 HashMap의 키가 되고, zip 함수의 파라미터가 HashMap의 값이 됩니다.

fn main(){
    use std::collections::HashMap;
    // HashMap의 키에 해당하는 벡터 생성
    let names  = vec![String::from("Hyunmin Han"), String::from("Donghun Lee")];
    // HashMap의 값에 해당하는 벡터 생성
    let ages = vec![10, 50];
    // HashMap 생성
    let hash_ages: HashMap<_, _> = names.iter().zip(ages.iter()).collect();

    println!("HashMap : {:?}", hash_ages);
}

▶ 출력 결과

HashMap : {"Hyunmin Han": 10, "Donghun Lee": 50}

◆ 해쉬맵 조회

해쉬맵을 조회하는 방법은 두가지입니다.

 

첫번째, 대괄호([])와 HashMap의 키를 활용해서 HashMap의 값 조회(해당하는 HashMap의 키가 없을 경우 프로그램이 panic 될 수 있으니 주의가 필요합니다.)

 

두번째, get 함수에 HashMap의 key를 파라미터로 넘김으로써, HashMap의 값을 Option<&T>으로 조회(해당하는 HashMap의 키가 없을 경우 None을 반환합니다.)

fn main(){
    use std::collections::HashMap;
    
    let mut hash_ages : HashMap<String, u32> = HashMap::new();

    // 신규 등록
    hash_ages.entry(String::from("Hyunmin")).or_insert(28);
    println!("HashMap : {:?}", hash_ages["Hyunmin"]);
    println!("HashMap : {:?}", hash_ages.get("Hyunmin"));
}

▶ 출력 결과

HashMap : 28
HashMap : Some(28)

◆ 해쉬맵 갱신

해쉬맵을 갱신하는 방법은 세가지입니다.

 

첫번째, HashMap에 insert 함수를 통해 데이터를 추가할 때, 기존에 HashMap의 키가 있을 경우 HashMap의 값은 덮어쓰기 되며, 기존 HashMap의 키가 없을 경우 새롭게 추가됩니다.

 

예시) 신규 등록 이후 "Hyunmin" 이라는 HashMap의 키에 해당하는 HashMap의 값이 30으로 덮어쓰기된 것을 확인할 수 있습니다.

fn main(){
    use std::collections::HashMap;
    // HashMap 생성
    let mut hash_ages : HashMap<String, u32> = HashMap::new();
    // 신규 추가
    hash_ages.insert(String::from("Hyunmin"), 29);
    // 덮어쓰기
    hash_ages.insert(String::from("Hyunmin"), 30);
    // 신규 추가
    hash_ages.insert(String::from("Donghun"), 28);

    println!("HashMap : {:?}", hash_ages);
}

▶ 출력 결과

HashMap : {"Hyunmin": 30, "Donghun": 28}

두번째, HashMap에 entry 함수와 or_insert 함수를 활용하여 기존에 HashMap의 키가 있을 경우 등록하지 않고, 기존 HashMap의 키가 없을 경우 새롭게 추가합니다.

fn main(){
    use std::collections::HashMap;
    // HashMap 생성
    let mut hash_ages : HashMap<String, u32> = HashMap::new();
    // 신규 추가
    hash_ages.entry(String::from("Hyunmin")).or_insert(28);
    // 키가 있을 경우 등록하지 않음.
    hash_ages.entry(String::from("Hyunmin")).or_insert(29);
    // 신규 추가
    hash_ages.entry(String::from("Donghun")).or_insert(30);

    println!("HashMap : {:?}", hash_ages);
}

▶ 출력 결과

HashMap : {"Hyunmin": 28, "Donghun": 30}

세번째, HashMap에 entry 함수의 리턴값(&mut [값 타입])에 역참조자(*)를 활용한 HashMap의 값 수정

fn main(){
    use std::collections::HashMap;
    // HashMap 생성
    let mut hash_ages : HashMap<String, u32> = HashMap::new();
    // 신규 등록
    let age = hash_ages.entry(String::from("Hyunmin")).or_insert(28);
    // 역참조자를 활용하여 값 수정
    *age = 29;

    println!("HashMap : {:?}", hash_ages);
}

▶ 출력 결과

HashMap : {"Hyunmin": 29}
반응형

+ Recent posts