공부를 하면할수록 개념을 이해하는 과정에서 항상 CS 개념이 기본이된다는 생각이 든다. 오늘은 CS 면접 준비도 할 겸 실제로 Node.js를 이용하면서 내가 헷갈려하고 필요로하는 CS 개념을 정리해보려한다.
1. 동기 프로그래밍과 비동기 프로그래밍
- 동기 프로그래밍 (Synchronous Programming)
- 작업 순서: 작업이 순차적으로 실행됩니다. 하나의 작업이 완료된 후에야 다음 작업이 시작된다.
- 블로킹 I/O: 파일 읽기, 네트워크 요청 등의 I/O 작업이 완료될 때까지 다음 작업이 시작되지 않습니다. 이러한 작업은 "블로킹" 작업이라고 불린다.
- 코드 실행 흐름: 코드가 작성된 순서대로 실행됩니다. 각 작업이 완료될 때까지 코드 실행이 멈춘다.
- 비동기 프로그래밍 (Asynchronous Programming)
- 작업 순서: 작업이 동시에 실행될 수 있습니다. 하나의 작업이 완료되지 않아도 다음 작업이 시작됩니다.
- 논블로킹 I/O: I/O 작업을 비동기적으로 처리하여 작업이 완료되기 전에 다음 코드가 실행됩니다. 작업 완료 시 콜백 함수, 프라미스 등을 통해 결과를 처리합니다.
- 코드 실행 흐름: 코드 실행이 중단되지 않고 계속 진행됩니다. 작업 완료 시점에 콜백 함수나 프라미스의 then 메서드 등이 호출됩니다.
- Node.js에서 비동기 프로그래밍 기법이 중요한 이유
- 높은 성능과 확장성:
- 비동기 I/O 처리: Node.js는 비동기 I/O를 기본으로 설계되어, 파일 시스템, 네트워크, 데이터베이스 등과의 상호작용 시 블로킹 없이 처리할 수 있습니다. 이를 통해 많은 동시 연결을 처리할 수 있다.
- 이벤트 루프: Node.js의 이벤트 루프는 비동기 작업의 결과를 효율적으로 처리하여 높은 처리량을 제공 - 자원 효율성:
-싱글 스레드: Node.js는 기본적으로 싱글 스레드로 동작하지만, 비동기 작업을 통해 멀티스레드처럼 많은 작업을 동시에 처리하는 효과를 얻습니다. 이는 메모리와 CPU 자원을 효율적으로 사용하게 합니다. - 빠른 응답성:
-즉각적인 응답: 비동기 작업 덕분에 장시간 실행되는 작업이 다른 작업을 지연시키지 않습니다. 이는 서버 응답 시간을 단축시켜 사용자 경험을 향상시킵니다. - 간결한 코드 작성:
- 콜백, 프라미스, async/await: 비동기 프로그래밍을 위한 다양한 패턴과 문법 (callback, Promise, async/await)이 제공되어 복잡한 비동기 작업을 간결하고 가독성 있게 작성할 수 있음
Node.js의 비동기 프로그래밍은 서버가 많은 동시 요청을 효과적으로 처리하고, 높은 처리량을 유지하며, 자원을 효율적으로 사용하는 데 필수적입니다. 이는 특히 실시간 애플리케이션이나 대규모 네트워크 애플리케이션 개발에 매우 중요합니다.
2. 데이터베이스 ACID
ACID는 데이터베이스 트랜잭션의 신뢰성을 보장하는 데 중요한 네 가지 속성을 나타내는 약어입니다. ACID는 Atomicity(원자성), Consistency(일관성), Isolation(고립성), Durability(지속성)를 뜻합니다. 이 네 가지 속성을 이해하는 것은 데이터베이스 관리 및 설계에서 매우 중요합니다.
1. 원자성 (Atomicity)
- 정의: 트랜잭션은 전부 수행되거나 전혀 수행되지 않아야 합니다. 즉, 트랜잭션 내의 모든 작업이 완료되거나, 하나라도 실패하면 트랜잭션의 모든 작업이 취소되어야 합니다.
- 예시: 은행에서 계좌 A에서 계좌 B로 $100를 이체하는 경우, 트랜잭션은 A에서 $100를 빼고 B에 $100를 더하는 두 가지 작업으로 구성됩니다. 원자성은 두 작업이 모두 성공하거나, 하나라도 실패하면 둘 다 실행되지 않음을 보장합니다.
2. 일관성 (Consistency)
- 정의: 트랜잭션이 시작되기 전과 후에 데이터베이스가 일관된 상태를 유지해야 합니다. 즉, 트랜잭션은 데이터베이스의 무결성을 유지해야 하며, 모든 규칙과 제약 조건을 준수해야 합니다.
- 예시: 은행 계좌의 총 잔고는 모든 이체 작업 후에도 변함없이 유지되어야 합니다. 트랜잭션이 수행되기 전후에 데이터베이스의 상태는 일관성을 가져야 합니다.
3. 고립성 (Isolation)
- 정의: 동시에 실행되는 여러 트랜잭션이 서로의 중간 결과에 영향을 받지 않도록 보장해야 합니다. 각 트랜잭션은 독립적으로 실행되어야 하며, 다른 트랜잭션이 완료될 때까지 결과를 볼 수 없어야 합니다.
- 예시: 두 트랜잭션이 동시에 같은 계좌에서 돈을 인출하려고 할 때, 고립성은 두 트랜잭션이 서로 간섭하지 않도록 보장합니다. 각 트랜잭션은 독립적으로 실행되어야 하며, 다른 트랜잭션의 영향을 받지 않습니다.
4. 지속성 (Durability)
- 정의: 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 저장되어야 합니다. 시스템 오류나 장애가 발생하더라도 트랜잭션의 결과는 손실되지 않아야 합니다.
- 예시: 데이터베이스에 이체 작업이 성공적으로 완료된 후에는 시스템이 충돌하더라도 이체 기록은 남아 있어야 합니다. 트랜잭션의 결과는 영구적으로 저장됩니다.
예시로 설명
은행 이체 작업을 예로 들어 ACID 속성을 설명하면 다음과 같습니다:
- 원자성: 계좌 A에서 돈을 인출하고 계좌 B에 입금하는 두 작업이 모두 성공해야 합니다. 하나라도 실패하면 두 작업 모두 실행되지 않습니다.
- 일관성: 트랜잭션 전후에 두 계좌의 총액이 동일해야 합니다. 트랜잭션 후에도 데이터베이스 규칙과 제약 조건이 유지됩니다.
- 고립성: 두 트랜잭션이 동시에 동일한 계좌에 접근하더라도, 서로의 작업이 간섭하지 않습니다.
- 지속성: 이체 작업이 성공적으로 완료된 후에는 시스템 오류가 발생하더라도 이체 기록이 손실되지 않습니다.
이 네 가지 ACID 속성은 데이터베이스 시스템이 신뢰성과 무결성을 유지하는 데 필수적인 요소입니다. 데이터베이스 관리 시스템(DBMS)은 이러한 속성을 보장하여 안전하고 일관된 데이터 처리를 가능하게 합니다.
3. 이벤트 루프(EVENT LOOP)
Node.js에서 이벤트 루프(Event Loop)는 비동기 프로그래밍을 가능하게 하고, 논블로킹 I/O 작업을 처리하는 핵심 메커니즘입니다. 이벤트 루프는 Node.js가 고성능으로 동작할 수 있게 해주는 중요한 개념으로, 특히 서버 사이드 자바스크립트 환경에서 중요한 역할을 합니다.
- 이벤트 루프란?
이벤트 루프는 Node.js가 싱글 스레드 환경에서 비동기 작업을 효율적으로 처리할 수 있도록 해주는 메커니즘입니다. 이벤트 루프는 여러 비동기 작업(예: 파일 읽기/쓰기, 네트워크 요청 등)을 관리하고, 이러한 작업이 완료될 때 콜백 함수를 실행합니다.
- 이벤트 루프의 동작 방식
이벤트 루프는 다음과 같은 단계로 구성됩니다:
- 태스크 큐(Tasks Queue, Callback Queue):
- 비동기 작업이 완료되면, 해당 작업의 콜백 함수가 태스크 큐에 들어갑니다. 여기에는 I/O 작업의 콜백, 타이머 콜백, 비동기 API 콜백 등이 포함됩니다. - 콜 스택(Call Stack):
- 현재 실행 중인 함수들이 쌓이는 곳입니다. 자바스크립트 엔진은 콜 스택의 맨 위에 있는 함수를 실행합니다. - 이벤트 루프(Event Loop):
- 이벤트 루프는 콜 스택이 비어 있는지 확인하고, 태스크 큐에서 대기 중인 콜백을 콜 스택으로 옮겨 실행합니다. 이를 통해 비동기 작업의 콜백이 실행됩니다.
- 이벤트 루프의 단계
- 타이머(Timers):
- setTimeout()과 setInterval()의 콜백을 실행합니다. 지정된 시간이 지난 후 실행할 콜백 함수가 이 단계에서 처리됩니다. - I/O 콜백:
- I/O 작업의 콜백을 실행합니다. 예를 들어, 파일 시스템 작업이나 네트워크 요청의 콜백이 여기서 처리됩니다. - Idle, prepare:
- 내부적으로 Node.js가 사용하는 단계입니다. - 폴링(Poll):
- 새로운 I/O 이벤트를 체크하고, 적절한 콜백을 실행합니다. 이 단계에서 대부분의 작업이 처리됩니다. - 체크(Check):
- setImmediate()의 콜백을 실행합니다. 이 함수는 이벤트 루프의 한 사이클이 끝나기 전에 실행됩니다. - Close 콜백:
- 일부 닫기 이벤트 콜백을 처리합니다. 예를 들어, 소켓이 닫힐 때 실행됩니다.
const fs = require('fs');
console.log('시작');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('파일 읽기 완료');
});
setTimeout(() => {
console.log('타이머 완료');
}, 0);
console.log('끝');
이 코드의 실행 순서는 다음과 같습니다:
- console.log('시작');이 실행되어 '시작'이 출력됩니다.
- fs.readFile이 호출되고, 비동기 작업이 시작됩니다. 콜백은 태스크 큐에 넣어집니다.
- setTimeout이 호출되고, 0ms 후에 실행될 콜백이 태스크 큐에 넣어집니다.
- console.log('끝');이 실행되어 '끝'이 출력됩니다.
- 이벤트 루프는 콜 스택이 비어있는지 확인하고, 태스크 큐에서 setTimeout의 콜백을 가져와 실행합니다. '타이머 완료'가 출력됩니다.
- 파일 읽기 작업이 완료되면, fs.readFile의 콜백이 태스크 큐에서 실행됩니다. '파일 읽기 완료'가 출력됩니다.
이 과정에서 이벤트 루프는 콜 스택이 비어 있는지 확인하고, 태스크 큐에서 콜백을 실행하는 방식으로 동작합니다. 이를 통해 Node.js는 블로킹 없이 비동기 작업을 효율적으로 처리할 수 있습니다.
4. 실행 콘텍스트(Execution Context)
자바스크립트에서 실행 컨텍스트(Execution Context)는 자바스크립트 코드가 실행되는 환경을 의미합니다. 실행 컨텍스트는 변수, 함수, 객체 등이 어떻게 접근되고, 실행되는지를 결정하는데 중요한 역할을 합니다.
- 실행 컨텍스트의 구성 요소
실행 컨텍스트는 다음 세 가지 주요 구성 요소로 이루어져 있습니다:
- 변수 객체(Variable Object, VO):
- 변수 객체는 컨텍스트 내에서 정의된 변수, 함수 선언, 함수 인자를 포함합니다.
- 함수 실행 컨텍스트에서는 변수 객체를 활성화 객체(Activation Object, AO)라고 부릅니다.
- arguments 객체도 변수 객체에 포함됩니다. - 스코프 체인(Scope Chain):
- 스코프 체인은 현재 컨텍스트의 변수 객체와 상위 컨텍스트의 변수 객체를 참조합니다.
- 이를 통해 함수가 정의된 위치에 따라 변수나 함수에 접근할 수 있습니다.
- 스코프 체인은 함수의 유효 범위를 결정하는 데 사용됩니다. - this 바인딩(This Binding):
- thiis 키워드는 실행 컨텍스트에 바인딩되어, 컨텍스트에 따라 다른 값을 가질 수 있습니다.
- 전역 컨텍스트에서 this는 전역 객체를 가리키고, 메서드 내부에서는 호출한 객체를 가리킵니다.
- 생성자 함수에서 this는 새로 생성된 객체를 가리킵니다.
- 실행 컨텍스트의 종류
실행 컨텍스트에는 주로 세 가지 종류가 있습니다:
- 전역 실행 컨텍스트(Global Execution Context):
- 전역 코드가 실행될 때 생성됩니다.
- 단 하나만 존재하며, 애플리케이션이 종료될 때까지 유지됩니다.
- 전역 객체와 this를 전역 객체로 바인딩합니다. - 함수 실행 컨텍스트(Function Execution Context):
- 함수가 호출될 때마다 생성됩니다.
- 각 함수 호출마다 독립된 실행 컨텍스트가 생성됩니다.
- 함수의 인자, 지역 변수, this 등을 포함합니다. - Eval 실행 컨텍스트(Eval Execution Context):
- eval 함수가 호출될 때 생성됩니다.
- eal로 실행된 코드의 변수와 스코프를 관리합니다.
5. 프로미스(Promise)와 async/await
- 프로미스 (Promise)
개념
프로미스는 비동기 작업의 완료 또는 실패를 나타내는 객체입니다. 프로미스는 다음 세 가지 상태를 가질 수 있습니다:
- Pending: 초기 상태, 작업이 아직 완료되지 않음.
- Fulfilled: 작업이 성공적으로 완료됨.
- Rejected: 작업이 실패함.
사용 방법
프로미스는 new Promise 생성자를 사용하여 생성됩니다. 생성자는 두 개의 인자(콜백 함수)를 받습니다. 이 콜백 함수는 다시 두 개의 인자(resolve와 reject)를 받습니다.
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업 수행
const success = true;
if (success) {
resolve("작업 성공");
} else {
reject("작업 실패");
}
});
myPromise
.then(result => {
console.log(result); // "작업 성공"
})
.catch(error => {
console.error(error); // "작업 실패"
});
프로미스의 메서드
- then(): 프로미스가 성공했을 때 호출되는 콜백을 지정합니다.
- catch(): 프로미스가 실패했을 때 호출되는 콜백을 지정합니다.
- finally(): 성공 또는 실패와 관계없이 항상 실행되는 콜백을 지정합니다.
- async/await
개념
async/await는 프로미스를 사용하는 비동기 코드를 더 쉽게 작성할 수 있게 해주는 ES2017(ES8) 문법입니다. async 키워드는 함수가 프로미스를 반환하도록 만들고, await 키워드는 프로미스가 해결될 때까지 함수 실행을 일시 중지합니다.
사용 방법
async 키워드를 함수 앞에 붙여 비동기 함수를 만듭니다. 함수 내부에서 await 키워드를 사용하여 프로미스가 해결될 때까지 기다릴 수 있습니다.
async function myAsyncFunction() {
try {
const result = await myPromise;
console.log(result); // "작업 성공"
} catch (error) {
console.error(error); // "작업 실패"
}
}
myAsyncFunction();
- 프로미스와 async/await 비교
차이점
- 문법:
-프로미스: then, catch, finally 메서드를 사용하여 콜백 체인을 구성합니다.
-async/await: async 함수와 await 키워드를 사용하여 동기 코드처럼 작성할 수 있습니다.
- 가독성:
-프로미스: 복잡한 비동기 작업을 처리할 때 콜백 체인이 길어져 가독성이 떨어질 수 있습니다.
-async/await: 동기 코드처럼 작성할 수 있어 가독성이 높아지고, 코드의 흐름을 쉽게 파악할 수 있습니다.
- 에러 처리:
-프로미스: catch 메서드를 사용하여 에러를 처리합니다.
-async/await: try/catch 블록을 사용하여 에러를 처리합니다.
- 동시성 처리:
-프로미스: Promise.all, Promise.race 등의 메서드를 사용하여 여러 프로미스를 동시에 처리할 수 있습니다.
-async/await: await Promise.all([...])을 사용하여 여러 비동기 작업을 병렬로 처리할 수 있습니다.
결론
- 프로미스는 비동기 작업을 처리하기 위한 기본적인 방법으로, then과 catch를 사용하여 콜백을 체인처럼 연결합니다.
- async/await는 프로미스를 기반으로 비동기 코드를 동기 코드처럼 작성할 수 있게 해줍니다. 가독성이 높아지고 코드의 흐름을 쉽게 파악할 수 있어 복잡한 비동기 작업을 처리할 때 유리합니다.
- 두 가지 방법 모두 자바스크립트 비동기 프로그래밍에 중요한 역할을 하며, 상황에 맞게 선택하여 사용할 수 있습니다.
'개발 기초 다지기' 카테고리의 다른 글
내일배움캠프 27일차 : bcrypt 암호화 (0) | 2024.05.23 |
---|---|
내일배움캠프 26일차 : 데이터베이스 정규화 (0) | 2024.05.22 |
내일배움캠프 24일차 : 알고리즘 문제 정리(없는 숫자 더하기, 제일 작은 수 제거하기, 가운데 글자 가져오기, 내적, 약수의 개수와 덧셈) (0) | 2024.05.20 |
내일배움캠프 23일차 : VS Code Node.js 프로젝트 초기 세팅 (0) | 2024.05.17 |
내일배움캠프 22일차 : Node.js CRUD 구현하기 (0) | 2024.05.16 |
댓글