문제점

Django 백엔드 서버에서 대용량 리소스를 메모리에 올려놓고 서비스를 해야 하는 상황이 생겼다.
gunicorn 기본 동작은 workers 사이에 메모리를 공유하지 않고 각각의 worker 프로세스 생성 시 따로따로 리소스를 메모리에 올려놓는 방식이다.
이걸 다르게 말하면 worker 프로세스 수만큼 대용량 리소스를 메모리에 올려놓는다는 말이고 웹서버의 메모리를 엄청나게 잡아먹는다는 말이다.
대용량 리소스를 메모리에 올려놓고 worker 프로세스 사이에서 공유했으면 좋겠다.

환경 및 상황


version: '3.7'

volumes:
  static_vol:

services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile

    command: gunicorn --workers=3 recommend.wsgi:application --bind 0.0.0.0:8000
    volumes:
           - static_vol:/code/static/
           - .:/code/
    expose:
           - 8000
    env_file:
           - ./.env.prod

  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    volumes:
          - static_vol:/code/static/
          - ./media:/code/media/
    ports:
          - 80:80
    depends_on:
          - web

위와 같은 docker-compose 파일이 있다고 가정하자.
대용량 리소스의 크기는 약 3.2GB, worker 개수가 5개니깐 메모리를 약 16GB를 사용한다.
※ gunicorn --workers=5

하지만 command 제일 마지막에 --preload 라고 붙이면 귀신같이 메모리 사용률이 낮아진다
※ 사용하는 메모리 3.2GB, 그냥 1개!

왜냐? 하나만 메모리에 올리고 그걸 공유하기 때문에!
공유하는 걸 어떻게 알 수 있냐?? 해당 변수의 메모리 주소를 보면 알 수 있다.


print(hex(id(search_model)))

위와 같이 공유하고자 하는 무거운 리소스 변수가 가르치는 메모리 주소를 출력해보자
한번은 --preload를 빼고, 한번은 추가해서 진행해보자.

--preload 빼고 10번 호출!

web_1 | 0x7fe22f072590
web_1 | 0x7fe22f0af790
web_1 | 0x7fe22f073690
web_1 | 0x7fe22f074710
web_1 | 0x7fe22f073610

web_1 | 0x7fe22f074710
web_1 | 0x7fe22f0af790
web_1 | 0x7fe22f073610
web_1 | 0x7fe22f072590
web_1 | 0x7fe22f073690

위와 같이 변수의 주소값은 총 5개! 프로세스 별로 하나씩 메모리에 올려 놓고 사용하는 걸 확인 할 수 있다.

--preload 추가

web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550

web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550
web_1 | 0x7f1fc2c33550

0x7f1fc2c33550 이 주소 하나로 모든 프로세스들이 접근하고 있다.
--preload만 붙였을 뿐인데 메모리 사용량이 급격히 줄었다.

빈칸의 학습 효과

"어떻게 공부할 것인가" 책을 읽어보면 최대한 어렵게 공부하는게 좋다고 나와있다.

(어렵게 공부 할 수록 기억에 오래 남는다)

그리고 반복적으로 읽는 input 식 학습이 아닌 output 식의 학습이 권장 된다.

조금 더 자세히 말하면 학습법으로 가장 선호되는 방법인 반복적 읽기는 내가 이 내용을 알고 있다는 착각만 유발할 뿐 실제로 효율적인 공부가 되지 못한다.

저자는 조금 고통스러울 지라도 시험을 보고, 빈칸을 만들어 채우는 output 식의 학습법을 이용하면 공부를 훨씬 효율적으로 할 수 있다고 말한다. 그래서 아래의 어플을 사용하길 권한다.

 

빈칸 만들기 어플

  1. 이미지를 글자로 가져온다
  2. 외우고자 하는 단어에 빈칸을 만든다
  3. 최대한 고통을 받으며 외운다

빈칸 생성 전

 

 

빈칸 생성 후

 

 

다운로드 링크(안드로이드)

최대한 output식으로 효과적으로 공부하자!

https://play.google.com/store/apps/details?id=com.shuneesoft.blanker

 

빈칸 만들기 - Google Play 앱

1. 우측 하단 카메라 버튼을 눌러 글자로 변환할 이미지를 선택 혹은 촬영하세요. 2. 변환된 단어를 터치해 빈칸을 생성하세요. 3. 저장 버튼을 눌러 저장하세요(제목 입력 필수)

play.google.com

 

 

재귀 함수 + 비동기
  const onSubmit = async () => {
    let response = await create_customer();
    if (response.status !== 200) {
      return onComplete(false, '수집 되지 않은 좌표가 있습니다');
    }

    const invalidMutualDistanceCustomers = response.data;
    response = await getMutualDistanceByTmapRecursive(invalidMutualDistanceCustomers);

    try {
      response = await create_route();
    } catch (error) {
      return onComplete(false, '경로 생성 실패');
    }

    return onComplete(true, '배차 완료');
  };

 

위의 코드는 submit 버튼을 누르면 동작하는 async 함수다.

 

고객을 생성하고 유효하지 않은 고객들(invalidMutualDistanceCustomers 배열)을 재귀 + 비동기 함수로 처리 하려한다.

 

setTimeout 대신 사용할 비동기 sleep 함수
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));

 

재귀 함수
  const getMutualDistanceByTmapRecursive = async (invalidMutualDistanceCustomers: Array) => {

    const customer: Array = invalidMutualDistanceCustomers.pop();

    if (customer === undefined) {
      return;
    }

    await timeout(800);

    const response = await getMutualDistanceByTmap(customer[0], customer[1]);
    const totalDistance = response.data.features[0].properties.totalDistance;
    const jsonMap = JSON.stringify(response.data);
    await save(customer[0], customer[1], totalDistance, jsonMap);
    await getMutualDistanceByTmapRecursive(invalidMutualDistanceCustomers);

  }

 

1. 배열을 하나씩 pop 해주고 배열이 하나도 없으면(customer === undefined) 재귀 함수 종료

2. await timeout(800) : 0.8초 뒤에 api를 호출해라(api 초당 제한)

3. tmap api 호출하고 저장한 다음 다시 재귀 함수(getMutualDistanceByTmapRecursive) 호출

 

 

결론 : 비동기 처리는 까다로우니 async, await를 많이 박자

'JS' 카테고리의 다른 글

javascript es6 + Promise IE11 간단히 지원  (1) 2021.06.11
Promise 자바스크립트(js)  (0) 2019.10.18
loading button 쉽게 만들기(with js)  (0) 2019.10.15
js 화살표함수(arrow function)  (0) 2019.10.14
JS prototype(프로토타입)  (0) 2019.09.25

포큐아카데미 OOP

 

변수 앞에 final은 C나 javascript에서 사용하는 const와 같다. 변경못함! immutable!

 

 

포큐아카데미 OOP

메서드 앞에 붙는 final은 부모클래스의 메서드를 자식이 오버라이딩해서 사용 못한다는 뜻!

Athlete의 getHeight를 자식 클래스인 BasketballPlayer가 오버라이딩 하려고 하면 컴파일 오류가난다.

참고로 JAVA에서 final을 붙이면 정적바인딩으로 변한다.(기본 동작은 동적바인딩)

 

 

클래스 앞에 final 붙이면 상속을 못한다는 뜻이다.

 

final은 const와 같이 가능하면 모두 붙이는게 좋다

'JAVA' 카테고리의 다른 글

그래프 DFS(위상정렬)  (0) 2021.07.20
java annotation  (0) 2021.03.12

출처 : https://core.ewha.ac.kr/publicview/C0101020140321143516139010?vmode=f

스레드는 왜있을까?? 프로세스는 실행중인 프로그램을 프로세스라고 한다. 이 프로세스(프로그램)은 졸라게 복잡한 계산을 필요로 한다고 가정해보자. 어떻게 컴퓨터 운영체제의 부하를 줄이면서 최대한 빠르게 계산을 처리할 수 있을까??

 

바로 스레드를 사용하면 된다. 위의 그림을 간략히 설명하면 Code 영역에 실행해야할 명령어 집합(instruction)이 들어있고 data영역에 전역변수와 같은 변수들이 들어 있다. 계산을 할 때는 CPU만 졸라게 빨리 움직이면 되기 때문에 Code와 data영역은 공유하고 다음에 수행해야할 명령어 주소를 가르키는 PC 값을 각각 쓰레드에 매핑 시킨 후 계산을 처리하면 된다! (뒤에 좀 더 자세히)

 

 

출처 : https://core.ewha.ac.kr/publicview/C0101020140321143516139010?vmode=f

그래서 Thread는 하나의 프로세스에서 독립된 PC, register set, stack space를 가지며 code와 data, OS resource를 공유한다.

 

다음에 수행할 명령어(instruction)를 가르키는 PC값과 CPU 연산을 위해 필요한 임시 저장 공간 register, 함수를 실행하기 위한 고유의 stack 영역이 스레드마다 각각 필요하다는 말이다.

 

스레드를 좀 더 자세히 기술하면 하나의 스레드가 blocked(waiting) 상태인 동안에도 동일한 태스크 내의 다른 스레드가 실행되어 좀 더 빠르게 처리를 할 수 있다는 뜻이다.

 

하나의 스레드가 blocked 되었단 말은 I/O 작업 같이 오랜 시간이 걸리는 작업을 할 때를 뜻한다. 예를 들면 웹 브라우저에서 고용량 이미지를 받아올때다.

 

동일한 태스크 내(하나의 브라우저 안에서) 다른 스레드가 실행 된다(고용량 이미지를 받아오지만, 이미지를 받아오는 I/O 스레드가 blocked될 동안 일반 텍스트를 Display 해주는 스레드는 일함) 

 

 

출처 : https://core.ewha.ac.kr/publicview/C0101020140321143516139010?vmode=f

위의 그림은 싱글스레드와 멀티스레드의 차이를 그림으로 보여준다.

 

끝!

C++ 포큐아카데미

추상클래스는 함수 선언은 있는데 구현이 하나라도 없으면 추상 클래스다. 왜 구현을 안해놓냐?? 자식클래스가 구현을 할 거니깐! 만약 자식 클래스가 구현을 안하면 컴파일 에러가난다! 선언만 있고 구현이 없는 함수를 순수(Pure) 함수라고 한다. 위의 Speak함수는 순수함수다. 순수 가상함수는 virtual void Speack() = 0 처럼 virtual과 = 0 을 붙여주면 된다.

 

 

 

C++ 포큐아카데미

추상 클래스를 object로 만들면 당연히 컴파일 에러가 난다. 구현체가 없는데 어떻게 객체를 만들겠나..

객체를 스택에 만들든, 힙에 만들든 무조건 에러다. 하지만 자식 클래스 Cat을 만든 다음 포인터로 캐스팅 하면 된다. 왜냐하면 가상 테이블 때문에 Cat의 Speak을 호출할 걸 알기때문!

 

 

인터페이스

C++ 포큐아카데미

C++는 인터페이스가 없다. 그래서 순수 가상 함수만으로 이루어진 클래스를 interface라고 C++ 개발자들 끼리 부르기로 했다.

  

C++ 포큐아카데미

 

'C++' 카테고리의 다른 글

C++ 템플릿  (0) 2020.04.21
C++ 객체 백터 Vector  (0) 2020.04.17
C++ iterator 개념  (0) 2020.04.17
C++ 정적 바인딩, 동적 바인딩  (1) 2020.04.09
C++ 멤버함수의 메모리 위치  (0) 2020.04.09
문제점

C++ 포큐아카데미

위의 코드는 문제가 있다기 보다는 엄청 귀찮다. 왜냐하면 함수 오버로딩을 이용해서 매개변수, 반환 타입별로 함수를 만들기 때문이다. Add double이면 double도 만들어줘야 한다. 정말 귀찮다. 해결방법이 뭘까??

 

해결책(템플릿)
template <typename T>
T Add(T a, T b)
{
	return a + b;
}


int main()
{
	std::cout << Add<int>(3, 10) << std::endl;
	std::cout << Add<float>(3.14f, 10.14f) << std::endl;

	return 0;
}

위 처럼 템플릿 함수 Add를 만들면 컴파일 시에 int형 함수, float형 함수를 따로따로 만들어 준다. (컴파일러 정말 감사..... 꿀 기능)

 

작동 원리

템플릿을 인스턴스화 할때마다 컴파일러가 알아서 함수를 생성해준다.

그래서 자료형 가짓수에 비례해서 exe파일이 증가한다.

 

템플릿 클래스 예제
#include <stdio.h>
#include <iostream>
#include <map>


template<typename T>
class MyArray
{
public:
	bool Add(T data);
	MyArray();

private:
	enum { MAX = 3 };
	int mSize;
	T mArray[MAX];
};


template<typename T>
bool MyArray<T>::Add(T data)
{
	if (mSize >= MAX)
	{
		return false;
	}
	mArray[mSize] = data;
	mSize++;
	return true;
}

template<typename T>
MyArray<T>::MyArray()
	: mSize(0)
{
}



template <typename T>
T Add(T a, T b)
{
	return a + b;
}


int main()
{
	MyArray<int> scores;

	scores.Add(10);
	scores.Add(20);
	scores.Add(30);
	scores.Add(40);

	return 0;
}

'C++' 카테고리의 다른 글

추상클래스(Abstract), 인터페이스  (0) 2020.04.27
C++ 객체 백터 Vector  (0) 2020.04.17
C++ iterator 개념  (0) 2020.04.17
C++ 정적 바인딩, 동적 바인딩  (1) 2020.04.09
C++ 멤버함수의 메모리 위치  (0) 2020.04.09
Vector 값
#include <stdio.h>
#include <iostream>
#include <vector>


class Scores
{
public:
	Scores(int score, std::string subject);
	int score;
	std::string subject;
};

Scores::Scores(int score, std::string subject)
{
	this->score = score;
	this->subject = subject;
}

int main()
{
	std::vector<Scores> scores;
	scores.reserve(3);

	scores.push_back(Scores(30, "C++"));
	scores.push_back(Scores(40, "JAVA"));
	scores.push_back(Scores(50, "Python"));

	std::vector<Scores>::iterator it = scores.begin();


	for (std::vector<Scores>::iterator iter = scores.begin(); iter != scores.end(); ++iter)
	{
		std::cout << iter->score << (*iter).subject << std::endl;
	}

}

위의 코드는 Vector에 Scores 객체를 담는 코드다. 이때 new Scores가 아니라 그냥 Scores로 객체를 만들기 때문에 값 복사가 일어난다(포인터가 아님). Scores 처럼 용량이 작은 객체는 괜찮지만 용량이 큰 객체를 이런식으로 관리하면 값 복사 문제때문에 굉장히 힘들어진다.

 

 

C++ 포큐아카데미
C++ 포큐아카데미

위의 그림은 scores 객체의 capacity를 초과해서 메모리 재할당이 일어나는 경우를 나타낸 그림이다.

이렇듯 객체를 직접 보관하면 값복사 때문에 굉장히 느려질 수 있다. 이때의 해결책은 JAVA처럼 포인터를 Vector에 저장하는 방법이다.

 

C++ 포큐아카데미

 

 

위의 그림은 scores의 사본을 만들때 발생하는 값 복사 문제다. 값으로 저장되어 있기 때문에 사본을 그대로 복사한다. 문제다 문제

 

 

Vector 포인터(JAVA 방식)
	std::vector<Scores*> scores;
	scores.reserve(3);

	scores.push_back(new Scores(30, "C++"));
	scores.push_back(new Scores(40, "JAVA"));
	scores.push_back(new Scores(50, "Python"));

	std::vector<Scores*>::iterator it = scores.begin();


	for (std::vector<Scores*>::iterator iter = scores.begin(); iter != scores.end(); ++iter)
	{
		std::cout << (*iter)->score << (*iter)->subject << std::endl;
	}

위의 코드는 new를 사용해서 포인터를 저장하는 Vector다. 해서 타입도 std::vector<Scores*>이다

 

여기서 주의해야할 점은 it는 Vector의 첫번째 인덱스의 값 즉 Socre 포인터(Score *)를 가르키는 포인터다. 그래서 값에 접근하기 위해서는 (*iter)->socre와 같이 사용해야한다.

 

C++ 포큐아카데미
C++ 포큐아카데미

위의 그림과 같이 메모리 복사가 일어나도 포인터 바이트(통상적으로 4바이트) 수만 복사하면 되니깐 아주 가볍다.

 

하지만 여기서 중요한점은 포인터기 때문에 메모리 제거를 내가 스스로 해줘야한다. clear()라고 해도 알아서 메모리 해제 해주지 않는다.

 

'C++' 카테고리의 다른 글

추상클래스(Abstract), 인터페이스  (0) 2020.04.27
C++ 템플릿  (0) 2020.04.21
C++ iterator 개념  (0) 2020.04.17
C++ 정적 바인딩, 동적 바인딩  (1) 2020.04.09
C++ 멤버함수의 메모리 위치  (0) 2020.04.09

+ Recent posts