서버의 Scalability 를 관리하기 위한 방법은 굉장히 중요하다.

 

실 서비스의 아키텍처를 구조화할 때도 반드시 고려해야할 부분이고, 이 결정은 실제로 서비스에 큰 영향을 주게 된다.

 

가령 사용자가 갑자기 증가할 경우, 엄청난 수의 요청에 대한 처리를 어떻게 할 것인가는 Backend 에서 가장 중요하게 고려해야할 부분 중 하나이다.

 

Scaling 의 방법에는 크게 Scale Up 과 Scale Out 이 존재한다.

 

Scale Up : 서버 자체의 Spec 을 향상시킴으로써 성능을 향상시킨다. Vertical Scaling 이라 불리기도 한다. 서버 자체의 갱신이 빈번하여 정합성 유지가 어려운 경우(주로 OLTP 를 처리하는 RDB 서버의 경우) Scale Up 이 효과적이다.

 

성능 향상에 한계가 있으며 성능 증가에 따른 비용부담이 크다. 대신 관리 비용이나 운영 이슈가 Scale Out 에 비해 적어 난이도는 쉬운 편이다. 대신 서버 한대가 부담하는 양이 많이 때문에 다양한 이유(자연 재해를 포함한다...)로 서버에 문제가 생길 경우 큰 타격이 불가피하다.

 

Scale Out : 서버의 대수를 늘려 동시 처리 능력을 향상시킨다. Horizon Scaling 으로 불린다. 단순한 처리의 동시 수행에 있어 Load 처리가 버거운 경우 적합하다. API 서버나, 읽기 전용 Database, 정합성 관리가 어렵지않은 Database Engine 등에 적합하다.

특히 Sharding DB 를 Scale Out 하게 된다면 주의해야하는데, 기존의 샤딩 알고리즘과 충돌이 생길 수도 있고(샤드가 갯수에 영향을 받을 경우...) 원하는 대로 부하의 분산이 안생길 수 있으니 각별히 주의할 필요가 있다.

 

스케일 아웃은 일반적으로 저렴한 서버 여러 대를 사용하므로 가격에 비해 뛰어난 확장성 덕분에 효율이 좋지만 대수가 늘어날 수록 관리가 힘들어지는 부분이 있고, 아키텍처의 설계 단계에서부터 고려되어야 할 필요가 있다.

 

 

요즘은 클라우드를 사용하기 때문에 Scaling 에 있어서 큰 이점이 있으며, 서비스의 목표치에 알맞게 Scalability 를 설계하고 두 스케일링 방법을 모두 적용시키는 것이 일반적이다.

 

 

JVM 은 Java 코드 및 Application을 동작시킬 수 있도록 런타임 환경을 제공해주는 Java Engine 이다.

 

JVM 은 JRE의 일부분이며 Java 개발자로서 JVM 의 동작을 이해하는 것은 Java 구동 환경과 JRE(Java Runtime Environment) 을 이해하는 데 있어서 아주 중요한 부분이다.

 

Java 가 추구하는 가치는 WORA(Write Once Run Anywhere) 이며, 이는 모든 자바의 코드는 JVM 이라는 환경 위에서 동작하면서 어느곳이든 이식이 가능하다는 뜻을 포함하고 있다.

 

먼저 JVM 의 아키텍처는 다음과 같다.

 

하나하나 동작을 살펴보도록 하자.

 

Java Application 의 동작은 다음과 같은 순서로 이루어진다.

 

 

(1) Java JIT Compiler 가 Java 코드를 해석한다. Java Compiler 는 해석한 .java 코드를 .class 파일 형태로 해석결과로 만든다.

 

(2) 클래스 로더에 의해 .class 파일들은 다음 단계들을 거치며 해석된다.

 - Loading : 클래스 파일에서 클래스 이름, 상속관계, 클래스의 타입(class, interface, enum) 정보, 메소드 & 생성자 & 멤버변수 정보, 상수 등에 대한 정보를 로딩해서 Binary 데이터로 변경한다.

 

 - Linking : Verification 과 Preparation, Resolution 단계를 거치면서 바이트코드를 검증하고 필요한 만큼의 메모리를 할당한다. Resolution 과정에서는 Symbolic Reference 를 Direct Reference 등으로 바꿔준다.

 

 - Initialization : static block 의 초기화 및 static 데이터들을 할당한다. Top->Bottom 방식으로 클래스들을 해석한다.

참조 : (https://jins-dev.tistory.com/entry/Java-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C%EB%8D%94ClassLoader%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4)

 

(3) 컴파일된 바이트코드는 Execution Engine 에 의해 머신별로 해석된다. 이제 Host System 과 Java Source 는 Byte Code 를 이용해 중계된다. JVM 이 할당된 메모리 영역을 관리하게 된다.

Execution Engine 은 3가지 파트로 구성된다.

 

 - Interpreter : 바이트코드를 Line by Line 으로 해석하고 실행시킨다. 동일 메서드가 여러번 Call 되더라도 매번 Interperter 가 호출된다.

 

 - JIT compiler(Just In Time Compiler) : Interpreter 의 효율성을 증가시켜준다. 모든 Byte 코드를 한번에 Compile 해놓고 Direct 로 변환할 수 있게 해줌으로써 Re-interpret 의 발생을 낮춰준다.

 

 - Garbage Collector : Un-referenced Object 들을 제거해주는 가비지 컬렉팅을 수행한다.

 

 

이처럼 일반적인 C 프로그램과 다르게 Java 는 ByteCode 로 해석하고 변환하는 단계가 있기 때문에 느릴 수 밖에 없다.

- Dynamic Linking : Java 의 Linking 은 런타임에 동적으로 일어난다.

- Run-time Interpreter : Byte Code 의 머신코드로의 컨버팅 역시 Runtime 에 일어난다. 

 

물론 최신 Java 버전은 병목현상을 효과적으로 제거했기 때문에 구버전의 자바와는 비교가 안될정도의 퍼포먼스를 갖는다. :)

 

참조 : https://www.guru99.com/java-virtual-machine-jvm.html

 

Java Virtual Machine (JVM) & its Architecture

Java String has three types of Replace method replace replaceAll replaceFirst. With the help of...

www.guru99.com

https://www.geeksforgeeks.org/jvm-works-jvm-architecture/

 

How JVM Works - JVM Architecture? - GeeksforGeeks

JVM(Java Virtual Machine) acts as a run-time engine to run Java applications. JVM is the one that actually calls the main method present in a… Read More »

www.geeksforgeeks.org

 

Context Switching 은 면접에서 지원자의 기본기를 검사할 목적으로 단골로 등장하는 질문이자, CS의 중요한 기본 지식이기도 하다.

 

Context Switching 이란 CPU가 한 개의 Task(Process / Thread) 를 실행하고 있는 상태에서 Interrupt 요청에 의해 다른 Task 로 실행이 전환되는 과정에서 기존의 Task 상태 및 Register 값들에 대한 정보 (Context)를 저장하고 새로운 Task 의 Context 정보로 교체하는 작업을 말한다.

 

여기서 Context란, CPU 가 다루는 Task(Procee / Thread) 에 대한 정보로 대부분의 정보는 Register 에 저장되며 PCB(Process Control Block) 으로 관리된다.

 

여기서 Process 와 Thread 를 처리하는 ContextSwitching 은 조금 다른데, PCB는 OS에 의해 스케줄링되는 Process Control Block이고, Thread 의 경우 Process 내의 TCB(Task Control Block) 라는 내부 구조를 통해 관리된다.

 

Task 의 PCB 정보는 Process Stack, Ready Queue 라는 자료구조로 관리가 되며, Context Switching 시 PCB 의 정보를 바탕으로 이전에 수행하던 작업 혹은 신규 작업의 수행이 가능하게 된다.

 

PCB는 주로 다음과 같은 정보들을 저장하게 된다.

 

(1) Process State : 프로세스 상태

(2) Program Counter : 다음에 실행할 명령어 Address

(3) Register : 프로세스 레지스터 정보

(4) Process number : 프로세스 번호

 

Context Switching 시, Context Switching 을 수행하는 CPU 는 Cache 를 초기화하고 Memory Mapping 을 초기화하는 작업을 거치는 등 아무 작업도 하지 못하므로 잦은 Context Switching 은 성능 저하를 가져온다. 

 

일반적으로 멀티 프로세스를 통해 PCB를 Context Switching 하는 것보다 멀티 쓰레드를 통해 TCB 를 Context Switching 하는 비용이 더 적다고 알려져있다.

 

주로 Context Switching 은 Interrupt 에 의해 발생되는데, Hardware 를 통한 I/O 요청이나, OS / Driver 레벨의 Timer 기반 Scheduling 에 의해 발생한다.

 

 

더 자세한 참조 링크 : 

https://stackoverflow.com/questions/7439608/steps-in-context-switching/7443719

 

Steps in Context Switching

I am asked to describe the steps involved in a context switch (1) between two different processes and (2) between two different threads in the same process. During a context switch, the kernel wil...

stackoverflow.com

https://nesoy.github.io/articles/2018-11/Context-Switching

 

Context Switching이란?

 

nesoy.github.io

 

 

다익스트라 알고리즘은 IT 개발자로 종사하는 사람이라면 모르는 사람이 없을 정도로 유명하면서도 기본적인 알고리즘이다.

 

이 알고리즘은 교통망이나 통신망과 같이 그래프로 나타내어질 수 있는 자료 구조에서 최단거리를 구할 수 있는 방법을 제시한다. 정확히는 한점으로부터 목적지까지의 최단경로트리를 만듦으로써 최단거리를 구한다.

 

다익스트라 알고리즘은 다음 아이디어에서 기반한다.

 

위와 같은 그림이 있고 간선 위의 숫자가 거리를 나타낸다고 하자.

위의 그림에서 1->4 까지 가는 최단 경로는 어떻게 직관적으로 계산할 수 있을까?

 

우리는 직관적으로 1->4 로 직접 향하는 1->4 경로의 비용(13) 보다 1->2->4 로 가는 경로의 비용(14) 이 더 크고, 1->3->4 로 가는 경로의 비용(12) 이 최적이라는 것을 알 수 있다.

우리가 직관적으로 파악할 수 있는 이 내용은, 바로 각 노드들까지의 경로는 "각경로의 최단거리의 합 중 가장 작은값" 이라는 걸 알 수 있다.

 

즉, A-B-C 로 연결된 리스트가 있을 때, A-C 로 가는 최단 경로는 반드시 A-B와 B-C 의 최단경로를 지나치게 된다는 점이다.

다익스트라 알고리즘은 이를 이용해서 최단경로를 저장하는 배열을 만들고, 노드들간의 최단경로를 계속해서 업데이트해주면서 최종적으로 출발지(src) 에서 목적지(dest) 까지의 최단경로를 만들어낸다.

 

다익스트라 알고리즘은 구현에 따라 Naive 하게 접근하는 방법과 Priority Queue 를 이용해 개선한 방법이 있다.

 

Naive 한 알고리즘은 노드의 갯수 N에 대하여 O(N^2) 만큼의 시간 복잡도를 가지며 다음과 같다.

 

	/**
	 * 다익스트라 알고리즘.
	 * @param dist 최단경로 배열
	 * @param n    노드의 갯수
	 * @param src  시작 노드 인덱스
	 * @param dest 도착 노드 인덱스
	 * @return
	 */
	private static int djikstra(int[][] dist, int n, int src, int dest) {
		int[] checkers = new int[n+1];
		int checkCount = 0;
		checkers[src] = 1;
		checkCount++;
		while (checkCount < n) {
			int minIdx = -1;
			int minDist = INF;
			//새로 갱신된 최단경로를 dist 배열에서 새로 읽어온다.
			for(int i=1; i<=n; i++) {
				if(checkers[i] == 0 && dist[src][i] < minDist) {
					minIdx = i;
					minDist = dist[src][i];
				}
			}
			if(minIdx < 0) {
				break;
			}
			checkers[minIdx] = 1;
			checkCount++;
			int nidx = minIdx;
			int ndist = minDist;
			//src에서 가장 가까운 점 nIdx 를 기점으로 최단경로 재갱신
			for (int i = 1; i <= n; i++) {
				if (dist[nidx][i] != INF && ndist + dist[nidx][i] < dist[src][i]) {
					dist[src][i] = ndist + dist[nidx][i];
				}
			}
		}
		return dist[src][dest];
	}

 

위의 소스를 Priority Queue 를 통해 개선한다면 노드의 갯수 N 과 Edge 의 갯수 E 에 대하여 O(E + NlogN) 의 시간 복잡도로 개선할 수 있다.

 

		private static int INF = 1000000000;
	
		private static class Node implements Comparable<Node> {
			int idx;
			int val;
	
			int getIdx() {
				return idx;
			}
	
			int getVal() {
				return val;
			}
	
			Node(int idx, int val) {
				this.idx = idx;
				this.val = val;
			}
	
			@Override
			public int compareTo(Node o) {
				return this.getVal() - o.getVal();
			}
		}
	
		/**
		 * 다익스트라 알고리즘.
		 * @param dist 최단경로 배열
		 * @param n    노드의 갯수
		 * @param src  시작 노드 인덱스
		 * @param dest 도착 노드 인덱스
		 * @return
		 */
		private static int djikstra(int[][] dist, int n, int src, int dest) {
			PriorityQueue<Node> priorityQueue = new PriorityQueue<>();
			for (int i = 1; i <= n; i++) {
				if (src != i && dist[src][i] < INF) {
					priorityQueue.offer(new Node(i, dist[src][i]));
				}
			}
			while (!priorityQueue.isEmpty()) {
				Node next = priorityQueue.poll();
				int nidx = next.getIdx();
				int ndist = next.getVal();
				for (int i = 1; i <= n; i++) {
					if (dist[nidx][i] != INF && ndist + dist[nidx][i] < dist[src][i]) {
						dist[src][i] = ndist + dist[nidx][i];
						priorityQueue.offer(new Node(i, dist[src][i]));
					}
				}
			}
			return dist[src][dest];
		}
	

 

가장 기본적이면서도 중요한 알고리즘의 하나이고, 여러가지 중요한 아이디어들이 알고리즘에 녹아있기 때문에 잘 알아두고 계속해서 연습해보는 것이 필요하다.

 

알고리즘은 다음 링크에서 연습해볼 수 있다.

 

https://www.acmicpc.net/problem/1916

 

 

다음읜 위의 문제에 대한 해답 코드이다.

 

import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {

	private static int INF = 1000000000;

	private static class Node implements Comparable<Node> {
		int idx;
		int val;

		int getIdx() {
			return idx;
		}

		int getVal() {
			return val;
		}

		Node(int idx, int val) {
			this.idx = idx;
			this.val = val;
		}

		@Override
		public int compareTo(Node o) {
			return this.getVal() - o.getVal();
		}
	}

	private static int djikstra(int[][] dist, int n, int src, int dest) {
		PriorityQueue<Node> priorityQueue = new PriorityQueue<>();
		for (int i = 1; i <= n; i++) {
			if (src != i && dist[src][i] < INF) {
				priorityQueue.offer(new Node(i, dist[src][i]));
			}
		}
		while (!priorityQueue.isEmpty()) {
			Node next = priorityQueue.poll();
			int nidx = next.getIdx();
			int ndist = next.getVal();
			for (int i = 1; i <= n; i++) {
				if (dist[nidx][i] != INF && ndist + dist[nidx][i] < dist[src][i]) {
					dist[src][i] = ndist + dist[nidx][i];
					priorityQueue.offer(new Node(i, dist[src][i]));
				}
			}
		}
		return dist[src][dest];
	}

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int m = scanner.nextInt();
		int[][] dist = new int[1001][1001];
		for (int i = 0; i < 1001; i++) {
			for (int j = 0; j < 1001; j++) {
				dist[i][j] = INF;
			}
		}
		for (int i = 0; i < m; i++) {
			int a = scanner.nextInt();
			int b = scanner.nextInt();
			int d = scanner.nextInt();
			dist[a][b] = Math.min(dist[a][b], d);
		}
		int src = scanner.nextInt();
		int dest = scanner.nextInt();
		System.out.println(djikstra(dist, n, src, dest));
	}
}

 

실서비스를 준비하는 데 있어서 서버의 성능 테스트는 매우 중요한 부분이다.

(여기서 성능 테스트란 단순히 Performance test 만을 말하지는 않는다. 자세한건 다음을 참조 - 

https://jins-dev.tistory.com/entry/load-testing-%EA%B3%BC-stress-testing-performance-testing-%EC%97%90-%EB%8C%80%ED%95%9C-%EB%B9%84%EA%B5%90)

 

그렇다면 성능 테스트를 어떻게 할 것이고 어떻게 병목현상을 해결하여 개선을 이루어낼 것인가가 관건이며, 이를 위해서는 단순히 코드를 효율적으로 짜는 것 이상의 학습이 필요하다.

"성능" 에 대한 이해와 "테스트" 에 대한 이해, Performance Engineering 에 대한 이해가 모두 필요한 것이다.

 

그렇다면 먼저 서버의 성능 측정에 앞서 알아두어야할 용어들을 정리해보자.

 

(1) Response Time : 클라이언트의 요청을 서버에서 처리하여 응답해주는데까지 걸리는 전체 시간을 말한다.

실제 요청이 들어간 시점부터 네트워크를 거치고 비즈니스 로직의 처리가 끝난 후 다시 클라이언트가 응답을 받는 시점까지 걸리는 시간을 말한다.

 

(2) Think Time : 사용자가 다음 Request 를 보내기 전까지 활동하는 시간을 말한다. 실제 별도의 서버 요청을 하지않고 웹문서를 보거나 컨텐츠를 즐기는 시간을 Think time 으로 분류한다.

첫번째 요청에서 다음 요청을 보낼때까지의 시간이므로 Request Interval 로 부르기도 한다.

 

(3) Concurrent Users : 말그대로 동시에 서비스를 사용하는 유저를 말한다. 웹서버 / 세션서버 등 서버의 종류에 따라 유저의 행동양식에 대한 분류는 매우 다양해지게 된다.

일반적으로 Concurrent User 는 Active User 와 Inactive User 로 분류한다.

 

 - Active User : 실제 Request 를 보내고 Response 를 대기 혹은 Connection 을 잡고 있는 유저를 말한다.

일반적으로는 Request 에 대해 실제 Transaction 을 발생시키는 유저에 대해 언급한다.

여기서 포함되는 유저들의 목록에는 실제로 서버에서 클라이언트의 요청을 받을 시, Connection Pool 을 모두 가져가는 유저들과, Connection Pool 의 할당을 대기하고 있는 Pending User 들 역시 Active User 에 포함된다는 점이다.

 

 - Inactive User : 실제 Request 를 수행하지 않고 대기중인 유저를 말한다. 서버의 종류에 따라 연결을 유지하고 있는 유저의 경우에도 Inactive User 로 보기도 한다.

웹서버의 경우 대부분의 Inactive User 는 Think time 을 갖는 유저로 간주된다.

 

(4) Throughput : 서버가 클라이언트에 송신한 데이터량

 

(5) Vuser : Virtual User - 주로 퍼포먼스 테스트를 위해 만드는 가상 유저

 

(6) MRT : Mean Response Time 의 약자로 유저들에 대한 평균 응답시간을 말한다.

 

(7) TPS : Transaction Per Second 의 약자로 주로 서버 성능의 척도가 된다. 초당 트랜잭션 처리 수를 의미한다.

일반적으로 TPS = AU / MRT 라는 공식에 따라 Active User 의 수에 비례하고 유저들에 대한 평균 응답시간(MRTT)에 반비례한다.

 

(8) Scenario : 테스트를 위한 시나리오를 말한다. 정확한 TPS 측정을 위해서 가장 중요한 부분 중 하나로, 실제 서비스에서 유저가 어느정도 비율로 어떤 API 를 호출할지 예측하는 과정이 필수적이다.

Service Architecture 를 잘 이해하고 있어야됨은 물론이고, 이에 따라 API 호출의 비율과 그에 맞는 튜닝으로 서비스의 질을 향상시켜야 한다.

 

 

위의 용어를 이해했으면 다음으로 퍼포먼스 테스트를 위한 프로세스를 정리해보자.

 

1. 먼저 서비스의 목표와 그를 위한 규모를 측정해야 한다.

 

2. 서비스 내에서 사용자의 요청 빈도 수를 예측하고, 그에 알맞게 테스트 시나리오를 만든다.

 

3. 테스트 시나리오에 맞게 다양한 방향으로 테스트를 진행한다.

여기서 테스트는 Stress Test / Performance Test / Load Test 가 복합적으로 진행되어야 한다. 각 테스트별 한계값 임계치를 측정하는 것이 중요하며, 각각의 테스트 결과는 서로에 영향을 미치게 될 것이다.

 

4. Stress Test / Load Test / Performance Test 를 통해 현재 시스템의 부하 한계치, 퍼포먼스 등이 측정이 되었다면, 모듈별로 분석해서 튜닝하는 작업이 필요하다.

비즈니스 로직 뿐 아니라 Database / Network / Middleware / Infrastructure 등을 모두 튜닝하는 것이 중요하다.

 

5. 튜닝을 통해 테스트를 다시 수행하며 서비스의 한계치를 다시 측정한다. 목표에 이를 때까지 4~5 과정을 반복한다.

 

 

위의 테스트 Process 를 거치면 실제 생각했던것과 다른 상황을 많이 마주치게 된다.

 

가령, 우리는 응답시간이 큰 변동폭이 없을 것이며, 가용 자원량 만큼 TPS 가 올라갈 것이라 예측하고 한계점을 측정하겠지만, 실제로는 가용자원의 양은 처음에 급격이 증가하되, 어느정도 TPS 이상이 확보되면 일정해지며 응답시간은 TPS 증가에 따라 기하급수적으로 올라가게 된다.

 

이는 주로, Service architecture 가 Caching 을 하기 때문에 가용자원에 대한 부분을 상당 부분 부담해주기 때문이며, 응답시간의 경우 부하가 쌓이는 만큼 Connection Pool 에서 유저의 요청에 대한 Pending 이 생기기 때문이다.

 

 

아직 Performance Engineering 에 대해서는 지식이 짧아 정리하지 못했는데, 계속해서 공부를 해보면서 추가 포스팅 하도록 해야겠다.

 

(포스팅의 글이 너무 좋아 참조하였습니다. 

https://bcho.tistory.com/787, http://www.jidum.com/jidums/view.do?jidumId=562dumId=562)

 

iLiFO 지덤 :: 테지덤 성능테스트 – Little’s law 포함

I. 성능테스트의 개요와 원리 Little’s law 가. 시스템 테스트의 정의 사용자가 시스템을 사용하기에 성능상 문제가 있는 지를 검증하여 개선하기 위한 테스트 나. 소프트웨어 성능 테스트의 기본 원리 Little’s law ‘공간 내에 머무는 객체 수(L)’는 ‘객체의 공간유입량(λ)’과 ‘객체가 머무는 시간(W)’에 비례한다. 즉 L = λW이다. 시스템의 성능(TPS)는 트랜잭션을 발생시키는 사용자(Active User)를 평균응답시간(Mean

www.jidum.com

 

성능 엔지니어링 대한 접근 방법 (Performance tuning)

성능 엔지니어링에 대한 접근 방법 조대협 성능 개선, Performance Tuning, 용량 선정 과 같은 튜닝 관련 용어들은 모든 개발자나 엔지니어에게 모두 흥미가 가는 주제일 것이다. 그 만큼 소프트웨어에서 고성능을..

bcho.tistory.com

 

 

실제 라이브 서비스를 위해 서버 산정을 할 때, 서비스 규모에 걸맞는 트래픽을 감당하기 위한 테스팅은 필수적이다.

 

근래에는 클라우드의 발전으로 Scalability 나 Elasticity 를 확보할 수 있는 많은 방법과 기술들이 생겼다고는 해도, 결국 해당 솔루션을 사용하는 것은 "비용" 이며 서버개발자라면 이 비용을 최소화하고 효율적인 서버를 구축해내는 것이 "실력" 을 증명해내는 일이다.

 

여기서 피할 수 없는 백엔드 테스트의 종류들이 나타나는데, 일반적으로 스트레스 테스트 또는 부하 테스트라고 알려진 테스트들이다.

이 테스트들은 중요하지만, 개발자들이라도 차이를 잘 모르는 사람들이 꽤나 있고 서비스의 규모가 작다면 혼용되어 쓰는 경우가 다수이기 때문에 정리해서 알아둘 필요가 있다.

 

(1) Load Testing (부하 테스트)

말그대로 시스템이 얼마만큼의 부하를 견뎌낼 수 있는 가에 대한 테스트를 말한다.

 

보통 Ramp up 이라하여 낮은 수준의 부하부터 높은 수준의 부하까지 예상 트래픽을 꾸준히 증가시키며 진행하는 테스트로, 한계점의 측정이 관건이며, 그 임계치를 높이는 것이 목적이라 할 수 있다.

 

일반적으로 "동시접속자수" 와 그정도의 부하에 대한 Response Time 으로 테스트를 측정한다.

예를들어 1분동안 10만명의 동시접속을 처리할 수 있는 시스템을 만들수 있는지 여부가 테스트의 주요 관건이 된다.

 

(2) Stress Testing (스트레스 테스트)

스트레스 테스트는 "스트레스받는 상황에서 시스템의 안정성" 을 체크한다.

 

가령 최대 부하치에 해당하는 만큼의 많은 동시접속자수가 포함된 상황에서 시스템이 얼마나 안정적으로 돌아가느냐가 주요 관건이 된다. 

스트레스 상황에서도 시스템의 모니터링 및 로깅, 보안상의 이슈나 데이터의 결함 등으로 서비스에 영향이 가서는 안되므로 결함에 대한 테스트가 일부 포함된다.

가령 메모리 및 자원의 누수나 발생하는 경우에 대한 Soak Test 나 일부러 대역폭 이상의 부하를 발생시키는 Fatigue Test 가 포함된다.

 

이처럼 부하가 심한 상황 또는 시스템의 복구가 어려운 상황에서 얼마나 크래시를 견디며 서비스가 운영될 수 있으며 빠르게 복구되는지, 극한 상황에서도 Response Time 등이 안정적으로 나올 수 있는지 등을 검사하게 된다.

예를들어 10만명의 동시접속 상황에서 크래시율을 얼마나 낮출 수 있는가, 혹은 데이터 누락을 얼마나 방지할 수 있는가 에 대한 테스팅이 있을 수 있다.

 

(3) Performance Testing (퍼포먼스 테스트)

Load Test 와 Stress Test 의 모집합 격인 테스트의 종류이다. 리소스 사용량, 가용량, 부하 한도(Load Limit) 를 포함해서 응답시간, 에러율 등 모든 부분이 관건이 된다.

 

특정 상황에서 어떤 퍼포먼스를 보이는지에 대한 측정이 주가 되며, 서비스의 안정성보다는 퍼포먼스 자체에 집중한다.

 

주로 서비스적 관점에서는 Performance Test 보다는 Load Test 나 Stress Test 가 더 중점이 되며, 시스템 전체적인 성능의 양적 측정과 같은 관점에서 Performance Test 로 분류한다.

보통 그럴 때에 수행하는 테스트로 Benchmark 라고 하기도 한다.

 

 

위와 같은 종류의 테스트 들을 수행하는 데 있어 제일 중요한 부분 중 하나는 모니터링과 리포팅이라고 할 수 있겠다.

관련해서 경험과 같이 포스팅으로 정리할 예정이다.

 

 

 

SOA(Software Oriented Architecture) 란, 2000년대 초반부터 IT 업계 전반에 걸쳐 녹아든 IT System 의 패러다임이다.

 

고전의 Client-Server Architecture 에서 EJB 로 대표되는 n-Tier Model Architecture 로 진화한 웹 아키텍쳐는 2000년대 이후부터 비즈니스 요구사항에 발빠르게 대처하기 위한 구조로 Service-Oriented Architecture 라는 아키텍쳐로 진화한다.

 

SOA 란, 그전까지의 Application 의 Massive 한 기능들을 비즈니스적인 용도로 분류하고 기능 단위로 묶어서 하나의 표준 Interface 를 통해 각각을 "서비스" 로써 조합하는 Software Architecture 이다.

 

IT 업계의 요구사항이 많아지고 변화가 빨라짐에 따라 이에 대처하기 위해 나타난 Architecture 이고, 구성요소를 3가지로 분류한다.

 

1. Service Consumer - 서비스의 사용자. 사용자는 실제 클라이언트(End-User) 또는 다른 서비스가 될 수 있다.

2. Service Provider - 서비스 사용자에 요청에 맞는 서비스를 제공. Provider 는 다른 Service 의 Consumer 가 될 수도 있다.

3. Service Registry - 서비스의 정보를 저장한다. Provider 가 Registry 를 통해 Consumer 에게 서비스를 제공한다.

 

여기서 중요한건 Service Provider 와 Service Consumer 로, 이들은 실제 서비스의 유저 뿐 아니라 소프트웨어 아키텍처 내부의 "모듈" 도 대상이 될 수 있다. 

즉, SOA 는 비즈니스적으로 구분된 Service 들을 느슨하게 연결하며, 각 컴포넌트를 독립적으로 운용하여 조립이 가능하게끔 한다.

 

일반적으로 Service 를 위해 서비스 기능별로 모듈을 분리하고, 각 모듈이 다른 모듈과 상호작용할 수 있도록 만들어진 Architecture 를 SOA 라고 이해하면 된다.

 

여기까지 이해했으면 떠오르는 최근의 Architecture 모델이 있을 수 있다.

바로 Micro Service Architecture(MSA) 이다.

 

Micro Service Architecture 역시 각 서비스를 독립적으로 운용하며 서비스들을 조합하여 End User 에게 서비스를 제공하는 형태로 이루어진다. 그렇다면 이는 SOA 와 같은 아키텍처인가?

 

결론부터 말하자면, Micro Service Architecture 는 SOA 의 부분집합이라고 할 수 있다.

정확히는 SOA 는 패러다임이며, 그를 위한 Architecture 의 초안이고, Micro Service Architecture 는 SOA 의 패러다임을 따르되, 그 아키텍처를 따르지 않는다.

 

SOA 와 MSA 의 차이점

 

1. SOA 는 모듈의 의존성은 줄이되 모듈 내에서 공유할 수 있는건 최대한 공유하는 정책을 사용한다.

반면, MSA 는 가능한 공유하지 않고 모듈들이 독립적으로 운용될 수 있도록 아키텍처를 디자인 한다.

 

2. SOA 는 서비스의 Flow 를 유지하려하지만, MSA 는 Flow 의 구별을 요구한다.

가령, 서비스 내에서 결제를 하고자 할때, SOA 는 관련된 루틴을 수행하여 결제를 지원함으로써, 유저에게 제공해주는 "서비스" 를 1차 목적으로 한다.

반면, MSA 는 유저에게 관련된 루틴과 결제 루틴을 별도로 이용하게끔 한다. 즉 서비스 내의 독립이 아닌 독립된 서비스를 지향한다.

그렇다보니 SOA 아키텍처는 대게 어느정도 업격한 Protocol 과 Message 체계를 운용하게 되고, MSA 의 경우 별도의 체계가 없이 경량화된 프로토콜을 통해 운용되게 된다.

 

3. SOA 는 서비스들의 재사용에 중점을 두지만 MSA 는 서비스들의 독립을 추구한다.

이는 블로그 내 다른 포스팅에서 언급한 적 있는 Monolithic Architecture 와 유사한 부분으로, SOA 는 MSA 에 비해 보다 Monolithic Architecture 에 가깝다.

(참조 : https://jins-dev.tistory.com/entry/%EC%A0%84%ED%86%B5%EC%9D%98-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%AA%A8%EB%8D%B8-%EB%AA%A8%EB%86%80%EB%A6%AC%EC%8B%9DMonolithic-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98)

 

 

아키텍처를 디자인하는 일은 늘 어려운 일이고, 실무에서의 경험을 상당히 요하는 일이다.

아키텍처의 디자인과 이해를 위해서는 최신 트렌드 뿐 아니라 과거의 모델까지도 숙지해두는 것이 필요하겠다.

 

좀 더 자세한 설명은 다음 링크를 참조해보자.

https://dzone.com/articles/microservices-vs-soa-2

https://www.bmc.com/blogs/microservices-vs-soa-whats-difference/

 

 

실무에서 bit 연산을 사용할 때에는 주로 플래그 값들을 미리 정의해놓고 마스킹 하는 용도로 사용하지만,

알고리즘 문제를 풀 때에는 조합을 만들어낼 때에나, check 배열 또는 인덱스 값을 값싸게 저장하기 위한 용도로 많이 사용하게 된다.

 

그래서 자주 사용하지 않으면 헷갈리기 쉬운 bit 연산들을 몇가지 정리해보았다.

 

주로 사용되는 연산은 위와 같고, 해당 연산만 이용해도 비트 연산을 이용한 알고리즘의 상당수는 해결할 수 있다.

 

자주 쓰지않는 내용이라 까먹기 쉽지만, 필요할 때 쓸 수 있도록 잘 익혀두자.

 

 

Javascript 의 기본적인 내용이자 Scope 와 관련된 중요한 개념이다.

 

Javascript 는 많은 프로그래밍 언어와 다르게 Block-level Scope 가 아닌 Function-level Scope 를 따른다.

Block-level Scope 가 모든 코드 블록의 유효성 판단을 선언된 코드 블록({}) 단위로 하는 데 반면, Function-level Scope 는 함수 단위로 Scope 의 기준을 잡는다. 

 

Function-level Scope 의 중요한 특징은, 함수 내부에서 봤을 때 함수 외부는 Nested Scope 의 Outer function 이라 할지라도 Global 영역으로 취급된다는 점이다.

 

Hoisting 이란, Javascript 내에서 해당 변수 또는 함수의 "선언" 을 끌어올린다는 점이다.

여기서 주의할 점은 함수의 선언과 변수의 선언이 조금 다르다는 점이다.

위의 경우에 inner1 함수의 경우 정상적으로 호출이 된다. 해당 함수의 선언이 아래 있음에도 호출될 수 있는 이유는 해당 함수가 Hoisting 되기 때문이다.

반면 inner2 함수의 경우 Hoisting 되지 않는다. 그 이유는 변수의 선언과 함수의 선언이 다른 의미를 지니기 때문이다.

위와 같이 함수의 선언은 단순히 함수를 정의하면 되는데 반해 변수의 선언은 선언과 할당이 다르다.

 

이 때 의문이 생길 수 있다.

inner2 예제의 경우 "변수를 선언하고 함수를 할당한 것"이 아닌가?

 

정답은 "아니다." 이다. 정확히 예제의 inner2 의 경우 함수리터럴을 할당했다고 표현한다. 

함수 리터럴이란 함수의 이름없이 구현체만 연결시키는 방식으로, 이 경우에 Javascript Engine 은 이를 호이스팅시키지 않는다.

 

 

Javascript 의 클로저는 보통 Scope 와 같이 기술되는 특징적인 개념이다.

그 이유는 Closure 라는 개념이 Javascript 의 특징적인 Nested Scope 개념을 이용하기 때문이다.

 

다음 예제를 확인해보자.

 

위의 예제에서 함수 foo 는 innerFoo 의 실행 결과를 반환한다. outerFoo() 함수가 nestedFunction 을 return 해주고 있기 때문이다. 

이런 방식의 호출을 클로저(Closure) 라고 하며, 클로저로 정의된 innerFoo 함수는 외부 함수인 outerFoo 의 변수에 접근할 수 있기 때문에 Side effect 관리가 용이하며 정보 은닉(Private) 의 특성을 지닌다.

 

아울러 이렇게 정의된 outerFoo 는 innerFoo 를 위한 언어적 환경(Lexical environment) 를 제공하게 되기 때문에, 디자인 패턴적으로 높은 유연도를 가질 수 있게 된다. 

 

가령, outerFoo 를 innerFoo 를 위한 Factory 처럼 사용하거나 outerFoo 에 Parameter 를 받아 "독립적인 객체" 와 같은 환경을 만들거나 Singleton 처럼 사용할 수도 있다.

 

클로저의 중요한 특징 중 하나는 Scope Chain 을 이해하는 것이다.

근본적으로 Nested Scope 로 정의되는 Closure 는 3가지 종류의 Scope 를 가질 수 있으며 이를 순차적으로(Chaining) 접근하게 된다.

 

코드를 위와 같이 조금 변경해보았다.

 

(1) Local scope

 

(2) Outer function scope

 

(3) Global scope

 

위의 코드에서 Closure 는 위와 같은 3가지의 Scope 를 전부 가질 수 있으며,

동일한 이름의 변수에 대해서는 우선순위에 따라 다른 Scope 의 변수를 가리는(Hide) 모습을 볼 수 있다.

 

위와 같이 Closure 함수의 실행은 Scope 에서 Scope 로 전이되며 Scope 내에서 Scope 를 다시 선언하는 Scope chain 을 생성하는 과정이라 할 수 있다.

 

클로저를 이해하는 건 어렵지않으며, 중요한 것은 Scope chaining 을 이해하는 것이다.

 

주로 Javascript 의 유사한 동작을 패턴화 시키고, 그에 따른 독립적인 환경의 격리(private)가 필요할 때 사용하곤 한다.

 

+ Recent posts