개요

웹 포트폴리오를 만들기 위한 과정을 기록하려고 한다.

기록하는 이유는 그냥 내가 기억하려고... 누구 보라고 쓰는게 아니라서 가독성이 떨어질 수 있다.

 

웹기술 관련 업계 동향

웹만큼 빠르게 변화하는 업계는 없는 것 같다. 개인적으로 생각하는 업계 동향을 적어본다.

 

Front-end

프론트라는 개념이 나온 건 얼마 안됐다. 모든 마크업은 원래 서버의 담당이었다. 브라우저는 그저 그려낼 뿐.

동적인 웹의 필요성을 충족하기 위해 Javascript가 존재하긴 했지만 지금처럼 고도화 된 목적은 아니었다.

그러나 브라우저에서 점점 많은 걸 하게 되고 Ajax, jQuery 등을 거치면서 결국 프레임워크까지 오게 된다.

Angular(구글), React(페이스북)가 시작을 끊었고, 이보다 더 가벼움을 추구하는 Vue, Backbone등도 등장했다.

 

 

Back-end

원래 소규모 사이트는 PHP로, 좀 규모가 좀 된다 하면 ASP/JSP로 개발하는 것이 오랜 시간 지속되어 왔다.

다만 ASP는 IIS를 위해 돈이 좀 들어가므로 JSP 또는 Java 기반(EJB, ...)으로 작성하는 것이 선호되었다.

그러다 로드 존슨이 Java Enterprise의 단점들을 극복하는 책을 내면서 Java Spring이 등장했다.

또 어느 순간 스크립트 언어들이 우후죽순 생기며 다양한 백엔드가 등장했다.

PHP(Laravel 또는 Codeigniter), Python(Django), Ruby(Ruby on Rails) 등...

정점으로 Google V8 Engine이 퍼블릭으로 공개되면서 Javascript 또한 Node.js도 백엔드 대전에 참여했다.

 

포트폴리오 준비 계획

Kakao, Naver, LINE 채용 페이지에서 과연 어떤 경력직을 모집중인지 조사했다.

그리고 Web 기술이 아닌 것들은 배제했다. (핀테크, Video Streaming, Machine Learning 등)

 

대체로 요구하는 기술은 아래와 같다.

[공통]

RESTful API 개념, MSA 개념, MVC 패턴

형상관리 (Git)

Single Page Application (SPA) 개념

Linux, Shell Script, MySQL, NoSQL

 

[JavaScript]

ECMAScript5 (2015), TypeScript

Node.js, MongoDB, Express

jQuery, Backbone.js, React(+Redux), Angular, Vue.js

webpack, Electron

Bootstrap

 

[PHP]

Laravel

 

[Java]

Spring, Playframework(Scala)

 

얼추 프론트는 React, Angular, Vue 세 가지를 골고루 채용하고 있었고, 백엔드는 Spring과 Laravel을 채용하고 있다.

나에게 주어진 한달 남짓의 기간에 이걸 전부 공부하기에는 시간이 모자라다는 판단이 든다. 선택과 집중의 시간.

 

프론트와 백엔드에서 결정하기가 어렵다. 둘 다 취업을 열어놓고 공부를 해야 할 것 같고...

그렇다면 Node.js로 백엔드 공부를, 빠르게 배울 수 있는 Vue로 입문을 하고자 한다. 언어가 같아서 효율적이기도 하고.

 

웹 관련 개념

RESTful API

HTTP Request의 GET/POST/PUT/DELETE를 이용해서 CRUD(Create, Read, Update, Delete) 기능을 구현하는 것이다.

기능적인 측면만 봤을 때는 위와 같고, 사실 그 뒷면에 이념이나 규칙 등은 조금 더 자세하다. 그 내용은 아래 소스들 참고.
 
https://meetup.toast.com/posts/92 (REST API 제대로 알고 사용하기)
 
CRUD를 테스트하는 툴로 Postman을 많이 사용한다. (https://www.getpostman.com)
 

MSA (Microservice Architecture)

서비스를 작은 단위로 쪼개서 운영하는 구조. 반대말은 Monolithic Architecture이다. (앞으로 Mono와 Micro라고 하겠다.)

 

Monolithic 아키텍처는 쉽게 말하면 한 웹서버 안에 모든 기능을 다 때려박은 구조이다. 단순하므로 개발이 빠르고 쉽다. 장점들은 많다.

 

그런데 몇 가지 문제가 있다. (쿠팡 블로그에서 참조함)

1) 일부가 전체에 영향을 미친다. 코드에 메모리 누수가 발생하면 그 서버 전체가 다운되는 수가 있다.

2) 부분적인 Scale-out이 안된다. 일부 서비스에만 오버로드가 발생한다 해도 전체의 스케일을 키워야 한다.

3) 여러 컴포넌트가 강하게 연결되어 있어, 변경이 어렵다. 강결합의 단점은 코드 수정 시 다른 코드에도 영향이 갈 수 있고, 그 정도를 모른다는 점이다.

4) 일부 수정에도 높은 테스트 비용이 든다. 특정 부분만 수정해도 테스트는 전체 서비스에 대해 이뤄지므로 비용이 비싸다.

5) 서비스 규모가 커질 수록 배포 시간이 오래 걸린다.

 

따라서 서비스를 작은 단위로 쪼개고, 물리적인 서버도 분리하고, 각 서비스끼리는 Message Queue를 두어 통신하게 한다.

 

이외에 자세한 내용은 아래 소스들 참고.

 

https://alwayspr.tistory.com/20 (Microservice Architecture 란?)

https://jason-lim.tistory.com/1 (Microservice Architecture에 대한 이야기 - Integration)

https://medium.com/coupang-tech/행복을-찾기-위한-우리의-여정-94678fe9eb61 (쿠팡의 MSA — Part 1)

 

MVC 패턴

개발할 때 Model-View-Controller 3가지 형태로 역할을 분리하여 개발하는 방법론.

 

처음에는 '왜 굳이 번거롭게?' 라는 생각이 들 수 있으나, 나중에는 합리적이라는 생각이 든다.

물론 개발 과정이 기존처럼 파일 하나에 다 때려박던 것보다 번거로운건 사실인데, 이렇게 분리함으로써 나중에 관리하기가 편해진다.

 

[Model] 내부 비즈니스 로직을 맡는 역할. 알고리즘, DB, 데이터 관리 등

[Controller] Model과 View를 연결, 또는 유저와 연결되는 역할. 유저의 명령을 받아 Model에게 일을 넘겨주고 그 결과를 View를 통해 보여줌.

[View] 화면에 무언가 보여주기 위한 역할.

 

각자 하는 역할이 분리된다. 좀 더 자세히 설명하자면

Model은 데이터 혹은 작업 요청서(?)만 받아서 자기 일을 하고 Controller에게 주면 된다. 사용자랑 무슨 일이 벌어지는지는 관심 없다.

View는 Controller가 요청하면 자신의 모양대로 데이터를 그려내면 된다.

Controller는 Model-View를 중간에서 관리하면서, 유저와 연결되어 있기도 하다.

예)

사용자는 Controller에게 요청을 한다.

Controller는 필요에 따라 Model 에게 작업 혹은 데이터를 주어 처리한다.

그리고 그 결과를 View에게 전달하여 사용자에게 화면을 보여준다.

 

더 자세한 내용은 아래 소스들 참고.

 

https://medium.com/@jang.wangsu/디자인패턴-mvc-패턴이란-1d74fac6e256 ([디자인패턴] MVC 패턴이란?)

https://jayzzz.tistory.com/40 (MVC패턴 모델, 뷰, 컨트롤러)

 

Single Page Application (SPA)

이름 그대로 페이지 하나에 모든 기능을 때려박는(?) 접근 방법이다. 모던 웹의 패러다임이라고 할 수 있다. 페이스북이 이런 식이다.

 

장점은 페이지 로드 처음에 일단 모든 리소스를 다운받고, 이후에는 변하는 부분만 서버에서 내용을 받아와 표시한다.

따라서 새로고침이 발생하지 않으므로 Native App과 같은 사용자 경험을 줄 수 있다. 트래픽이 줄어들어 모바일 환경에서 좋다.

 

단점은 최초 1회 로딩에 대하여 많은 양의 리소스를 받아야 하므로 초기로딩속도가 느려지게 된다.

더불어 내용이 상황에 따라 동적으로 변하기 때문에, 검색엔진에서 크롤링이 불가능하다.

 

이외에 장단점이 더 많이 있다. 역시나 아래 소스들 참고.

 

https://poiemaweb.com/js-spa (Single Page Application & Routing)

https://medium.com/@bbirec/spa-single-page-application-개발에서-고려할-사항-eedcb7cb618f (SPA 개발에서 고려할 사항)

https://m.mkexdev.net/374?fbclid=IwAR3H9b90QaKAuuNPgJMfoXnWwfsnU4glF4uBp0QSDhAlnBJ8gNb8D5cdeKQ (SPA 단점에 대한 단상)

 

 


웹 기술 공부

Node.js (Back-End)

Server-Side에서 동작하며 Network Application을 제작할 수 있는 플랫폼이다. 언어는 JavaScript를 사용한다. (Google V8 엔진 기반)

 

혼동하지 말 점은, Node.js는 Web Server가 아니라는 점이다. (Apache가 아니다!)

그냥 JavaScript를 실행(Run)할 수 있게 해 주는 런타임(runtime)이다. 마치 Python처럼 말이다.

 

Node.js가 웹 서버로서 동작하려면 라이브러리(주로 Express)를 이용하여 HTTP 서버 코드를 직접 작성해야 한다.

여기서 직접 작성한다는 말은, Apache가 FTP로 파일을 툭 올렸을 때 폴더 기반으로 파일의 URL을 알아서 처리해 줬던 것과는 달리

Node.js는 URI를 해석하는 것부터, 어떤 파일과 매칭시킬 지 혹은 어떤 코드를 실행시킬 지를 코드로 일일이 라우팅(Routing) 해 주어야 한다.

 

기존 Apache+PHP 환경에 익숙한 사람은 Node.js가 다소 불편할 수도 있다. 지향하는 바가 조금 다른 것 같다.

 

Apache는 FTP로 올린 파일의 경로가 URL에도 1:1로 매칭되는 시스템이다. 즉, 파일 위치가 곧 URL이다.

Node.js는 파일의 위치와 URI가 다른 경우가 허다하다. URI와 파일을 직접 매칭 시켜줘야 한다. 그래서 번거로울 수도 있다.

 

앗, URL과 URI라고 다르게 표기한 것을 눈치챘는가?

더보기

URL의 L은 Locator, 다시 말하면 URL이 파일의 위치를 나타내는 의미를 갖는다.

URI의 I는 Identifier, 위치가 아니라 식별(?) 역할을 하게 된다.

 

URI의 예시:

http://daum.net/login 에서, login 부분은 '기능'을 나타낸다. login 이라는 파일은 없다!

https://www.facebook.com/acuworld 에서, 아이디 부분은 당연히 파일이 아닐 것이다. acuworld는 '정보'를 의미한다.

 

 

URI를 사용하여 파일과는 상관 없이 기능을 자유자재로 매핑시킬 수 있다는 점은 Node.js의 장점이다.

(물론 Apache에서도 rewrite 모듈을 사용하면 가능하긴 하다...)

 

 

입문(Tutorial)

Node.js는 공식적인 튜토리얼이 없고, API 문서만 제공한다.

따라서 외부 튜토리얼로 공부해야 한다. 나는 다음 2개의 튜토리얼을 통해 학습하였다.

 

https://www.w3schools.com/nodejs (영문)

https://velopert.com/node-js-tutorials (한글)

https://backend-intro.vlpt.us (백엔드 기초 다지기, 프로젝트 시작하기)

 

 

설치

학습을 위해 Amazon AWS에 가입하고 프리티어(1년)를 받아 가상서버에서 진행하였다.

Red Hat 리눅스에 설치하였고, 버전은 Node.js 11.10.0 (npm 6.7.0)이다.

 

엔터프라이즈(RedHat, CentOS, Fedora)는 NodeSource라는 곳에서 바이너리를 배포한다고 한다. (공식 사이트의 안내사항이다.)

아래 링크에서 쉽게 설치하는 쉘스크립트를 제공하고 있다. 명령어 한 줄로 Node.js를 설치할 수 있다.

https://github.com/nodesource/distributions/blob/master/README.md

 

 

Hello World 찍어보기

다음 코드를 hello.js에 작성해본다. (vim 이용)

 

console.log("Hello, World!");

 

node를 실행하는 명령어는  node 이다. hello.js를 실행하려면  node hello.js 를 입력하면 된다.

 

 

아주 간단한 웹서버 만들기

웹서버를 만들기 전에, HTTP 프로토콜에 대한 기반 지식이 필요하다. 특히 헤더의 구조에 대해 알고 있어야 한다.

 

아래 코드는 http 모듈을 불러오고, 8080 포트에 http 서버를 오픈하는 예제이다.

그 어떤 URI로 접속하던지 동일한 결과를 출력할 것이다. Header에 Response Code: 200(성공)을 보내고, Body는 Hello World.

 

var http = require("http");

 

http.createServer(function(request,response) {

    response.writeHead(200, {'Content-Type': 'text/plain'});

    response.end("Hello World\n");

}).listen(8080);

 

코드를 해석한다. createServer()는 웹서버 객체(Object)를 만든다. 그리고 객체를 만들 때 익명 함수를 작성하여 전달했다.

더불어 웹서버 객체에 대해 8080 포트로 요청을 받아들이도록 listen() 메소드를 호출했다.

 

이제 해당 웹서버는 8080 포트로 접속이 들어올 때마다, 등록한 익명 함수를 호출할 것이다.

 

웹서버 예제는 더 복잡한 거 말고 여기까지만 하는 게 좋다. 어차피 Express 모듈을 써서 웹서버를 만들 것이기 때문.

 

 

Node Package Manager (NPM)

NPM은 패키지/모듈을 쉽게 설치하고 관리할 수 있도록 해 주는 명령어이다. (RedHat의 yum, Ubuntu의 apt-get과 같은 역할)

 

Node.js로 사이트(프로젝트)를 만들 때 수많은 모듈을 짜집기해서 만들게 된다. 따라서 필연적으로 다음과 같은 2개의 문제점이 발생한다.

 

1) 프로젝트마다 필요한 모듈이 다를 것이다.

2) 프로젝트의 개발 시점에 따라서 사용한 모듈의 버전이 달라질 것이다.

 

따라서 이 프로젝트에 어떤 모듈이 사용됐는지, 모듈의 버전이 무엇인지를 프로젝트 내 package.json 파일에 기록해야 한다.

물론 package.json은 NPM에 의해 자동으로 관리된다. 그런 역할을 NPM이 해 주고 있다.

 

만약 다른 사람의 프로젝트 파일을 다운받았다고 가정해보자. NPM에서 제공하는 간단한 명령어 2~3줄로

해당 프로젝트의 의존성(Dependency) 패키지들을 한 번에 설치하고, 프로젝트를 바로 실행할 수 있다. 

 

 

# 프로젝트 생성

npm init

 

리눅스에서 적당히 폴더를 하나 만들고, 위 명령어를 치면 이제부터 해당 폴더가 프로젝트 폴더가 된다.

프로젝트의 이름, 버전, 제작자 등의 정보를 기입할 수 있다. 귀찮으면 전부 엔터를 쳐버려도 된다.

 

 

# 특정 모듈 설치

npm install [모듈명]

 

# 현재 프로젝트의 모든 Dependencies 모듈 설치

npm install

 

위 명령어는 모듈 파일을 다운받아 프로젝트 폴더에 설치해 주지만, package.json에는 기록하지 않는다.

 

--save 옵션을 주어야 package.json에 "Dependencies" 항목으로 기록된다.

--save-dev 옵션을 주면 "DevDependencies" 항목으로 기록된다. (음... 쉽게 얘기하면 개발용과 배포용을 따로 사용할 수 있다.)

--dev 옵션을 주면 "DevDependencies" 항목의 패키지를 설치한다. 옵션 안주면 일반 Dependencies만 설치된다.

-g 옵션을 주면 글로벌로 설치한다. 기본값은 로컬 설치(현재 프로젝트 폴더에만 설치)이다. 글로벌 설치는 아예 시스템에 파일이 박힌다. (sudo 필요)

 

 

이외에도 NPM의 다양한 기능들이 있다.

예를 들어 npm run-script 명령어는 아주 편하고 많이 사용되는 기능이다. script 관련 내용은 아래 package.json에서 익힐 수 있다.

 

 

package.json

다음 블로그에서 잘 설명해 주고 있다:

https://programmingsummaries.tistory.com/385 (모두 알지만 모두 모르는 package.json)

 

 

Callback Function(콜백 함수) 개념

이 개념을 받아들이기 어려워 하는 사람도 있다. 생소하기 때문이다.

자세한 설명은 다른 블로그가 더 잘 해주고 있고, 여기서는 개요만 설명하겠다.

 

 

기존 Blocking 코딩에서, get_number() 함수가 처리에 5초 걸린다고 가정해 보겠다.

 

var a = get_number(); // 5초 걸리는 놈

console.log(a); // get_number()의 결과를 출력

 

next_function();

 

프로그램의 실행 흐름은 다음과 같다:

get_number() 호출 --> 5초 대기(blocking) --> a 출력 --> next_function(); 실행

 

즉, next_function() 입장에서는 앞 코드 실행이 끝날 때까지 기다려야 자기가 실행될 수 있다.

 

 

다음 예제는 Node.js가 익명 함수를 사용하여 Blocking을 없애고 있는 모습이다.

 

get_number(  function(req,res) { console.log(req); }  );

next_function();

 

get_number() 호출 --> next_function() 호출 --> 5초 후 --> 익명 함수(function)를 호출해서 결과 출력

 

next_function()은 아무런 대기 시간 없이 즉시 실행될 수 있다.

get_number() 함수가 끝났을 때 무슨 코드를 실행할 지를, 익명 함수를 통해 작성하면 된다. (Non-blocking 구현)

익명 함수는 본 함수의 처리가 끝났을 때 호출될 것이다.

 

 

자, 지금까지는 기존 언어(Java 등)에 익숙했기 때문에 '익명 함수' 라고 불렀지만, 사실은 이게 '콜백 함수'이다.

처리가 끝나면 불려지는 함수를 콜백 함수라고 한다.

 

 

모듈 시스템

JavaScript는 모듈 시스템이 없었다. <script src="jquery.js"> 와 같이 페이지 내에 다수의 스크립트를 한 번에 로드해야 했다.

이렇게 하면 단점은 다른 모듈의 코드나 변수에 접근이 가능하다는 점이다.

 

ECMAScript 2015(ES6)부터 모듈 개념이 도입되었다. 모듈은 각각이 개별 프로그램이라는 느낌이 든다. 변수나 패키지도 그 안에서만 사용된다.

문제는 Node.js의 모듈 시스템과 ES6의 모듈 시스템이 호환이 안된다는 점이다.

 

정확하게는 Node.js 모듈 시스템(require)이 자체적으로 먼저 가지고 있었고, ES6 모듈 시스템(import)이 나중에 나온 것이다.

현재까지도 Node.js는 ES6의 모듈(import) 문법을 지원하지 않는다. Node.js에서 import 문법을 쓰려면 Babel 툴로 컴파일을 해야 한다.

 

Babel은 ES6/ES7 문법을 ES5로 transpiling 해 주는 컴파일러이다. 일부 브라우저에서 ES5 까지밖에 지원을 안하므로 이런 툴을 써야 하는 것이다.

 

 


Express 모듈

Node.js로 웹 어플리케이션을 만들기 위한 프레임워크이다. 다양한 웹 관련 기능들을 제공한다.

 

설치

 npm install express --save  으로 간단하게 할 수 있다.

 

 

입문(Tutorial)

대부분의 내용은 아래 소스를 통해 공부하였고, 본 블로그에는 생소한 개념이나 어려웠던 점 위주로만 기록하고자 한다.

 

https://expressjs.com/ko/starter/hello-world.html

https://velopert.com/node-js-tutorials

 

 

라우팅(Routing)

새로운 개념이다.

기존에 Apache+PHP를 사용할 때는 코드를 PHP 파일로 작성하고 FTP로 서버에 올리면 끝이었다. FTP에 올린 경로가 곧 URL이었다.

Apache는 URL을 해석하여 해당 위치의 파일을 PHP 런타임에 전달하고, 결과로 출력된 HTML 코드를 그대로 사용자에게 보내주었다.

 

Node.js에서는 URL이 파일의 위치를 의미하지 않는다. (물론 파일을 의미하게 할 수도 있지만...)

내가 직접 URL을 분석하여, 어떤 행동을 할 지 또는 어떤 파일을 출력할 지를 일일이 결정해 주어야 한다.

 

HTTP 메소드(GET/POST/PUT/DELETE)의 종류에 따라 라우팅이 가능하다.

 

예를 들어 다음 코드는 GET 명령으로 "/" URI가 들어왔을 때, 어떤 함수를 실행할 지를 등록하는 코드이다.

app.get('/', function (req, res) {

    res.send('Hello World!');

});

 

 

Express 애플리케이션 생성기

예제를 풀거나 공부를 할 때는 Single File에서 코드를 작성하였다. 실제로 서비스를 개발하려면 파일을 역할별로 분리하여 관리하는 게 합리적일 것이다.

Express 앱 생성기를 이용하면 기본적인 폴더 구조를 알아서 만들어준다. 일종의 템플릿이라고 보면 된다.

 

폴더는 public(정적 파일), routes, views, bin 으로 나눠진다.

 

간단히  express --view=pug myapp  명령을 입력하면 myapp 디렉토리가 생성되며 Express App이 생성된다. (템플릿 엔진으로 pug를 설정)

해당 디렉토리로 이동 후  npm install  로 기본적인 패키지를 설치한다. 이후  DEBUG=myapp:* npm start  명령어로 프로젝트를 실행해 볼 수 있다.

 

자세한 내용은 다음 링크 참고: https://expressjs.com/ko/starter/generator.html

 

 

템플릿 엔진(Template Engine)

각종 설명들을 요약하면 HTML 부분과 DATA 부분을 분리하기 위해 사용한다고 한다.

결과를 어떻게 표시할지를 Template으로 만들어두고, 여기에 Data만 집어넣으면 HTML 결과가 나오는 것이다.

 

사람의 입장에서 보면, 웹 퍼블리셔와 백엔드 개발자의 역할을 분리할 수 있다.

웹 퍼블리셔는 HTML/CSS로만 디자인을 하고, 데이터가 들어갈 곳에는 <% 변수명 %> 등의 문법으로 자리만 잡아주면 된다.

백엔드 개발자는 Database에 쿼리를 쏴 데이터를 추출하고, 변수명만 잘 맞춰서 Template에 입력하기만 하면 된다.

 

브라우저 입장에서 보면, 쓸데없는 데이터 재전송을 막을 수 있다.

사이트에서 일부 내용만 바뀌는 경우(예: 페이스북) 기존 방식대로라면 페이지 전체를 다시 렌더링 해야 한다.

템플릿을 적용하면 데이터가 변한 그 부분만 내용을 수정할 수 있어 렌더링 시간과 네트워크 대역폭을 절약할 수 있다.

 

자세한 내용은 다음 소스들 참고:

https://gmlwjd9405.github.io/2018/12/21/template-engine.html (템플릿 엔진(Template Engine)이란)

https://show-me-the-money.tistory.com/56 (템플릿 엔진이란 무엇인가?)

https://nesoy.github.io/articles/2017-03/web-template (웹 템플릿 엔진(Template Engine) 이란?)

 

 


MongoDB

MySQL은 Relational Database이다. 데이터의 규격 및 관계가 명확하게 정의된 채 사용되는 데이터베이스이다.

이러한 관계형 DB는 SQL(Structured Query Language) 이라는 언어를 사용하며, ACID(Atomic, Consistency, Integrity, Duarabity) 특성을 지원한다.

ㅋ... 쉽게 말하면 데이터의 신뢰가 보장되지만 구조의 유연성이 떨어진다고 요약할 수 있다. 정확한 데이터 처리를 요하는 은행, ERP 등에는 필수이다.

 

시대가 흐르면서 Non-Relational Database가 등장한다. 비정형 데이터를 쉽게 담아서 쓰는 NoSQL 이라고 부른다.

차이점은 관계형 DB보다 융통성 있고 유연한 데이터 모델을 사용하며, 데이터의 저장 및 탐색에 특화된 알고리즘을 사용한다.

ㅋ... 이것도 요약하면 관계성을 포기한 대신 단순 저장&탐색에는 아주 빠른 성능을 보인다는 것이다. 대용량 데이터(페북, 트위터 등)를 다룰 때 좋다. 

 

MongoDB는 NoSQL에 해당한다.

NoSQL에는 여러 종류의 데이터 모델(Model)이 존재하는데, Key Value DB, Big Table DB, Document DB, Graph DB 등이 있다.

이 중 MongoDB는 DocumentDB에 해당한다. DocumentDB는 데이터가 JSON, XML 등의 Document로 모델링 되어있는 형태이다.

 

다양한 NoSQL별 특징 및 성능 비교 등등 정보는 다음 링크 참고:

https://www.samsungsds.com/global/ko/support/insights/1195843_2284.html (NoSQL이란 무엇인가? 대량데이터 동시처리위한 DBMS 종류와 특징)

https://www.mongodb.com/compare/mongodb-mysql (MongoDB and MySQL Compared)

 

 

입문(Tutorial)

공식 튜토리얼(영문): https://mongoosejs.com/docs/index.html

한국어 강좌 링크: https://velopert.com/mongodb-tutorial-list

 

우선 용어가 조금 다른데, MySQL의 테이블, 로우, 컬럼을 MongoDB에서는 컬렉션, 도큐먼트, 필드 라고 부른다.

더 자세한 개념은 위 링크들 참고.

 

 

간단한 커맨드들

설치 후  mongo  명령어로 Command Line에 접속할 수 있다.

 

Database를 생성하려면  use 디비명  을 이용하면 된다. 왜 커맨드가 use인가?

이미 DB가 존재한다면 있던 걸 사용하고, 없으면 새로 생성한다. 단, Document를 하나 이상 생성해야 DB도 생성된다.

 

Database 목록은  show dbs  를 통해 확인하며, 사용중인 디스크 용량까지 볼 수 있다.

 

 

관리 도구: Compass

공식 사이트에서 MongoDB를 관리할 수 있는 GUI 프로그램을 제공하고 있다. (MySQL의 Workbench 같은 느낌)

 

다운로드 링크: https://www.mongodb.com/products/compass

 

 

Mongoose

Node.js의 모듈이다. 역시  npm install mongoose --save  로 설치할 수 있다.

Mongoose는 MongoDB에서 부족한 부분들을 다양한 자체 기능으로 메꾸고 있다.

 

스키마(Schema) 기능을 제공한다. NoSQL의 장점이자 단점은 Document에 넣는대로 들어간다는 것이다.

즉, 필드가 정해져 있지 않으므로 실수로 필드명을 오타 내거나, 혹은 다른 자료형으로 데이터를 삽입하는 실수가 일어날 수 있다.

그래서 데이터를 넣기 전에 한 번 필드명과 자료형을 검사해 주도록 하는 기능이 제공된다.

(MySQL에서 Table을 만들 때 column을 미리 정하듯이, Schema를 통해 Document의 field를 미리 정하게 된다.)

 

Populate라는 기능도 있다. 쉽게 말하면 MySQL의 JOIN을 구현한 것이다.

만약 어떤 Document가 다른 Document의 ID를 갖고 있을 때, MySQL에서는 JOIN을 통해 두 Document의 내용을 한 번에 가져올 수 있다.

MongoDB에서는 두 번의 Read를 거쳐야 한다. 첫 번째 Document를 먼저 가져오고, 두 번째 Document를 ID로 조회해서 또 가져와야 한다.

이를 Mongoose가 알아서 해 주는 것이다. 다만 MySQL의 JOIN과는 달리 그냥 대신 해 주는 것에 불과하므로, 남발하면 성능 하락이 있다.

 

이외에도 PromiseQuery Builder 등의 좋은 기능들도 있다.

 

자세한 내용은 다음 링크 참고:

https://www.zerocho.com/category/MongoDB/post/5963b908cebb5e001834680e (Mongoose(몽구스) 시작하기)

https://velopert.com/594

 (Express와 Mongoose를 통해 MongoDB와 연동하여 RESTful API 만들기)

 

 

간단한 예제 1 (DB에 데이터 넣어보기)

위에서 언급한 공식 사이트(영문)에서 다양한 개념을 잘 설명해 주고 있으니 건너 뛰었다면 한 번 정도는 보기를 권한다.

특히 Mongoose 기능 전반에 대해 한 페이지로 요약 설명한 Getting Started 의 내용이 좋다. (https://mongoosejs.com/docs/index.html)

 

먼저 다양한 세부 개념에 대한 설명은 다음 링크 참조:

https://www.zerocho.com/category/MongoDB/post/59a1870210b942001853e250 (Mongoose 스키마(Schema))

https://dalkomit.tistory.com/112 (Mongoose 모델의 강점)

 

Schema는 Document의 필드들을 정의한다. MySQL에서 Table의 Column을 정하는 것이라고 보면 된다. 그런데 Table을 만든 것은 아니다!

그저 Rule을 만든 것이다. 아직까지는 Collection(Table)과는 아무런 관련이 없이, 규칙에 불과하다.

 

방금 만든 Schema를 Model이라는 것으로 컴파일 해야 비로소 Collection의 역할을 하는 것이다. (처음엔 이해가 안될 수도 있음)

 

예제는 심플함을 위해 싱글 파일로 작성하고 실행하였다. 뭔 말이냐면 Express Generator를 쓰지 않았다는 소리다.

npm start니 app.js니... 겨우 예제 푸는데 귀찮게 그러지 않고  node example.js  이런 식으로 싱글 파일로 실행했다.

(아 물론 npm install로 mongoose나 express 등 모듈들은 설치해 줘야 한다.)

 

다음 코드의 예제를 보도록 하자. (공식 사이트의 Getting Started를 다듬었다.)

 

/* Mongoose 객체를 생성함 */ var mongoose = require('mongoose'); /* 서버에 접속 시도 */ // connect() 두 번째 인자로 옵션 준 거는 그냥 주라고 해서 준 거임. 생략해도 무방 mongoose.connect('mongodb://localhost/portfolio', { useNewUrlParser: true }); /* 객체에서 커넥션 정보 가져오기 */ // 접속 실패 시 에러 메시지 띄우기 var db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function () { // Connected! }); /* 스키마(Schema) 생성 */ var personSchema = new mongoose.Schema({ name: String, age: Number, }); /* 모델(Model) 생성 */ // Collection을 만들었다고 보면 됨 var Person = mongoose.model('Person', personSchema); /* 모델을 이용하여 Document 생성 */ var acu = new Person({ name: 'Acu', age: 26 }); // Document를 객체로써 취급한다. (ODM 개념) console.log(acu.name); // 'Acu' console.log(acu.age); // 26 /* 모델을 실제 DB에 반영 */ acu.save();

 

느낌은 마치 C++에서 클래스를 만들고 객체로 이것저것 데이터 처리를 하는 느낌이다.

 

실제로 MySQL의 데이터를 객체 쓰듯이 쓸 수 있도록 한 ORM(Object Relational Mapper)이 존재한다.

얘도 마찬가지로 똑같은데, 이름은 살짝 다르게 ORM(Object Document Mapper)이다. 즉 객체처럼 쓰는 게 맞다는 것ㅎㅎ

 

코드를 정리하자면, Schema를 만들어서 규칙을 생성하고, 이 규칙을 토대로 Model을 만들어 Collection을 담당하게 한다.

그리고 Model을 기반으로 Document를 만들어 데이터를 넣는다. 마지막으로 실제 DB에 save()로 적용하면 된다.

 

리눅스 MongoDB에서 다음 커맨드로 위 코드의 결과를 알 수 있다.

 

> show dbs

admin      0.000GB

config     0.000GB

local      0.000GB

portfolio  0.000GB

 

> use portfolio

switched to db portfolio

 

> show collections

people

 

> db.people.find({})

{ "_id" : ObjectId("5c7fd76db6c337600a1b7c14"), "name" : "Acu", "age" : 26, "__v" : 0 }

 

Model로 컴파일하면서 Collection의 이름을 지정하게 된다. 예를 들어 var Person = mongoose.model('모델 이름', 스키마);

여기서 모델 이름이 자동으로 Collection의 이름이 되는데, 특이한 점은 자동으로 영어 복수형으로 변환된다.

 

뭔 소리냐? 예를 들어 mongoose.model('Car', carSchema); 를 실행하면 실제 MongoDB에는 Cars 컬렉션이 생성된다.

Person을 입력하면 People이 된다(...).

 

이렇게 Document와 Collection의 이름이 연동되는게 싫으면 별도로 옵션을 주면 된다는데 그것까진 굳이 안찾아봤음.

 

 

간단한 예제 2 (RESTful API 만들기)

이 예제도 역시나 싱글 파일로 작성했다. 하지만 나중에는 Router 파일을 분리하는 등 코드를 체계적으로 관리해야 할 것이다.

 

참고 사이트: https://velopert.com/594 (Express와 Mongoose를 통해 MongoDB와 연동하여 RESTful API 만들기)

 

[사전지식: URL을 통한 정보 전달]

일반적으로 http://google.com/?first=512&second=12512 이런 식으로 URL에 데이터를 전달한다. 이런 걸 Query String 이라고 한다.

정보는 key=value 형식으로 되어 있고, 각 데이터는 &로 구분된다.

 

다른 방식으로도 전달이 가능한데, http://blog.com/view/12345/mobile 이런 식이다. 이런 걸 Semantic URL 이라고 한다.

정보는 value의 나열로 되어 있고, 각 데이터는 /로 구분된다. 그럼 key는??? 프로그래머가 결정한다.

 

라우팅 시 app.get('/view/:id/:mode') 형태로 받으면 id=12345, mode='mobile' 을 의미하게 된다.

 

내용 출처: https://wayhome25.github.io/nodejs/2017/02/18/nodejs-11-express-query-string/ (Express-URL을 이용한 정보의 전달)

 

[사전지식: JSON Parsing]

REST API는 Header로 GET/POST/PUT/DELETE 중 1개가 오고, Body에는 JSON 데이터가 온다. (안올수도 있지만;;)

이 말은, Body를 파싱할 필요가 있다는 뜻이다. JSON Parser를 이용해서 데이터를 객체(Object)로 만드는 과정이 필요하다.

 

예를 들어 { name: "Acu", age: 50 } 이라는 JSON 데이터가 Body로 도착한 경우, JSON Parser를 통해 파싱을 거치면

body.acu 라던지 body.age 처럼 파싱된 데이터를 body라는 객체에 담아 편리하게 쓸 수 있다.

 

[과거와 달라진 점]

원래는 JSON Body를 파싱하기 위해 body-parser 모듈을 사용하는게 정석이었으나,

Express v4.16.0 부터 Body Parser를 내장했기 때문에 body-parser 모듈이 더 이상 필요치 않다고 한다.

 

var bodyParser = require('body-parser')

app.use(bodyParser().json())

app.post('/', function(req, res) => {

    console.log(req.body)

})

 

이렇게 썼던 기존 코드를

 

app.use(express.json())

app.post('/', function(req, res) => {

  console.log(req.body)

})

 

이렇게 내장 Parser를 이용하여 파싱된 req.body를 얻을 수 있다. (Parser를 쓰지 않으면 req.body는 Undefined 임)

 

코드가 좀 길어서 부분별로 나눴다. 전부 한 파일로 합치면 RESTful API 서버가 된다.

 

더보기

/* 패키지 로드 */ var express = require('express'); var mongoose = require('mongoose'); /* Express 객체 생성 */ var app = express(); app.use(express.json()); // JSON Body Parsing

 

더보기

/* Mongoose 접속 */ var db = mongoose.connection; db.on('error', console.error); // 에러 났을 때 뭐할지 등록 db.once('open', function () { // 접속 시 뭐할지 등록 console.log('Connected to mongod server.'); }); mongoose.connect('mongodb://localhost/portfolio', { // 서버 접속 useNewUrlParser: true }); /* Mongoose Scheme & Model 생성 */ var personSchema = new mongoose.Schema({ name: String, age: Number, }); var Person = mongoose.model('Person', personSchema);

 

더보기

/* 라우팅(Routing) */ app.post('/', function (req, res) { // Body에 {"name":"Hong", "Age":30} 과 같은 JSON이 온다. // 새로운 document 생성 var person = new Person(); person.name = req.body.name; // JSON Parser가 req.body에 담아주었다. person.age = req.body.age; // 실제로 db 서버에 반영 person.save(function (err) { if (err) { console.error(err); res.json({ result: 0 }); // 성공 혹은 실패를 0,1로 반환 return; } res.json({ result: 1 }); // 성공 혹은 실패를 0,1로 반환 }); });

 

더보기

app.get('/', function (req, res) { // 별도의 Data를 받지 않는다. DB의 모든 데이터를 출력 // find()에 아무런 쿼리가 없으면 모든 document를 가져온다 Person.find(function (err, data) { if (err) return res.status(500).send({ error: 'fail.' }); res.json(data); }) }); app.get('/:name', function (req, res) { // Semantic URL 방식으로 name을 받았다. DB에서 name을 찾아 정보를 출력 // 1번째 인자는 쿼리, 2번째 인자는 Projection(어떤 필드를 보이고 숨길 지) Person.find({ // (1)쿼리 name: req.params.name }, { // (2)프로젝션 _id: 0, name: 1, age: 1 }, function (err, data) { // (3)콜백 함수 if (err) return res.status(500).send({ error: 'fail.' }); res.json(data); }); });

 

더보기

app.put('/:name/:new_age', function (req, res) { // 먼저 name에 해당하는 document를 찾는다 Person.findOne({ name: req.params.name }, function (err, data) { if (err) return res.status(500).send({ error: 'fail.' }); if (!data) return res.status(404).json({ error: 'data not found' }); // document를 새 데이터로 Update data.age = req.params.new_age; // document 저장 data.save(function (err) { if (err) res.status(500).json({ error: 'failed to update' }); res.json({ message: 'data updated' }); }); }); // 만약 Person.find()로 찾으면 data는 Array로 반환되기 때문에 data.save()가 불가능하다. (1개 고르면 되긴 함) // Person.findOne()을 쓰면 data는 1개의 Document기 때문에 data.save()가 가능하다. // find() 후 save() 하는 방법 이외에도 // Person.update()나 Person.findOneAndUpdate() 등 업데이트 방법은 다양하게 있다. });

 

더보기

app.delete('/:name', function (req, res) { Person.remove({ name: req.params.name }, function (err, output) { if (err) return res.status(500).json({ error: "fail" }); res.status(204).end(); }) });

 

더보기
/* Port 3000에 웹서버 시작 */ app.listen(3000, function () { console.log('Listening on port 3000!'); 

});

 

코드를 보면 알겠지만, 데이터를 전달하는 방법으로 두 가지가 있는데

  1. Body에 JSON으로 전달하는 방법

  2. URL에 Param으로 전달하는 방법 (Semantic URL)

이 있다. 정답은 딱히 없겠지만, 데이터가 많으면 1번으로, 그렇지 않고 대표성이 있다면 2번을 취하는 게 좋은 것 같다.

 

마침 비슷한 고민을 한 사람이 있다: https://blog.outsider.ne.kr/1116 (Stackoverflow, Twitter, Github, Medium의 URL 패턴 간단 정리)

 


실제로 뭔가를 만들기 위해 필요한 개념들

Token 기반 REST API

기존에는 서버 구현 시 사용자의 로그인 정보를 서버가 가지고 있었다. 이를 세션(Session)이라고 한다.

세션 방식은 서버가 계속 사용자의 정보를 들고 있어야 하므로, 서버를 확장하기 어려운 등 유연성이 제한되는 문제가 있다.

 

비슷하게 브라우저에서도 쿠키(Cookie)를 가지고 자신의 상태를 보관하거나 서버에게서 받은 정보를 담아두었다.

쿠키의 단점은 단일 도메인 혹은 서브 도메인까지만 작동하므로, 도메인이 여러 개인 서비스를 운영 시 쿠키 관리가 어렵다는 점이다.

 

정리하자면 세션(Session)이나 쿠키(Cookie) 모두 '현재 상태', 즉 State를 보관하는 용도로 대부분 사용되기 때문에 단점과 한계점이 많다.

그런데 Token 기반으로 API를 짜면 Stateless로 동작한다. 사용자가 로그인 되어 있는지 등의 상태 관련 정보를 서버가 몰라도 된다는 것이다.

 

더 자세한 개념들은 다음 링크 참고:

https://velopert.com/2350 ([JWT] 토큰(Token) 기반 인증에 대한 소개)

 

 

음... 간단하게 토큰(Token)을 찜질방 비유로 생각해 보았다.

 

[로그인]: 찜질방에 입장할 때 내 이름을 적고, 옷장키(Token)를 받는다. 옷장키는 찜질방 내 결제에 사용할 것이다.

  [Token 생성]: 옷장키(Token) 안에는 (1)내 이름(2)찜질방 주인의 서명이 들어있다. 서명으로 인해 찜질방 주인만 키 생성 가능.

 

[API 이용]: 찜질방에서 계란을 사려고 한다. 카운터(Server)(1)계란을 사겠다는 주문(API)(2)옷장키(Token)를 전달한다.

  [Token 검증]: 찜질방 주인은 옷장키(Token)에 적힌 (1)사람 이름(2)서명을 본다. 서명이 올바르면 키에 적힌 이름으로 구입 내역을 기록한다.

 

[Token 위조]: 누군가 내 이름으로 라면을 사먹으려 시도한다. 빈 옷장키(Token)에 내 이름은 적었으나, 찜질방 주인의 서명을 할 수 없어 실패한다.

[Token 탈취]: 내가 실수로 옷장키(Token)를 두고 자리를 비웠다. 누군가 그 키를 주워 식혜를 사먹었다. 옷장키(Token)는 정상이므로 막을 수 없다.

 

웹으로 다시 말하면, 유저는 로그인 시 서버로부터 Token을 발급받는다. 그리고 이후부터는 API 사용 시마다 Token을 같이 전달하면 된다.

서버는 유저의 로그인 사실을 기억할 필요 없이 API 호출 시마다 Token의 유효성(Valid)만 확인하면 된다. 유저 또한 쿠키 등을 보관할 필요가 없다.

 

문제는, Token 구현에 기본적으로 암호화가 없기 때문에 Token을 탈취 당하면 공격자가 사칭을 할 수 있다.

따라서 Token을 별도로 암호화 하는 기술을 사용하거나, Token의 유효 시간(Expire Time)을 짧게 하여 탈취당한 Token이 빨리 만료되도록 한다.

 

 

JSON Web Token (JWT)

그럼 실제로 Token을 어떻게 구현하고 이를 REST API에 적용할 수 있는가? 웹표준으로 JWT라는 것이 존재한다.

 

다음 사이트에서 Token의 개념을 익힐 수 있다:

https://jwt.io (JSON Web Tokens)

https://velopert.com/2389 ([JWT] JSON Web Token 소개 및 구조)

https://hycszero.tistory.com/108 (JWT(JSON Web Token)로 로그인 / 로그아웃 구현하면서...)

https://victorydntmd.tistory.com/115 ([HTTP/인증] JWT ( JSON Web Token ))

 

JWT는 REST API를 호출할 때 Token을 Header에 낑겨 보내는 방식을 취한다. (별도의 JSON으로 Token을 주는 것이 아니다.)

 

HTTP Header에 실으려면 JSON 형태보다는 단일 String 형태가 적합하다.

따라서 Token의 내용은 비록 JSON으로 작성되지만 최종 단계에서 이를 Base64로 인코딩하여 1개의 String으로 만든다.

 

JWT는 3부분으로 이뤄진다:

Header: 이 Token의 정보를 담고 있다. Token의 해싱 알고리즘이 무엇인지 기록한다.

Payload: 실제 Token의 데이터이다. 사용자 정보, 토큰 생성 시간, 만료 시간 등을 기록한다.

Signature: 서버의 비밀키를 이용해 Header,Payload에 대해 해싱함으로 서명을 만든다. 따라서 비밀키가 없는 외부 공격자는 서명 생성이 불가능하다.

 

----------

뭐 해싱하고 검증하고 이런 작업들이 복잡한데, 우리가 직접 할 필요는 없다. jsonwebtoken 모듈을 사용해서 실습해 볼 것이다.
아래 코드는 Token을 생성하는 코드이다. URL에 원하는 아이디를 입력하면 토큰을 생성한다. (예: http://localhost/create/my_id)

 

var express = require('express'); var jwt = require('jsonwebtoken'); var app = express(); const secretKey = 'aw35gvaw5aw35h112'; app.get('/create/:userid', function (req, res) { var payload = { id: req.params.userid }; var token = jwt.sign(payload, secretKey); // Token 생성 res.send(token); // Token 출력 });  

app.listen(3000, function () {});

 

핵심은 jwt.sign() 함수이다. 원래 비밀키는 길이가 길 수록 좋고 512비트 이상을 권장하지면 여기서는 편하게 아무거나 쳤다.

 

Token의 Payload는 id라는 항목만 집어넣었다. 보이진 않지만 iat(Issued At)라는 이름의 '토큰 생성 시간' 항목이 자동으로 들어가게 된다.

따라서 위의 웹 페이지를 들어가 계속 새로고침하면 Token 내용이 시간에 따라 변하는 것을 알 수 있다.

 

https://jwt.io/ 사이트에 들어가서 왼쪽 Encoded 칸에 우리가 만든 Token을 넣고, 오른쪽에는 Secret Key를 입력하면 즉석 Verification을 해볼 수 있다.

 

----------

이번에는 Token 검증을 해보려 한다. 위의 코드에서 라우팅 하나만 추가하도록 하자. (예: http://localhost/verify/eyJhbGciOiJIUzI1NiIsInR5........)

 

app.get('/verify/:token', function (req, res) { var token = jwt.verify(req.params.token, secretKey); // Token 검증 if (token) res.send('valid!'); else res.send('invalid!'); 

});

 

핵심은 jwt.verify() 함수이다. 검증 결과를 boolean으로 출력한다.

 

----------

만약 여기서 Token에 Expire Time을 10초로 하고 싶다면 Token 생성 부분에 다음과 같이 코드를 바꾸면 된다.

jwt.sign()의 3번째 인자로 Option을 새로 준 것이다.

 

var options = { expiresIn: '10s' }; 

var token = jwt.sign(payload, secretKey, options); // Token 생성

 

위 코드로 생성된 Token은 10초 뒤 Verify를 해 보면 Expired 라고 뜰 것이다.

 

 

더 자세한 실습 등은 다음 링크 참고:

https://victorydntmd.tistory.com/116 ([Node.js] JWT 기반으로 사용자 인증 구현하기 ( jsonwebtoken 모듈 ))

 

 

로그인/회원가입 기능 구현

일단 Front-end는 고려하지 않고, Back-end에서 REST API로 만들 것이다.

 

Node에서도 세션과 쿠키를 이용한 전통적 로그인을 구현할 수 있다. cookie-parser, express-session 모듈을 사용하면 된다.

하지만 여기선 Web Token 기반 로그인을 구현할 것이다. 로그인 시 Token을 발행해 주고, 이후 API 사용 시마다 해당 Token을 받는 형태이다.

 

Token은 탈취당할 수 있기 때문에 Expire Time을 짧게 하고, 대신 Refresh가 가능하도록 할 것이다.

(참고로 `자동 로그인` 기능은 Token의 Expire Time을 없애는 것으로 구현할 수 있다.)

 

만든 API를 이쁘게 Document로 적고 싶으면, 다음 레퍼런스들 참고:

https://bocoup.com/blog/documenting-your-api (REST API DOCUMENTATION BEST PRACTICES)

http://apidocjs.com (Inline Documentation for RESTful web APIs)

https://trello.com/c/lBnwdKRj/22-rest-api-문서화로-어떻게-남길까 (REST API 문서화로 어떻게 남길까)

https://sanghaklee.tistory.com/50 ([Swagger] RESTful API 문서 만들기)

 

다음과 같은 API를 만들 것이다. 간단한 몇 개만 구현한다.

 

Title: 회원 가입

URL: http://localhost:3000/users

Method: POST

Data params:

{

    "u" : {

        "username": [string],

        "password": [string],

        "name": [string],

        "email": [string]

    }

}

Success Response: Code: 200, Content: { result: 1 }

Error Response: Code 422, Content: { msg: [string] }

 

 

Title: 로그인 (Token 받기)

URL: http://localhost:3000/users

Method: GET

 

 

이제 슬슬 번듯한 사이트를 만들 것이므로 Express Generator로 프로젝트를 만들고 구조를 따라가려 한다.

마침 기본으로 users라는 이름의 라우터가 들어 있으므로, routes/users.js에 우리의 API를 구현하면 된다. app.js에 Mongoose 모듈 로드만 해 주자.

 

 

 

 

 

 

 

 

 

 

하 귀찮네

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(공부하면서 계속 내용 추가 중)

(Last Update: 2019/03/11 월요일) 

 

 

 

아아~~ IT기업 상반기에 뽑는데가 없어서 때려침ㅎㅎ 삼성가야지~

심심하면 글 계속 씀

NC도 1차 면접에서 떨어졌다. 너무 어려웠다.




2:1 면접이었고, 내 지원 직무는 게임개발이다.

서버/클라이언트 구분이 없어서, 나는 양 쪽 다 해본 경험을 어필하고 포트폴리오를 구성했다.


전반적으로 질문이 문제해결능력창의성을 보려고 했던 것 같다.

문제를 몇 개 예로 들면

- 3D 맵디자이너가 맵을 만들어왔는데, 떨어지면 가파라서 다시 올라올 수 없는 지형을 프로그래머 입장에서 어떻게 찾아낼 것인가? (구체적으로)

- 동접자가 100만명이 들어왔을 때, 접속중인 유저의 정보를 담은 클래스를 어떤 자료구조로 관리할 것인가? (DB 문제가 아님)

- 이외에도 문제 다수


다 현업에서 부딪히고 있는 듯한 시나리오였고, 학부생으로서는 당연히 접해보지 못한 문제이기에 그냥 내 생각대로 말했다.

그리고 해시함수나 트리를 이용한 서버 운영 시나리오도 몇 개 질문받았는데, 그 내용은 복잡해서 생략하겠다. 굉장히 어려웠다.


학부 기초 내용에 관한 질문도 있긴 있었는데, 서버와 클라이언트를 구성할 때 TCP/UDP 중 무엇을 쓸 것이고 그 이유를 물어봤다.

그리고 TCP의 특징 3가지와 같은 아느냐/모르느냐의 문제여서 전부 답했다.




결국 떨어졌는데, 여기는 그냥 실력부족으로 떨어졌다. 너무 어려웠다.

공부를 더 해야겠다는 생각이 든다. 포트폴리오도 부실했고.

1차 면접에 떨어진 후기를 써본다. 떨어졌으므로 간단하게 써야지




3:1 면접이고 분위기는 아주 편했다. 좁은 테이블에 4명이 바싹 붙어서 팀플하는 느낌.

들어가면 1분 자기소개 하고, 풀었던 코딩테스트 문제에서 뭐가 인상깊거나 아쉬웠는지 간단히 물어본다.


면접은 100% 자기소개서만 가지고 하는 것 같다. 그러니까 서류 쓸 때 면접까지 생각해서 신경썼어야 했다.

자기소개서에 쓰여진 경험, 팀플, 프로젝트에 대해서 아주 자세하게 물어본다.

어떤 역할을 맡았고, 무슨 기술을 썼으며, 어떤 어려움이 있었고 어떻게 해결했는지


그리고 꼬리물기 질문이 어렵게 들어온다.

예를 들면 내가 홈페이지를 만들었었다 -> 로그인 보안은? -> SSL을 썼다 -> SSL도 뚫리는 거 알고 있느냐? -> 모른다 첨듣는다... -> ㅇㅇ수고

이런 식으로 내가 모르는 내용이 나올 때까지 긁고, 면접관이 실망하는 것이 반복되었다.

근데 억울한게... 나도 꽤나 많이 알고있다고 생각했는데 진짜 듣도보도 못한 걸 갖고와서 물어본다ㅜ...


그래놓고 내가 시무룩하면 아, 어차피 학부생들한테 별로 기대하는 거 없어요 라고 말을 해 준다. (위로인가?)




일단 면접 떨어진 이유는 확실히 짐작이 간다. 내가 서류 쓸 때 생각 없이 썼다.

포트폴리오에 Server 관련 내용을 잔뜩 써놓고, 정작 지원 분야는 Client로 썼다. 그래서 직무가 맞질 않았다. (면접관이 직접 묻기도 했다)


말실수로 대답 잘못한 것도 몇 개 기억나는데, 지금 생각해보면 면접관이 전부 기회를 줬던 것이고 내가 잡지 못했다.

이번 하반기 첫 면접이어서 멋도 모르고 멍청하게 망쳤다. 담엔 잘하지 뭐...

도대체가 NC TEST 라는것은 구글에 쳐도 정보가 몇 개 없다. 내가 정보를 하나 남긴다.


NC는 수년째 NC TEST를 개포고등학교에서 보고 있는 듯하다. 구룡역이 더럽게 깊으므로 빠져나오는데 다소 오래 걸린다. 엘베 이용 바람.


내 지원 분야는 게임개발이다.

오전 인적성 / 점심 식사 / 오후 직무적성검사 시험을 보았다. 시험 응시인원은 통합 1,000명 정도 되어보였다.






점심엔 도시락을 주는데 이 메뉴도 수년 째 동일한 것 같다. 제조사는 Welstory. (학식으로 지겹게 먹은...)

메뉴 구성은 도시락에선 호화로운 편이다.







이제 시험 얘기를 해본다.



인적성아모레퍼시픽과 유형이 동일하다. 같은 연구소에서 만들었다고 한다.

NC TEST 문제집이 없으니 아모레 문제집을 사서 공부해야 한다. 그리고 NC는 한국사를 제외했으므로 암기 부담이 적다.


나는 다음 책을 보았다: 2018 하반기 합격이 답이다 아모레퍼시픽그룹 인적성검사 - 종합편

졸업논문때문에 바빠서 전날에 책을 처음 폈다. 그것도 각 유형별 연습문제만 풀고 모의고사는 안풀었다.

총 공부시간은 3시간 정도? 많이 푼다고 실력이 늘어나지 않을 거란 생각이 들어서 많은 시간을 들이지 않았다.

이럴거면 책값이 좀 아깝다... 그래도 유형 미리 알아간다는 것 정도는 도움되었다.


인적성은 딱히 팁을 줄 게 없다. 그냥 잠 푹 자고 맑은 정신으로 가는 게 좋을 것 같다.

그리고 볼펜/샤프 등을 쓸 수 없게 했다. 컴퓨터용 싸인펜만 사용이 허가되었다.




오후에는 직무적성검사를 보았다. 이게 참 정보도 없고 해서 뭘 공부해 갈 지 몰라 이것저것 해갔는데, 결국 별 도움 안됐다.

다음 시험 보는 사람은 이 글을 보고 감이라도 잡고 시험장에 들어가길 바란다...


문제가 총 50문제였던 것 같은데 앞에 10문제 정도가 NC 회사 관련된 문제다.

예를 들면 NC의 기업문화(홈페이지에 있는), NC가 만든 게임과 각 게임의 설명, 상 받은 게임, 해외 진출 현황 등등... 싹 다 공부해 가야 한다.

NC 다이노스 문제도 하나 나온다. 창단일이나 연고지, 최근 성적 등등 죄다 두루두루 알아가면 될 듯.


20문제는 컴퓨터공학에 대한 내용이다. 아주 깊게는 안 물어본다. 그냥 IT기업들 필기시험 대비 하듯이 다 공부해 가면 된다.


20문제는 C++로 된 코드를 주고 문제를 푸는 유형이 출제되었다. C++의 언어 자체를 아주 자세하게 물어보는 문제가 많이 나왔다.

예를 들면 클래스 구조, 클래스의 크기 측정(sizeof), 생성자와 파괴자의 호출 순서, 어느 시점에 객체가 자동으로 사라지는지, cout << 함수1() << 함수2() 같은 형태에서 호출 순서 문제 등등... 

그니까 C++의 어려운 응용이 아니라, '기본기'를 '아주 자세하게' 물어보는 것이다.

C++를 오래 전에 마지막으로 공부한 사람은 반드시 공부를 하고 가야 할 것이다. 안그러면 손도 못댈 것... (내 얘긴가??)




NC TEST 결과 발표는 5일 뒤에 해 준다고 한다.


그리고 합격자에 한해 온라인 코딩테스트가 추가로 있다고 한다. 코딩테스트는 올해부터 추가되었다고 한다.

긍정적으로 생각하면 NC TEST 합격자 배수가 늘어날 것 같고... 나쁜 점은 절차가 하나 늘었다는 거...


어렵다 어려워.






(2018.11.08 추가) 직무적성 망쳤는데 합격!


(2018.11.15 추가) 코딩테스트는 그냥 면접 때 참고하려고 하는 것 같다. 못 본다고 면접 떨어지는 그런게 아닌 것 같다.

- 시간은 3문제에 1시간 30분. 난이도는 카카오/라인보다 훨씬 쉬웠다.

- 채점 기능 없이 코드 저장 제출 방식이라서, Test Case를 직접 만들고 반례를 찾아야 한다.

- 프로그래머스 사이트 이용해서 진행됨. 언어는 C/C++/Python

Matched Filter, 정합 필터

원본 신호를 알고 있을 때, 수신된 신호에서 어느 부분이 원본 신호 부분인지 찾아낼 때 사용


Matched Filter는 수신된 신호 중 원본 신호가 위치한 곳에서 최대가 된다.


목적은 두 신호의 유사도를 구하는 게 아니라 원본 신호 지점에서 SNR(Signal-to-Ratio)를 최대로 만드는 것인 듯


Matched Filter의 구현은 한 쪽 신호를 뒤집어서 두 개를 Convolution한다. 그러면 두 신호가 최대로 유사한 지점에서 최대값을 가지게 됨.

(링크에서 Matched Filter 파트의 그림을 참고할 것: http://blog.naver.com/PostView.nhn?blogId=moony6463&logNo=220072038153)


Cross Correlation과 같다고 볼 수 있는데 차이점은 CC가 0을 기준으로 좌우 대칭 모양이 나오는 반면에

Matched Filter는 입력 소스만큼의 길이만 나온다. 즉 CC가 두 배 더 긴 결과를 출력함.


매트랩으로 해 본 결과, Matched Filter와 Cross Correlation은 같은 결과를 내었다.

Cross Correlation은 Correlation이고, Matched Filter는 Reversed Signal을 Convolution 하는 것이라서

수식적으로 풀어보면 같은 효과라고 어디서 줏어들었음

라인 플러스 2차 시험에 응시한 후기를 남겨본다.

강남 멀티캠퍼스 건물에서 시험을 봤다. 삼성에서 운영하는 컴퓨터학원(?)이라는 듯 싶다.


코딩시험 120분 + 필기 90분 시험을 보았다.





먼저 코딩은 4문제가 나왔다. 배점은 [20,25,25,30] (의미 없지않나?) 난이도는 중간 정도.

대회 수준까지는 아니지만 연습 없이 가면 한 개도 못풀 것 같다.


문제가 대략 기억나는것이

- infix 수식 계산 문제 (stack을 이용한 문제. postfix로 변환해서 계산하면 쉽다)

- 주어진 문자열의 substring 중에서, 조건에 맞는 것 중 가장 긴 길이를 구하는 문제 (greedy 접근법을 써야 한다)

- 3번 개같은거 말로 설명안됨 -_-; 여기서 시간 다 뺏김

- 최단 경로 찾는 문제인데, 조건이 살짝 성가심 (DFS 쓰면 됐을 것 같은데 시간이 없어서 못풀었다)





필기는 서술형 5문제 + 빈칸 채우기 3문제였다.

서술형은 주어진 코드의 출력 쓰기 문제와 암호학, 컴퓨터네트워크, Garbage Collection, Thread&Mutex 관련으로 나왔다.

빈칸 채우기는 이미 대부분의 코드가 주어진 상태에서 빈 칸이 뚫려있고 그 곳에 내용을 채우는 식이었다.

문제 주제로는 Max-Heap과 Tree Inorder, 그리고 Compare-and-Swap (CAS) 관련 문제가 나왔다.





정말 다행히도 필기는 대략 공부했던 내용이라 일단 채워넣긴 했는데

코드가 일반적인 버전이 아니고 공부했던거랑 다르게 생겨서 이해를 좀 해야 했다.


근데 코딩을 2문제밖에 못푼 건 너무 아쉽다. 3문제면 무난하게 합격할 것 같은데 불안하네..





(2018.10.25 추가) 다행히 붙었다. 면접 ㄱㄱ

9.29(토)에 라인 1차 코딩테스트+필기테스트(온라인)를 봤다.

시험 시간은 코딩 2시간, 필기 1시간.




코딩테스트는 2시간 4문제로 주어졌다. (작년엔 5문제였을건데?) 문제 배점은 [10,30,30,30].

문제 난이도는 그다지 어렵지 않았다. 4문제 전부 Python 코드 30줄 이내로 풀 수 있는 수준?


상세 내용은 저작권이 있어서 대략 기억나는게...

- Stack을 이용해 방문 노드를 기록하고, 노드 재방문 시 프로그램 종료

- 주어진 쿼리 String을 파싱하고, 그에 따른 적절한 행동 하기


근데 위 2문제 풀고 서버가 다운돼서 코테가 취소됐다. 이런...




필기는 20문제가 주어졌는데, 1시간이 빠듯했다. 객관식, 단답형, O/X 세 유형으로 나왔다.


문제 유형별로 문제 스타일은

- 단답형: 주어진 코드의 결과가 무엇인가 / 주어진 코드에서 특정 함수는 몇 번 실행되는가 등등

- 객관식: 이 개념에 대해 맞는 설명을 모두 골라라 / 주어진 코드에서 빈 칸 뚫어놓고 여기에 적합한 코드를 골라라

- O/X: 주어진 설명이 맞는지 틀린지 골라라


출제 범위가 데이터베이스부터 네트워크와 컴퓨터구조까지 엄청 넓은데, 꽤나 깊이 있는 난이도로 물어봤다.

문제 하나하나가 수준이 높아서 바로 풀 수 있는 기본적인 내용은 하나도 없었던 것 같다. (결국 누가누가 구글 빨리 찾나)


결국 20문제 중 17문제밖에 못풀었는데 하필 코딩테스트가 취소돼서...

필기에서 걸러지지 않을까 매우 걱정이다. 3일 뒤에 결과 발표해준다는데, 부디 합격하길ㅎㅎ




(2018.10.05 추가) 붙었다~ 2차 시험을 공부해본다.

어쩌다보니 연구에 OFDM을 써야하게 되었다. 조사한 내용을 정리해본다.

내 사용 목적은 (1)소리로 데이터를 전송하는 것과, (2)녹음된 소리의 Time Sync를 정확하게 맞추기 위함이다.




OFDM (Orthogonal Frequency Division Multiplexing, 직교 주파수 분할 다중화)

직교하는 부반송파(sub-carrier)를 수십~수천 개씩 사용해서 병렬로 정보를 전송하는 기술

기존 기법들은 Single Carrier로써, 주파수 하나만 잡고(예: FM 라디오 등등)데이터를 전송했으나, 단점이 많이 있었음


(carrier와 channel 용어는 서로 섞여서 쓰인다)




장점

  • Multipath 환경에 강하다.
    • 주변 사물에 부딪혀서 여러 번 겹쳐 녹음될 경우, 신호 분리가 까다로운데 이를 해결할 수 있음.
    • Single Carrier 쓸 때는 데이터를 많이 전송하려면 Higher Rate를 썼어야 했고
      그러면 Multipath 환경에서 신호가 너무 쉽게 겹친다. (목욕탕에서 말을 빨리 하는 것과 같음)
    • OFDM은 여러 Carrier에서 데이터를 나눠서 Lower Rate로 전송한다.
      => 한 비트가 오래 유지되니까 알아듣기 쉽고, 앞 신호랑 조금 겹쳐도 그 부분 걍 잘라내면 된다.
  • 특정 채널의 상태가 좋지 않아도, 병렬 전송이라서 영향이 크지 않다.
    • SIngle Channel 였다면 신호가 아예 죽었을 것
  • 대역폭을 굉장히 효율적으로 사용한다
    • Sub-carrier를 겹치게 배치할 수 있으므로

(더 많은 장점은 위키를 참고: https://ko.wikipedia.org/wiki/직교_주파수_분할_다중_방식)




왜 Orthogonal인가?

병렬 전송에 대한 개념으로 FDM이 먼저 나왔으나 단점이 존재했다. 각 sub-carrier들이 겹치지 않아야 했으므로 Guard Band가 필요했다.

결과적으로 대역폭(Bandwidth)를 많이 차치하여 비효율적인 배치가 된다.


근데 어찌어찌 '직교성' 이라는것을 잘 이용하면 sub-carrier들을 겹치게 배치해도 잘 동작하게 된다. 아래처럼...

FDM과 비교했을 때 훨씬 많은 sub-carrier를 이용할 수 있음을 알 수 있다. Guard Band도 없다.




OFDM 채널 배치 구조

한 Carrier의 Amplitude가 최대일 때, 다른 Carrier들의 Amplitude는 0이 되도록 스펙트럼이 절묘하게 겹쳐져 있다. (직교성)

Theoretical BPSK OFDM spectrum



Guard Interval

위에서 Guard Band라는 용어가 쓰였는데, 채널 사이를 분리해 주는 역할이었다. (Frequency domain)
이번 용어는 Guard Interval로 전혀 다른 개념이며, 전송하는 데이터 사이를 분리해 준다. (Time domain)

예를 들어, 사우나에서 한 단어를 말하고 연달하서 다음 단어를 말하면 Multipath에 의해 첫 단어와 두 번째 단어가 겹쳐서 들린다.
그래서 첫 단어를 말하고 잠깐 쉬어주면(Guard Interval) 첫 단어의 Echo가 완전히 사그라 들게 되고, 이 때 두 번째 단어를 말한다.

즉 Guard Interval은 Echo들이 완전히(혹은 충분히) 사그라 들 때까지 기다리는 시간을 의미한다.

아래 그림은 Signal이 Multipath에 의해 3번 수신된 상황을 예시로 든 것이다. 마지막 Reflection까지를 Guard Interval로 설정했다.
물론 Guard Interval은 환경에 따라 매 번 개발자가 직접 지정해 줘야 하는 부분이다. 동굴 안에서는 충분히 길게 해야 할 것이고...




주로 쓰이는 OFDM 송수신부 구조


위 구조에서 Time Sync와 Cyclic Prefix 등은 빠져있음. 실제 구현하려면 위 구조보다 조금 더 복잡함.



Cyclic Prefix

Cyclic Extension (순환 확장)은 Multipath로 인해 발생하는 ISI(Inter Symbol Interference)를 극복하기 위해 고안된 방법이다.

목적은 Sub-carrier 사이의 직교성(Orthogonality)의 파괴를 방지하기 위함이다.

방식은 Cyclic Prefix의 경우 유효 신호의 마지막 부분 신호를 일정 부분 복사해서 앞에다가 삽입한다.


아래와 같은 데이터를 전송한다고 해보자. 3개의 Symbol이 있다.


이를 전송하면 Multipath로 인해서 Delay를 가진 신호들이 여러 번 들어오게 된다. 아래는 Original과 2개의 Multipath를 예로 들었다.

잘 보면 S2의 앞부분이 S1의 Echo와 겹치기 때문에 Inter Symbol Interference가 발생하여 S2를 추출해 낼 수 없게 된다.

따라서 결과를 보면 S2의 앞부분이 손실되고 뒷 부분만 얻어내게 된다.


그래서 Cyclic Prefix를 두게 된다. 어려운 개념은 아니고 Symbol의 뒷부분 일부를 앞에다가 복사하는 것이다.


그리고 신호를 전송해 보면... Multipath로 겹치는 부분이 발생하더라도 결국은 S2 안에서 일어나는 일이기 때문에

Symbol간의 Inter Symbol Interference는 발생하지 않는다는 것을 알 수 있다.




자주 쓰이는 용어 및 개념

  • FFT (Fast Fourier Transform)
    • 시간 도메인 신호를 주파수 도메인으로 바꿔주는 것
  • IFFT (Inverse Fast Fourier Transform)
    • FFT를 역으로 하는것
  • ISI(Inter Symbol Interference), ICI(Inter Channel Interference)
    • Multipath로 인해 신호가 겹쳐 들리는 현상
  • Modulation
    • 신호를 잘 전달하기 위해 적절히 데이터를 변조하는 것 (전자쪽 용어, 신호처리 분야)
    • QPSK, 16-QAM 등등 주로 사용됨
  • Cyclic Prefix
    • Guard Interval(최대 지연시간)동안의 신호를 복사해서 앞에 갖다붙이는 것
    • Guard Interval을 쓰는 이유? Multipath로 신호가 겹쳐봤자 최대 이 시간까지만 겹칠 것이라고 가정하는 것
    • 쉽게 말하면 뒷쪽 신호를 짤라서 앞에 갖다붙이는 것
    • Inter Symbol Interference를 극복하기 위함 - 신호가 어느정도 겹쳐도 걍 CP를 잘라버림 (어차피 뒤에 또 나올거니까)





[출처]

https://ko.wikipedia.org/wiki/직교_주파수_분할_다중_방식 (OFDM 기본 설명 및 장단점)

http://www.whydsp.org/209 (OFDM과 CP에 대한 이해가 쉬운 설명)

https://www.csie.ntu.edu.tw/~hsinmu/courses/_media/wn_11fall/ofdm_new.pdf (영문)(설명이 그림과 함께 아주 잘 되어있음)

https://dsp.stackexchange.com/questions/20132/ofdm-transmitter-bandwidth (영문)(그림 출처)

https://caesarhks.blog.me/70133244891 (OFDM에 대한 간단한 설명)

http://www.ni.com/white-paper/3370/ko (영문)(OFDM에 대한 간단한 설명)

https://www.radio-electronics.com/info/rf-technology-design/ofdm/ofdm-basics-tutorial.php (영문)(OFDM 설명)


http://www.ktword.co.kr/abbr_view.php?m_temp1=3164 (Cyclic Prefix 용어 설명)

http://www.telecomhall.com/what-is-cp-cyclic-prefix-in-lte.aspx (영문)(Cyclic Prefix 기본 개념 설명)


[좋은 코드 예시들]

http://blog.naver.com/PostView.nhn?blogId=ykryu7&logNo=221256145776 (MATLAB. 예시 코드와 설명)(실수, 허수 구조)

http://www.rfwireless-world.com/source-code/MATLAB/OFDM-matlab-code.html (영문)(MATLAB. 단순하며 직관적인 4-channel 코드 예시)

http://www.skydsp.com/publications/4thyrthesis/code.html (MATLAB. WAV 소리 파일로 생성하는 코드. 논문에 사용된 코드라 다소 복잡함)

http://dspillustrations.com/pages/posts/misc/python-ofdm-example.html (Python. 단계별로 아주 자세히 설명해 주는 코드 예시)

http://wisechoding.tistory.com/41 (MATLAB, 단계별 OFDM 시뮬레이션)

공식 설치 메뉴얼: https://www.tensorflow.org/install/install_windows


TensorFlow 설치 전에 먼저 깔아야 하는 것들(Requirements)

- CUDA Toolkit 8.0

- cuDNN v6.1


주의할 점은 위 requirement들을 최신 버전으로 깔면 TensorFlow가 지원을 안함 ㅡㅡ;;

현재 CUDA 9.1과 cuDNN 7이 최신이라 홈페이지 메인에 떡하니 있는데, 좋다고 넙죽 받아서 설치하면 당연 안되고

꽁꽁 숨어있는 Legacy 다운로드 메뉴로 들어가서 굳이 구버전을 깔아야 함


CUDA Toolkit은 크기가 좀 커서 그렇지(1.5GB) 그냥 깔면 되고

cuDNN은 받아서 압축 풀면 dll이 튀어나오는데, 적절히 원하는 폴더에 넣고 환경 변수에 해당 경로를 박아야 함

예를 들어 C:\Program Files\cudnn 폴더 만들고 그 안에 cudnn64_5.dll를 넣었으면 그 경로를 %PATH%에 박고 재부팅ㄱ





TensorFlow가 잘 깔렸는지 확인하는 파이썬 스크립트:

https://gist.github.com/mrry/ee5dbcfdd045fa48a27d56664411d41c#file-tensorflow_self_check-py

[Sampling]

이 세상의 소리(Sound)라는 것은 아날로그이기에 Linear 하다.

그런데 컴퓨터는 디지털이므로 이 Linear한 소리를 Discrete 한 데이터로 변환해야 한다.

 

아날로그는 거의 무한대의 해상도를 가지므로 이것을 그대로 디지털로 표현하는 건 불가능하다.

따라서 그 일부분만 채취(샘플링)하여 최대한 원본과 유사한 디지털 데이터를 만들어야 한다.

이러한 과정 또는 행위를 샘플링이라 한다.

 

용어가 몇 개 있다:

(1) 샘플링 레이트(Sampling Rate) : 1초에 몇 개의 샘플을 추출할 것인지

(2) Bit Depth : 한 개의 샘플이 얼마만큼의 정확도/단계를 가지는지

 

샘플링 레이트가 높을 수록 아날로그와 유사한 모양의 데이터를 얻을 수 있다.

아래 그림은 샘플링 레이트에 따른 디지털 데이터의 모양을 나타낸다.

잘게 쪼갤 수록 아날로그의 것과 같이 부드러운 곡선이 되는 것(=원본에 가까움)을 확인할 수 있다.

 

더불어서 각 샘플(sample)이 표현할 수 있는 값의 범위를 sample size 라고 한다.

하나의 샘플이 0부터 1까지의 값을 표현할 것인데 이를 얼마나 정밀하게 표현할 것인가...

예를 들어 샘플 사이즈가 3 bit라면 8단계로 표현 가능할 것이다. (0.0, 0.125, 0.25, 0.375, ...)

(출처: http://www.morphfx.co.uk/music/edu-sampling.htm)

 

따라서 아날로그를 디지털로 샘플링 시 필요한 용량은 Sample Rate와 Sample Size의 곱이다.

 

예를 들어보자. 우리가 일반적으로 구입할 수 있는 음반 CD의 스펙은 44,100 Hz, 16-bit 이다.

이는 1초에 44100개의 샘플을 추출하고, 각 샘플의 크기는 16 bit 라는 것이다.

따라서 둘을 곱하면 705,600 bit가 1초를 표현하는 데 사용된다. (88,200 byte = 86.13 KB)

이론상 4분짜리 곡은 20.15 MB가 필요할 것이다. 실제로 WAV, PCM 등의 무손실이면 이 용량이 나온다.

 

 

[나이퀴스트 샘플링 이론(Nyquist–Shannon sampling theorem)]

왜 대부분의 MP3 파일, 혹은 하드웨어들이 44,100 hz 스펙을 가지고 있는지 대충 납득할 수 있다.

 

이 이론의 결론을 요약하면 다음과 같다:

A sufficient sample-rate is therefore 2B samples/second, or anything larger. 

충분한 샘플링 레이트는 대역폭의 두 배, 혹은 그 이상이다.

 

더보기

우선 앨리어싱(Aliasing)이라는 개념이 있다.

원본을 샘플링하여 새로이 구성된 데이터가 원본과 다를 때 발생하는 왜곡이나 아티팩트를 의미한다.

 

샘플링 레이트가 충분히 높지 않다면 샘플된 데이터는 원본 데이터를 충분히 표현하지 못한다. 당연한 말이다.

아래 그림은 벽돌 벽(원본)을 사진(샘플링)으로 촬영한 사진이다.

 

충분히 큰 해상도로 촬영하여, 벽돌의 규칙적인 모양을 잘 표현한 사진

  

낮은 해상도로 촬영하였기에 벽돌의 연속적인 패턴을 제대로 표현하지 못하는 모습

 

이 건물은 벽돌이 연속성을 가지고 배치되어 있다.

그런데 낮은 해상도로 사진을 찍을 경우 벽돌의 연속성을 제대로 표현하지 못하고 원본과 다른 왜곡된 모습을 표현하게 된다. 마치 시멘트가 좀 더 많이 발려진 것 같이 보인다.

 

이유는 갈색 벽돌 부분과 흰색 시멘트라는 두 요소를 조화롭게 샘플링해야 하는데

샘플링 비율이 한 쪽에 치우치게 되면 원래의 모양이 왜곡된 것처럼 보이는 것이다.

 

그런데 무조건 이 문제가 생기는 것은 아니고...

정말 재수가 좋아서 원본의 특징점을 잘 샘플링 할 경우... 딱히 문제가 없을 수도 있다.

혹은 눈속임으로 샘플들 사이를 비벼버리는 테크닉도 있다. (anti-aliasing)

 

시그널 프로세싱은 이러한 아다리를 지향한다고 할 수 있다.

어떻게 하면 최대한 적은 샘플링으로 원본의 특징을 그대로 살릴 수 있을지...

이와 관련하여 나이퀴스트는 Aliasing이 발생하지 않는 샘플링 레이트를 대역폭의 2배라고 제시한 것이다.

 

관련해서 자세한 내용은 관련 논문 참고:

http://medialab.sjtu.edu.cn/teaching/DIP/Projects/chapter_bas/ShannonTheoremTutorial.pdf

 

사람이 소리로 들을 수 있는 가청 주파수의 범위는 20~20,000 Hz 으로 알려져 있다. (나는 16,000 까지밖에 안들리던데...)

즉 데이터의 대역폭이 20 KHz라는 것인데, 이를 왜곡(Aliasing 등) 없이 샘플링 하려면

대역폭의 2배인 40 KHz 샘플링 레이트 이상으로 샘플링 해야한다는 이론이다.

 

하지만 이론상의 최소 요구가 2배라는 것이고, real-world에서는 8배 정도가 안전하다는 얘기를 엔지니어들이 종종 한다.

그렇기 때문에 192 kHz 까지 사용하는 Hi-Fi 업계가 존재하는 것으로 보인다.

 

현재는 44,100Hz와 48,000Hz가 범용적으로 가장 많이 사용되고 있다.

44,100hz : 25FPS PAL, 30FPS NTSC

48,000hz : 29.97FPS NTSC

 

 

 

[Sources]

https://en.wikipedia.org/wiki/Sampling_(signal_processing)

https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem

https://en.wikipedia.org/wiki/Aliasing

+ Recent posts