데이터베이스
데이터베이스가 애플리케이션이 의존하는 영속 데이터를 저장하고, 조직하고, 신뢰성 있게 검색하는 방식.
데이터베이스가 존재하는 이유
애플리케이션 메모리는 일시적입니다. 실제 시스템은 크래시, 재시작, 배포를 견디는 영속적인 저장소가 필요합니다.
- 서버 메모리(RAM)는 휘발성이며 프로세스가 종료되면 지워집니다.
- 사용자, 주문, 메시지, 로그 같은 중요한 데이터는 실행이 끝난 뒤에도 유지되어야 합니다.
- 데이터베이스는 디스크를 기반으로 한 영속적이고 구조화된 저장소를 제공합니다.
상세정보
백엔드 서버는 운영체제의 제어 아래에서 실행되는 하나의 프로세스일 뿐입니다. 메모리 안의 변수들은 프로세스가 재시작되거나, 크래시가 나거나, 다시 배포되면 사라집니다. 그래서 메모리는 계산에는 이상적이지만, 장기 저장에는 전혀 신뢰할 수 없습니다.
프로덕션 시스템은 데이터의 영속성을 필요로 합니다. 사용자 계정은 재부팅 후에도 그대로 존재해야 합니다. 주문 기록은 배포 후에도 남아 있어야 합니다. 로그는 디버깅과 감사 목적으로 계속 접근 가능해야 합니다.
데이터베이스는 구조화된 데이터를 영구 저장소(보통 SSD 또는 디스크)에 기록함으로써 이 문제를 해결합니다. 메모리가 속도에 최적화되어 있다면, 저장소는 영속성에 최적화되어 있습니다. 데이터베이스 엔진은 데이터가 어떻게 기록되고, 인덱싱되고, 안전하게 복구되는지를 관리합니다.
데이터베이스가 없으면 시스템에는 장기 기억이 없습니다. 가장 나쁜 방식으로 무상태가 되어, 장애를 견디지 못하게 됩니다.
데이터베이스란 무엇인가?
데이터베이스는 데이터를 영구적으로 저장하고, 구조적으로 조직하며, 통제된 검색을 가능하게 하는 관리형 시스템입니다.
- 프로세스 수명과 무관하게 데이터를 디스크에 안정적으로 영속화합니다.
- 데이터를 구조화된 모델(테이블, 문서, 키-값)로 조직합니다.
- 쿼리 엔진을 제공하고 무결성을 위한 제약 조건을 강제합니다.
상세정보
데이터베이스는 단순한 저장소가 아니라, 데이터를 위한 통제된 실행 엔진입니다.
관계형 모델에서는 데이터가 행과 열로 구성된 테이블에 저장됩니다. 각 행은 레코드를 나타내고, 각 열은 속성을 나타냅니다. 스키마는 구조, 데이터 타입, 제약 조건을 정의하며, 잘못되었거나 일관성 없는 데이터가 삽입되는 것을 방지합니다.
데이터베이스는 쿼리 엔진도 제공합니다. 애플리케이션은 원시 파일을 읽는 대신, 구조화된 쿼리를 실행하여 특정 데이터 부분집합을 효율적으로 조회하거나 수정합니다.
시스템 관점에서 데이터베이스는 신뢰할 수 있는 단일 진실의 원천입니다. 데이터의 지속성을 보장하고, 구조적 무결성을 유지하며, 예측 가능한 상태 관리를 가능하게 합니다.
SQL vs NoSQL
SQL 및 NoSQL 데이터베이스는 서로 다른 구조적 및 확장성 트레이드오프를 가진 유사한 저장 문제를 해결합니다 — 어느 쪽도 보편적으로 더 우수하지는 않습니다.
- SQL: 구조화된 스키마, 테이블 기반 모델, 강한 일관성 보장.
- NoSQL: 유연한 스키마, 주로 문서 또는 키-값 기반, 수평 확장을 위해 설계됨.
상세정보
SQL 데이터베이스(관계형 시스템)는 엄격한 스키마를 가진 미리 정의된 테이블로 데이터를 구성합니다. 테이블 간 관계는 명시적으로 모델링되며, 강한 트랜잭션 보장이 기본입니다. 따라서 무결성과 복잡한 쿼리가 필요한 시스템에 적합합니다.
NoSQL 데이터베이스는 엄격한 스키마를 완화합니다. 많은 경우 문서 기반 또는 키-값 구조를 사용하여 레코드마다 필드가 달라질 수 있습니다. 이러한 유연성은 빠르게 변화하는 데이터 모델의 개발을 단순화할 수 있으며, 분산 클러스터 전반에서의 수평 확장을 더 자연스럽게 지원할 수 있습니다.
실제 차이는 아키텍처의 강조점에 있습니다. SQL은 구조와 강한 일관성을 우선합니다. NoSQL은 유연성과 분산 확장성을 우선합니다. 프로덕션 시스템은 워크로드에 따라 두 방식을 함께 사용하는 경우가 많습니다.
서버가 데이터베이스를 조회할 때 무슨 일이 일어날까?
대부분의 실제 시스템에서는 애플리케이션 로직이 아니라 데이터베이스 호출이 요청 지연 시간의 대부분을 차지합니다.
- 요청 흐름은 다음처럼 길어집니다: Client → Load Balancer → Server → Database → Server → Client.
- 서버는 데이터베이스가 쿼리를 처리하는 동안 블록되거나(await) 대기합니다.
- 데이터베이스 왕복 시간은 전체 응답 시간을 결정하는 경우가 많습니다.
상세정보
클라이언트가 HTTP 요청을 보내면, 결국 서버 프로세스에 도달합니다. 핸들러는 애플리케이션 로직을 실행하지만, 의미 있는 작업 대부분은 영속 데이터를 읽거나 써야 합니다.
서버는 데이터베이스 엔진에 쿼리를 보냅니다. 그 쿼리에는 SQL 파싱, 권한 확인, 관련 데이터 페이지 찾기, 인덱스 사용, 디스크에서 읽기, 결과 집합 구성 등이 포함될 수 있습니다.
이 시간 동안 서버 스레드는 보통 대기 상태입니다. 동기 모델에서는 블록되고, 비동기 모델에서는 이벤트 루프가 결과를 기다립니다. 어느 경우든 진행은 데이터베이스가 작업을 끝내는 데 달려 있습니다.
중요한 점은 이것입니다: 데이터베이스 상호작용은 백엔드 시스템의 지연 시간에 가장 크게 기여하는 경우가 많습니다. 비효율적인 쿼리, 누락된 인덱스, 네트워크 왕복, 디스크 I/O 지연은 애플리케이션 계산 시간보다 훨씬 큰 영향을 줄 수 있습니다.
시스템이 느리게 느껴진다면, 가장 먼저 확인할 곳은 종종 데이터베이스 계층입니다.
인덱싱
인덱스는 데이터베이스가 모든 행을 하나씩 확인하지 않고도 데이터를 빠르게 찾을 수 있게 도와줍니다.
- 인덱스 없음 → 데이터베이스가 행을 하나씩 확인합니다.
- 인덱스 있음 → 데이터베이스가 일치하는 데이터로 바로 이동합니다.
- 인덱스는 읽기 속도를 높이지만, 데이터를 쓸 때 추가 작업이 필요합니다.
상세정보
사용자 100만 명이 있는 테이블에서 특정 이메일 주소를 검색한다고 가정해 봅시다.
인덱스가 없으면, 데이터베이스는 일치하는 값을 찾을 때까지 각 행을 확인해야 할 수 있습니다. 테이블이 커질수록 이 작업은 느려집니다.
email 컬럼에 인덱스가 있으면, 데이터베이스는 별도의 정리된 구조를 유지합니다(책의 색인과 비슷합니다). 모든 데이터를 훑는 대신, 그 구조를 사용해 올바른 행을 빠르게 찾습니다.
인덱스는 읽기 속도를 훨씬 빠르게 만들지만, 공짜는 아닙니다. 데이터를 insert, update, delete 할 때마다 인덱스도 함께 업데이트되어야 합니다.
쿼리가 예상보다 느리다면, 가장 먼저 물어볼 질문 중 하나는: “검색하는 컬럼에 인덱스가 있는가?” 입니다.
트랜잭션과 일관성
트랜잭션은 관련된 작업 묶음이 모두 함께 성공하거나 모두 함께 실패하도록 보장합니다.
단계는 순차적으로 실행됩니다. 하나라도 실패하면 이전의 모든 변경 사항이 되돌려져 데이터베이스가 절반만 업데이트된 상태로 끝나지 않습니다.
- 여러 데이터베이스 작업을 하나의 논리적 단위로 묶을 수 있습니다.
- 어떤 단계에서 실패하면, 이전의 모든 변경 사항은 롤백됩니다.
- 이렇게 하면 데이터가 부분적으로만 업데이트된 상태로 남는 것을 방지할 수 있습니다.
상세정보
계좌 A에서 계좌 B로 돈을 이체한다고 생각해 봅시다.
1단계: 계좌 A에서 $100를 차감합니다.
2단계: 계좌 B에 $100를 추가합니다.
시스템이 1단계 이후, 2단계 이전에 충돌하면 돈이 사라집니다. 이것이 데이터 손상입니다.
트랜잭션은 이를 방지합니다. 두 작업은 트랜잭션 경계 안에 함께 묶입니다. 데이터베이스는 다음 중 하나를 보장합니다:
• 두 단계가 모두 성공적으로 완료되거나,
• 어느 단계도 영구적으로 적용되지 않음.
이러한 “모 아니면 도” 동작을 원자성이라고 합니다. 이는 실패가 발생하더라도 시스템이 논리적으로 일관된 상태를 유지하도록 보장합니다.
트랜잭션은 데이터의 정확성이 중요한 모든 곳에서 필수적입니다 — 금융, 재고, 인증 등.
ACID
ACID는 데이터베이스 트랜잭션을 올바르게 유지하고 장애에 강하게 만드는 네 가지 보장을 정의합니다.
- 원자성: 트랜잭션은 완전히 성공하거나 완전히 롤백됩니다.
- 일관성: 커밋된 트랜잭션 이후에도 데이터는 유효한 상태를 유지합니다.
- 격리성: 동시에 실행되는 트랜잭션이 서로를 손상시키지 않습니다.
- 지속성: 커밋된 데이터는 장애와 재시작 이후에도 유지됩니다.
상세정보
ACID는 이론이 아니라, 실용적인 신뢰성 계약입니다.
원자성은 부분 업데이트를 방지합니다. 한 단계라도 실패하면 모든 작업이 롤백됩니다.
일관성은 트랜잭션이 완료된 후에도 제약 조건, 관계, 규칙이 유지되도록 보장합니다.
격리성은 여러 사용자가 동시에 작업할 때 트랜잭션이 서로를 손상시키지 않도록 보호합니다.
지속성은 데이터베이스가 성공을 확인한 뒤에는 데이터가 영구 저장소에 기록되어 시스템 장애가 발생해도 살아남는다는 것을 보장합니다.
이러한 속성들이 함께 작동하여 데이터베이스를 실제 시스템의 신뢰할 수 있는 기반으로 만듭니다.
데이터베이스 확장
트래픽과 데이터가 증가하면, 단일 데이터베이스 인스턴스가 종종 병목이 됩니다. 확장 전략은 부하를 분산하거나 용량을 늘립니다.
- 수직 확장은 단일 데이터베이스 머신의 성능을 높입니다.
- 읽기 복제본은 데이터의 여러 복사본에 읽기 트래픽을 분산합니다.
- 샤딩은 전체 부하를 분산하기 위해 데이터를 여러 데이터베이스로 나눕니다.
상세정보
사용량이 증가하면 쿼리 수도 늘어납니다. 결국 단일 데이터베이스 서버는 들어오는 요청을 감당하지 못하게 됩니다.
한 가지 방법은 수직 확장입니다 — 기존 머신에 더 많은 CPU, 메모리 또는 더 빠른 스토리지를 추가하는 것입니다. 아키텍처가 그대로 유지되므로 가장 간단한 해결책입니다. 하지만 하드웨어 업그레이드에는 한계가 있고 비용도 빠르게 증가합니다.
또 다른 전략은 읽기 복제본을 추가하는 것입니다. 이 모델에서는 기본 데이터베이스가 쓰기를 처리하고, 복제된 복사본들이 읽기 전용 쿼리를 처리합니다. 이렇게 하면 기본 서버의 부담을 줄일 수 있지만, 노드 간에 데이터가 전파되는 동안 작은 지연이 생길 수 있습니다.
더 큰 규모의 성장이 필요한 경우, 시스템은 샤딩을 사용할 수 있습니다. 모든 데이터를 한 머신에 저장하는 대신, 데이터셋을 여러 데이터베이스 인스턴스에 나눠 저장합니다. 예를 들어, 하나의 샤드는 사용자 A–M을 저장하고 다른 샤드는 N–Z를 저장할 수 있습니다. 이렇게 하면 총 용량은 늘어나지만, 신중한 라우팅 로직과 운영 관리가 필요합니다.
이러한 접근 방식들은 모두 확장성을 개선하지만, 비용, 복잡성, 일관성 측면에서의 트레이드오프도 함께 가져옵니다.
데이터베이스 장애 및 병목 현상
데이터베이스가 느려지거나 실패하면, 전체 애플리케이션이 그 영향을 즉시 반영합니다.
애플리케이션
DB를 기다리는 요청
데이터베이스 상태
- 데이터베이스를 사용할 수 없게 되면, 요청은 즉시 실패하기 시작합니다.
- 느린 쿼리나 잠금 경합은 애플리케이션 전반의 지연 시간을 증가시킵니다.
- 연결, 디스크, 복제 지연과 같은 리소스 고갈은 불안정성을 초래합니다.
상세정보
데이터베이스는 백엔드 시스템에서 가장 중요한 의존성인 경우가 많습니다. 데이터베이스가 느려지거나 실패하면, 애플리케이션은 그 영향을 즉시 반영합니다.
데이터베이스에 접근할 수 없게 되면, 요청 핸들러는 쿼리를 완료할 수 없고 애플리케이션은 500 수준의 오류를 반환하기 시작합니다.
데이터베이스가 실행 중이더라도, 느린 쿼리, 인덱스 누락, 또는 잠금 경합은 지연 시간을 증가시킬 수 있습니다. 외부에서 보면 서버가 느려 보이지만, 실제로는 데이터베이스를 기다리고 있는 것입니다.
부하가 높을 때는 연결 고갈, 복제 지연, 디스크 포화와 같은 리소스 한계가 불안정성과 일관성 없는 동작을 유발할 수 있습니다.
많은 “애플리케이션 문제”는 영속성 계층에서 시작됩니다. 운영 환경 문제를 진단하려면 이러한 의존성 체인을 이해하는 것이 필수적입니다.
질문 섹션
1 / 5