210603 Server scaling

Server scaling

프론트엔드와 백엔드 서버의 구성

프론트엔드와 백엔드 서버를 각 각의 서버 PC로 구성하는 이유에 대해서 공부를 하던 중에 서버 스케일링(Server scaling)의 개념에 대해서 알게 되었다.

대규모 트래픽이 발생하는 웹 어플리케이션의 개발을 한다고 가정했을때, 프론트엔드와 백엔드 서버를 하나의 PC에 같이 관리하는 것과 각 각의 두 PC로 분리해서 관리하는 두 가지 경우로 나눠서 생각해 볼 수 있다.

우선 하나의 서버 PC에서 프론트엔드와 백엔드 서버를 모두 관리한다고 가정해보자.
만약에 프론트엔드 서버와 백엔드 서버가 8:2비율로
트래픽이 발생한다면, 프론트엔드 서버의 확장이 필요하다.

이때 서버의 스케일링을 하게 되면, 기존 서버 PC와 똑같은 서버 PC가 복제가 된다. 하지만 실제로 트래픽이 발생하는 서버는 프론트엔드 서버이기 때문에 백엔드 서버의 복제는 불필요하다.

이러한 이유로 프론트엔드와 백엔드 서버를 각 각의 서버 PC로 분리해서 관리하는 것이 자원을 아끼며 효율적으로 관리할 수 있는 것이다.

주문/배달 어플리케이션을 예로들면, 주문과 결제에 대한 트래픽이 많이 발생하기 때문에 주문과 결제를 개별 서버로 분리해서 서버의 확장이 필요한 경우에 해당 서버만 스케일링을 통해 확장하면 된다.

수직적 확장(Vertical Scaling)과 수평적 확장(Horizontal Scaling)

수평적 확장의 경우에는 로드 밸런싱이라는 것이 있기 때문에 서버 중에 하나가 실패하면 로드 밸런서가 요청을 다른 서버로 redirect하고, 새로운 서버가 추가되면 로드 밸런서가 해당 서버로 요청을 보내기 시작한다.
이처럼 수평적 확장의 경우에는 여러 서버와 효율적으로 작동하며 서버의 수를 늘리거나 줄일때 유연성을 제공한다.

하지만 수직적 확장의 경우에는 단 하나의 거대한 서버로 관리가 되기 때문에 위와 같은 수평적 확장이 가지는 유연함이 없다.
이외에도 수직적 확장은 하드웨어의 제한이라는 단점이 있다. CPU/RAM/DISK와 같은 리소스의 추가는 가능하지만, 사용자가 많아지면 충분하지 않은 경우가 생기게 되고 이러한 이유로 대기업에서는 좀 더 유연성을 제공하는 수평적인 확장이 이상적이다.

그렇다면 수직적 확장의 장점에는 어떤 점이 있을까?
수직적 확장의 장점은 데이터의 일관성이 있다는 것이다. 이는 모든 데이터가 보관된 세스템이 하나이기 때문이다. transaction이 여러 서버에 거쳐 데이터를 전송하는 수평적 확장 방식의 서버관리는 복잡성과 유지관리의 어려움이 있지만, 수직적 확장 방식의 서버 관리는 이러한 어려움이 없다.

소규모 회사를 운영하고 사용자 및 데이터가 증가하는 폭이 작다면 수직적 확장이 이상적이며, 사용자 및 데이터가 증가하는 폭이 높다면 수평적 확장이 이상적이다.

210603 Nodejs + Sequelize + MySQL

Nodejs + MySQL

Express.js 사용의 이유

아래와 같이 Node에서 제공하는 http 모듈 패키지를 사용해서 서버를 구현할 수 있다.
하지만 체계적으로 파일을 분리해서 관리하기에는 다소 어려움이 있다.
POST 방식의 처리와 GET 방식의 처리를 하기 위해서 req.method로 조건분기 처리를 하고, 그 안에서도 req.url을 통해서 요청받은 url을 구분해야 되기 때문에 조건처리하는 부분의 코드가 길어지게 되고, 코드의 가독성도 좋지 않다.

1
2
3
4
5
6
7
8
9
10
const http = require('http');
const server = http.createServer((req, res) => {
console.log(req.url, req.method);
res.write('<h1>SERVER TEST</h1>');
res.end('<h1>TEST END</h1>');
});

server.listen(3065, () => {
console.log('Server is running');
});

이러한 이유로 인해 Node.js의 프레임워크인 Express.js를 사용해서 서버를 구현하는 것이다.

head와 body

body는 res.json 또는 res.send로 보내지는 부분이다. head는 이 body(요청에 대한 응답)에 대한 부가적인 정보를 담고 있다.
(예를들어, 응답의 날짜, 데이터 타입, 용량 등의 정보를 담고 있다.)

Swagger API Documentation

Seagger 툴을 사용해서 API를 문서화할 수 있다.

[참고] : https://swagger.io/

REST API 방식

100% REST 방식을 지켜서 작성하기 어렵다. 어느정도 협상을 통해 REST 방식으로 API를 작성하도록 한다.

  • get : 취득
  • post : 생성
  • put : 전체 수정(통째로 덮어쓰기)
  • delete : 제거
  • patch : 부분 수정
  • options : 서버에 요청에 대한 확인
  • head : header 정보 취득

MySQL8

(1) MySQL Community server 설치
(2) MySQL workbench 설치 (terminal 말고 시각화해서 내부 데이터베이스 확인)

sequelize와 mysql2 driver 설치 및 설정

1
$ npm i sequelize sequelize-cli mysql2
  • mysql2 : node.js와 mysql을 연결해주는 드라이버

  • sequelize : 직접 query를 작성해주지 않아도 javascript를 sql로 변환해주는 라이브러리

1
2
# 기본 sequelize 설정
$ npx sequelize init
Read more

210511 Node.js TDD Practice 다섯번째 이야기 - 통합 테스트 (Integration Test) 코드 작성

Nodejs Mocking Http Request

통합 테스트(Integration Test)

모듈을 통합하는 단계에서 수행하는 테스트로, 단위 테스트를 통해서 모듈들이 모두 잘 작동하는 것을 확인한 뒤에 모듈들을 서로 연동하여 테스트를 수행하는 것을 말한다.

Supertest

단위 테스트에서 jest 모듈을 사용해서 테스트를 했다면, 통합 테스트는 supertest라는 모듈을 이용해서 구현을 한다.

단위 테스트에서는 MongoDB 부분은 문제가 없다는 가정하에 mock 함수로 처리를 했는데 통합 테스트에서는 supertest를 사용해서 실제로 요청을 보내서 테스트를 진행한다.

통합 테스트 코드 작성

통합 테스트 코드는 실제 작성한 코드의 로직을 기반으로 작성한다.

예를들어 아래와 같이 MongoDB에 req.body를 통해 넘겨받은 데이터를 새로 추가하고 추가된 데이터를 별도의 변수에 담아 response의 status code가 201인 경우에 json 데이터로 함께 전달하고 있다.

1
2
3
4
5
6
7
8
9
exports.createProduct = async (req, res, next) => {
try {
const newProduct = await productModel.create(req.body);
console.log('createProduct', newProduct);
res.status(201).json(newProduct);
} catch (error) {
next(error);
}
};

위의 코드를 통합 테스트 코드로 작성을 하면, 아래와 같이 통합테스트 코드를 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
const request = require('supertest');
const app = require('../../server');
const newProduct = require('../data/new-data.json');

test('POST /api/products', async () => {
// 실제 코드와 같이 요청을 보내고 함께 보낸 데이터를 response 변수에 담는다.
const response = await request(app).post('/api/products').send(newProduct);
// 위의 response 객체의 상태코드를 확인
expect(response.statusCode).toBe(201);
// response의 body(json) 데이터를 확인을 한다.
expect(response.body.name).toBe(newProduct.name);
expect(response.body.description).toBe(newProduct.description);
});
Read more

210510 Node.js TDD Practice 네번째 이야기 - 비동기 요청 에러에 대한 테스트 코드 작성

Nodejs Mocking Http Request

Error handling

이번 포스팅에서는 req.body로부터 넘겨받은 데이터를 MongoDB에 저장할때 발생할 수 있는 에러 케이스에 대해 에러 처리를 하기 위한 테스트 코드 작성에 대한 내용을 작성해보려고 한다.

현재 테스트에 있어서 프론트단이 별도로 개발되어있지 않기 때문에 Postman을 이용해서 임의로 만든 API를 테스트 할 것이다. Postman을 사용하면 API 개발의 생산성을 높여 줄 수 있으며, Request를 임의로 전달해서 테스트를 할 수 있다.

Postman

Postman의 사용은 위의 캡처와 같이 우선 테스트할 Request의 method를 선택을 하고, Endpoints를 작성해준다. 요청시에 Body에 별도로 데이터를 담아서 전달해야되는 경우에는 Body 옵션을 선택해서 전송하고자하는 데이터의 타입을 지정해서 데이터를 작성해주면 된다.

모든 설정이 끝난뒤에 Send버튼을 클릭해주면, 앞서 endpoint에 작성해준 URL과 mapping되는 router의 callback 함수(controller)가 앞서 Postman에서 설정한 내용을 기반으로 실행이 된다.
(Body에 별도의 데이터를 담아서 전달한 경우에는 callback 함수(controller)의 req.body를 통해 전달이 된다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.use('/api/products', productRoutes);

router.post('/', productController.createProduct);

// 비동기 요청에서 발생되는 에러에 대한 예외처리
exports.createProduct = async (req, res, next) => {
try {
const newProduct = await productModel.create(req.body);
console.log('createProduct', newProduct);
res.status(201).json(newProduct);
} catch (error) {
next(error);
}
};
Read more

210402 Node.js TDD Practice 세 번째 이야기

beforeEach, response status code and value, mockReturnValue(), _isEndCalled(), _getJSONData()

Nodejs Mocking Http Request

beforeEach() 활용해서 공통 코드 처리하기

여러개의 테스트 코드를 작성하면서 공통된 코드가 있다면 beforeEach 안에 작성을 해서 불필요한 코드의 반복을 줄여줄 수 있다.
beforeEach의 위치는 describe의 내부와 외부 모두 가능하다. describe 단위로 공통된 코드는 describe 내부에 beforeEach를 작성해서 공통된 코드를 처리해주고, 모든 describe에 공통적으로 참조해야하는 공통 코드가 있다면 이는 describe 외부에 작성을 해서 작성한 모든 테스트 케이스에서 공통 코드를 참조할 수 있도록 해야한다.
(아래 첨부한 첫 번째 노트를 참고)

response 객체를 통해 상태값 전달하기

request 객체의 body 속성으로부터 저장할 데이터에 대한 정보를 받아 데이터베이스에 저장을 했다면 이제 제대로 저장이 되었는지, 제대로 저장이 되었다면 상태값에 대한 정보를 보내줘야 한다.
상태값은 res.status(201)과 같이 response 객체의 status로 상태코드를 인수로 넘겨준다.

테스트 코드에서는 mock response 객체의 statusCode 속성을 참고해서 전달된 상태값을 확인할 수 있다.
expect(res.statusCode).toBe(201)

response 객체를 통해 결과값 전달하기

앞서 response 객체의 status로 상태코드를 인수로 넘겨서 상태값을 전달하였다.
그렇다면 추가적으로 결과값을 전달해야될 때에는 어떻게 해야할까?
(아래 첨부한 세 번째 노트 필기 참고)

Read more

210401 Node.js TDD Practice 두 번째 이야기 - jest.fn() and node-mocks-http

jest.fn() Mock function

expressjs mongodb image

jest.fn()

이번 포스팅에서는 jest에서 제공해주는 Mock 함수(jest.fn())에 대해서 정리를 해보려고 한다.
mock은 한글로 직역하면 ‘모의’라는 의미를 가지며 가짜, 흉내내는 이라는 뜻을 가지고 있다.

Mock 함수는 단위 테스트를 작성할 때에 테스트하려는 코드가 의존하고 있는 부분을 가짜로 대체하는 일을 해준다.

여기서 의존하는 부분을 가짜로 대체하는 이유는 무엇일까?

의존하는 부분을 가짜로 대체하는 이유는 우선 첫 번째, 의존적인 부분을 개별적으로 구현하기 까다로운 경우가 있다. 그리고 두 번째, 의존적인 부분의 상태에 따라 테스트 결과가 다르게 나온다면 안되기 때문에 의존적인 부분에 의해 테스트 결과가 영향을 받지 않도록 해야한다.

데이터베이스의 데이터 조작을 테스트

데이터베이스의 데이터를 추가하는 부분을 테스트한다고 가정했을때, 실제 데이터베이스를 가지고 테스트를 한다면 Network, I/O task, Transaction creation, Query transmission etc...의 다양한 작업과 데이터 베이스에서 변경된 데이터를 직접 원상복귀하거나 Transaction rollback 해야하는 경우에 데이터 베이스에 데이터를 저장하는 부분 테스트에 고려해야 되는 작업이 많아 비효율적인 방법이 될 것이다.
혹여나 테스트 도중에 실 데이터베이스가 죽어버린다면 테스트 결과에 영향을 미치게 될 것은 뻔하디 뻔한 상황일 것이다.

그래서 이러한 의존적인 부분을 jest.fn()을 이용해서 가짜 함수를 생성함으로써 해결할 수 있다.

이 jest.fn() 함수는 매우 유용하게 사용될 수 있는데, 생성한 가짜 함수에 어떤 일들이 발생했는지, 다른 코드들에 의해서 어떻게 호출이 되는지 기억해주는 역할을 해주기 때문이다.

함수가 내부적으로 어떻게 사용되는지 검증할 수 있다.

구체적인 mock 함수 사용은 아래 첨부한 노트 1~2을 참고하도록 하자.

Read more

210331 Node.js TDD Practice

Express.js + MongoDB

expressjs mongodb image

이전에 배웠던 NodeJS의 내용을 복습하기 위한 목적으로 간단한 앱을 만들어 본 적이 있는데 만든지 시간이 좀 지나서 다시 상기시킬 겸 Express.js + MongoDB의 형태를 TDD 방식으로 연습을 해보려고 한다.

React도 TDD 방식으로 개발하는 방법에 대해서 공부를 해보며 실습을 해보았는데, NodeJS도 JavaScript 기반이기 때문에 Jest라는 공통 라이브러리를 사용해서 테스트 코드를 작성한다.

이미 React를 위한 단위 테스트 코드를 작성해 본 경험이 있기 때문에 테스트 코드 작성의 개념적인 부분과 작성방법은 다시 복습하는 느낌이었다.

처음에 테스트 코드를 작성했을때에는 이전에 개발을 했을때 다른 언어로 단위 테스트 코드를 작성해 본 경험이 있었음에도 생각보다 익숙해지는데 여러번 반복이 필요했다.
요즘 새로운 것들을 스스로 학습하면서 느끼지만, 이 프로그래밍이라는 언어는 사람관계와 비슷한 점이 많은 것 같다.

Read more

210330 bodyParser와 express embedded middleware(express.json())

본 포스팅 내용은 과거에 개인적으로 공부할때 정리했던 NodeJS의 내용을 복습의 목적으로 다시 정리하는 포스팅입니다.


Front-end

Front-end에서 post방식으로 body에 데이터를 담아 보내는 경우,

1
2
3
4
5
// front-end에서 body를 함께 Request를 보내는 경우,
axios.post('/products', {
name: 'Lee Hyungi',
description: 'Hi!'
});
Read more

210204 HTTP - Request/Response

본 포스팅 내용은 과거에 개인적으로 공부할때 정리했던 NodeJS의 내용을 복습의 목적으로 다시 정리하는 포스팅입니다.


GET/POST 방식 비교표

GET 방식과 POST 방식은 위와같이 정보 전송방식과 보안적 측면, 전송할 수 있는 데이터의 길이, 전송데이터의 Caching 가능 유/무 등에서 차이점을 보인다.

HTTP의 동작방식에서 URL을 통해서 browser에 접속하게 되면, browser가 GET method 방식으로 Browser의 Page를 읽어온다.

로그인과 같이 중요한 정보를 전송할때에는 POST method로 browser에서 server로 정보를 전달하게 된다.

간단하게 browser상에서 /(root) 경로로 이동했을때의 처리를 작성해보자.

index.js

1
2
3
4
5
const handleHome = () => {
console.log('Hi from home!');
};

app.get('/', handleHome);
Read more

210204 Build a server using ExpressJS & Babel

본 포스팅 내용은 과거에 개인적으로 공부할때 정리했던 NodeJS의 내용을 복습의 목적으로 다시 정리하는 포스팅입니다.


Express 프레임워크를 사용해서 NodeJS 서버 작성

1
2
3
4
5
6
7
8
9
10
11
// express라는 이름의 파일을 찾아보고, 없으면 node_modules내부에서 찾아본다.
const express = require('express');
const app = express();

const PORT = 4000;

const handleListening = () => {
console.log(`Listening on: http://localhost:${PORT}`);
};

app.listen(PORT, handleListening);

package.json에 entry command 만들어주기

1
2
3
4
// package.json
"scripts":{
"start": "node index.js"
}
Read more