[GCP 원데이] CPU를 극단적으로 사용하는 애플리케이션
1. CPU를 극단적으로 사용하는 애플리케이션
1) 컴퓨터 부품
컴퓨터에서 가장 주요한 부품은 하드디스크, 메모리, CPU이다.
- 애플리케이션은 일반적으로 하드디스크에 담겨있고 이를 프로그램이라고 지칭한다.
- 하드디스크에 있는 프로그램을 실행시키면 메모리 위로 올라가는데 이를 프로세스라고 한다.
- 메모리 위에는 실행시킨 프로그램 외에도 여러개의 프로세스가 함께 올라가 있다.
- 이 프로세스중 누군가는 CPU에 의해 실행되는데, 어떤 프로세스가 실행될지 결정하는 것일 스케줄링이라고 한다.
2) 메모리가 필요한 이유?
메모리가 필요한 이유는 속도차이 때문이다. CPU는 하드디스크보다 굉장히 빠르게 때문에 CPU가 프로세스를 실행하기 위해 하드디스크에게 직접 요청하게 되면 CPU가 아무리 빨라도 하드디스크의 속도와 같아지게 된다. 즉, 병목 현상이 생기게 된다.
따라서 중간에 속도차이를 줄여주기 위한 메모리가 존재하는 것이며, 메모리와 CPU간의 차이마저 줄여주기 위해 '메모리보다 빠르지만 CPU보다는 느린' 캐시 메모리가 존재한다.
3) I/O Burst-Bound, CPU Burst-Bound
CPU는 하드디스크에게 직접 통신하지 않지만 상호작용할 필요는 있다. 이런 작업을 하는 동안 CPU는 놀고 있는 것이 아닌 다른 프로세스를 실행시켜 최대한 CPU를 효율적으로 사용한다.
이처럼 읽고 쓰는 작업을 입출력(I/O)이라고 한다. 즉, 프로세스가 I/O를 하는 동안에는 다른 프로세스가 CPU를 사용하는 것이다. 이러한 I/O의 종류에는 하드디스크만이 아닌 DB나 네트워크에 대한 I/O도 존재한다.
한 프로세스 실행 도중, I/O를 하는 시간을 I/O Burst라 이야기하고 CPU에서 실행되는 시간을 CPU Burst라고 한다. 또한 해당 프로세스가 전체적으로 I/O를 많이 하는 애플리케이션이라면 I/O Bound라하고 CPU를 많이 사용하는 애플리케이션이라면 CPU Bound라고 한다.
하단의 그림이 두 개의 프로세스를 나타낸다고 할 때, 검은 선이 I/O라고 하고 회색 막대가 CPU라고 하는 경우 다음과 같다.
- 상단의 프로세스는 I/O Burst가 있긴 하지만 CPU Burst가 주를 이루는 애플리케이션
- 하단의 프로세스는 CPU Burst가 있긴 하지만 I/O Burst가 주를 이루는 애플리케이션
- 상단의 프로세스는 CPU Bound 애플리케이션
- 하단의 프로세스는 I/O Bound 애플리케이션이 된다.
4) CPU를 많이 사용하는 프로그램
여기서 CPU를 많이 사용하는 프로그램은 I/O를 적게 사용하고 CPU 연산을 많이 사용하는 애플리케이션을 뜻한다. CPU를 많이 사용하는 애플리케이션을 만들기 위한 다양한 방법들이 존재한다. 그 중 MD5 Hash 연산을 이용하는 방법은 다음과 같다.
우선 Spring Boot 프로젝트를 만들어 간단하게 10만번 해시를 하여 반환하도록 컨트롤러를 생성해준다.
package com.example.cpu;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@RestController
public class HashController {
@RequestMapping("/hash/{input}")
public String getDigest(@PathVariable("input") String input) throws NoSuchAlgorithmException {
for(int i = 0; i < 100_000; i++) { // 10만번 MD5 해시하고 반환
input = getMD5Digest(input);
}
return input;
}
@RequestMapping("/hello")
public String hello() {
return "hello";
}
private String getMD5Digest(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input.getBytes());
byte[] digest = md.digest();
String myHash = DatatypeConverter
.printHexBinary(digest).toUpperCase();
return myHash;
}
}
올바르게 동작하는 것을 확인하였다면 GCP에 애플리케이션을 배포할 수 있도록 jar 파일로 만들어 주어야한다. Spring Boot Gradle의 경우 빌드시 Build 디렉터리가 생성이 되는데, 이 디렉터리의 libs 하위에 jar 파일이 생성이 된다.
이 jar 파일을 GCP가 다운받을 수 있도록 깃허브에 올리고, 깃허브에 있는 파일을 GCP 인스턴스가 다운로드 받아 가도록 해준다.
여기서 Hash 알고리즘은 동일한 입력을 넣으면 항상 동일한 출력이 나오는 특성이 있다 따라서 파일 내용이 변경되었는지 확인하는 무결성 검사를 위해 사용되기도 한다.
Hash란 단방향 암호화 기법으로 해시 알고리즘을 이용하여 고정된 길이의 암호화된 문자열로 바궈버리는 것을 의미한다. 해시 함수는 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수이다. 이 때 매핑 전 원래 데이터의 값을 키, 매핑 후 데이터의 값을 해시값, 매핑하는 과정을 해싱이라고 한다. 별도로 해시를 만들어주는 사이트도 있다.
해시 알고리즘은 종류가 다양하고 알고리즘마다 해시 길이가 다르다. 해시 알고리즘은 모두에게 공개되어있다. 따라서 해커에게도 공개되어있기때문에 이미 보안이 뚫린 해시 함수들이 존재한다. 대표적으로 MD5, SHA-1, HAS-180은 사용해서는 안되는 해시 알고리즘이다. 대신 SHA-256, SHA-512(권장) 등을 사용하기를 권고하고 있다.
5) 인스턴스 생성 및 설정
인스턴스를 새로 생성해주고, wget과 java를 설치해준다.
sudo yum install wget
sudo yum install java
sudo wget https://github.com/ozofweird/cpu-bound-application/raw/master/cpu-0.0.1-SNAPSHOT.jar
sudo java -jar cpu-0.0.1-SNAPSHOT.jar
'/hash/123'으로 접속했을 때 약 100ms가 걸리는 것을 확인할 수 있다.
'/hello'는 대략 20ms 정도 걸린다.
결국 (100-20)ms 가 MD5 횟수를 10만 번 정도 수행하는 시간이라고 생각하면된다.