HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
👻
개발 기록
/
🗾
데이터 중심 애플리케이션 설계
🗾

데이터 중심 애플리케이션 설계

1, 2장
  • API에서 여러 테이블을 참조해서 데이터를 가져와야 할 때 일반적으로는 SQL 쿼리로 JOIN을 해서 한 번에 가져오는 게 기본이에요.
    • 단일 DB, 같은 스키마 → SQL JOIN으로 한 번에 가져오는 게 기본.
    • 규모 크거나 분리된 환경 → 여러 번 쿼리해서 합치거나 캐시/서드파티 조회 조합.
  • 어떻게 DB를 덜 참조할지 고민하는 게 관건
    • 쿼리 최적화
    • 캐싱
    • 데이터 모델링 개선
    • 비동기, 배치 처리
  • 왓챠는 다대다 구조인가? 어떤 데이터 모델 채택 중인지?
    • noSQL, SQL 어떤 걸 더 많이 쓰는지..?
 
코비선생 왈)
  • 다대다 구조가 많음. 왓챠는 콘텐츠의 속성이 있음. ex 영화에 출연한 배우, 수록곡, 관련 콘텐츠, 영화에 붙을 게 너무 많으니.. 이런 식으로 설계하면 다대다 구조가 됨.
  • DB 테이블 설계 시 일대다로 만들고 정교화하면 다대다, 쿼리는 join이나 여러 테이블 참조하기 때문에 비용이 발생함.
  • 데이터를 얼마나 빠르게 조회할지, nosql - 데이터 많음, 빠르게 조회함. 피크 타임에 장점이 있음. nosql은 hashmap임. sql은 내부적으로 여러 테이블의 연관 데이터를 가져옴. 서버에선 객체로 파싱해서 내려줘야 함. 쓰기와 읽기 비율이 많은지로 판단함.
    • 다이나모db(nosql) : 재생기록, 이어보기 기록; 데이터 건수나 쓰기 비율이 많고 읽기 비율도 빠르게 증가할 떄가 있음.
 
3장

저장소 및 검색 방식 비교

개념
특징
장점
단점
사용 예시
파일 추가(Append-only log)
데이터가 추가될 때 파일 끝에 기록
- 구현 단순- 쓰기 성능 빠름- 장애 발생 시 복구 쉬움
- 데이터 중복 발생- 오래된 값 제거 및 파일 병합 필요
로그 저장, 이벤트 소싱
색인(Index)
특정 키 → 파일 내 위치를 가리키는 매핑 구조
- 조회 속도 향상- 원하는 데이터 빠르게 접근
- 추가 저장 공간 필요- 인덱스 업데이트 비용 존재
해시 인덱스, 검색 엔진
SSTable (Sorted String Table)
정렬된 키-값 쌍을 불변 파일로 저장
- 범위 검색 효율적- 머지 시 빠른 병합 정렬 가능- 압축 및 스토리지 효율적
- 쓰기 전에 정렬 필요- 임시 메모리 구조(MemTable) 필요
LSM 트리의 기본 단위
LSM 트리 (Log-Structured Merge Tree)
메모리에 먼저 쓰고, 일정 크기 되면 SSTable로 디스크에 기록 후 주기적 병합
- 쓰기 성능 매우 좋음- 디스크 순차쓰기 활용- 대용량 데이터 처리 유리
- 읽기 시 여러 SSTable 탐색 필요 → 읽기 비용↑- Compaction 비용 큼
Cassandra, HBase, RocksDB
B-트리 (Balanced Tree)
디스크 블록 단위로 분할된 균형 트리
- 읽기 성능 뛰어남- 특정 키 조회 O(log n)- 랜덤 액세스 최적화
- 쓰기 시 랜덤 I/O 발생- 디스크 조각화 가능
MySQL(InnoDB), PostgreSQL, Oracle
👉 요약하면:
  • 쓰기 위주 → Append-only + LSM 트리 (순차 쓰기 최적화)
  • 읽기 위주 → B-트리 (빠른 키 조회, 트랜잭션 DB 적합)
  • 범위 쿼리 → SSTable, B-트리
  • 로그/간단 저장 → Append-only
 
질문
  • 파일 추가, 색인, SS 테이블 & LSM 트리, B 트리 사내에서 쓰고 있는건?
  • 트랜잭션 처리 최적화 & 분석 최적화, 사내에선 어떻게 관리하고 있는지?
 
 
4장
구분
부호화 형식
데이터플로 (어떻게?)
프론트엔드 관점
웹 브라우저 ↔︎ 웹 서버
JSON (주로), XML
REST API (HTTP 요청)
가장 기본적이고 중요한 영역. JSON의 구조와 API 명세를 잘 이해하는 것이 핵심.
내부 서버 ↔︎ 내부 서버
프로토콜 버퍼, Thrift
RPC (gRPC 등)
프론트엔드가 직접 다루진 않지만, 백엔드 API가 느리다면 이런 내부 통신 때문일 수 있음. "gRPC 쓰시나요?"라고 물어볼 수 있는 배경지식!
비동기 처리 시스템
Avro
메시지 큐 (Kafka 등)
주문 처리, 알림 발송 등 백그라운드 작업이 어떻게 돌아가는지 이해하는 데 도움. 내가 보낸 요청이 어떤 과정을 거쳐 처리되는지 큰 그림을 그릴 수 있음.
그럼에도 불구하고 json 쓰는 이유?
웹 개발 환경에서는 기계의 처리 성능을 극한으로 끌어올리는 것보다 개발자의 생산성과 디버깅 편의성을 높이는 것이 더 중요하게 여겨지며, JSON은 이 두 마리 토끼를 모두 잡은 가장 합리적
 
7장
  • 트랜잭션 : 애플리케이션에서 몇 개의 읽기와 쓰기를 하나의 논리적 단위로 묶는 방법
    • 쓰는 이유 : 안전성 보장 (ACID)
    • 단점 : 코드가 길어지고 분산환경에서 관리가 어려움
  • 네트워크가 중간에 끊기거나 동시에 쓰기를 할 때 필요
  • 완전한 격리란 없음. 웬만한 애플리케이션은 완화된 격리 수준을 가짐.
 
8장
  • 단일 노드가 중대한 결정을 잘할 수 없으므로 다른 노드의 도움을 요청하고 동의할 정족수를 이루려는 프로토콜이 필요함.
  • 날짜 관련 소소한 정보들..
    • js의 date는 여러 문제가 많다. month +1, 크로스 브라우징 필요, 브라우저 시간대를 이용해서 국가마다 상이
    • date 관련 라이브러리를 쓰자
    • 1년은 365일 6시간임. 윤년이 생기는 이유
    • new Date(2024, 1, 29); // 2024-02-29 ← 윤년이니까 OK new Date(2023, 1, 29); // 2023-03-01 ← 23년은 윤년 아님 → 3월 1일로 교정됨
      // Date는 milliseconds 기반이라 “하루”가 항상 24시간이 아님 const d = new Date(); d.setDate(d.getDate() + 1); // 일부 타임존에서 날짜 2일 이동하는 버그 발생 가능
"책에 나온 'GC 중단' 경고는 자바스크립트에도 해당됩니다. 다만 프론트엔드에서는 서버 다운보다는 '화면 멈춤(Freezing)'이나 '소켓 연결 끊김'으로 나타납니다."
프론트엔드 개발자가 주의할 점:
  1. 메모리 누수(Memory Leak) 주의: useEffect에서 이벤트 리스너를 해제하지 않거나, 전역 변수에 데이터를 계속 쌓으면 GC가 처리해야 할 쓰레기가 많아져서 **멈춤 시간(Stop-the-World)**이 길어집니다.
  1. 대량의 객체 생성 지양: 스크롤 이벤트 같은 고빈도 작업에서 매번 새로운 객체를 생성하면(new Object, [...] 등), GC가 너무 자주 일어나 화면이 버벅댑니다.
  1. 크롬 개발자 도구 활용: Performance 탭에서 녹화를 떠보면 **Major GC*라고 표시되면서 메인 스레드가 멈춘 구간을 눈으로 확인할 수 있습니다. 이것이 바로 책에서 말한 그 '범인'입니다
 
사용자가 결제 버튼을 클릭하여 POST /payment 요청을 보냈습니다. 하지만 10초 동안 응답이 없어 브라우저에서 타임아웃(Timeout) 에러가 발생했습니다. 이때 서버의 상태에 대한 설명으로 가장 적절한 것은?
  1. 타임아웃이 발생했으므로 서버는 요청을 받지 못한 것이 확실하다. 즉시 재요청을 보내도 안전하다.
  1. 서버가 요청을 처리하던 중 에러가 발생하여 롤백된 상태이다.
  1. 서버가 요청을 성공적으로 처리했는지, 아예 받지 못했는지, 처리 중인지 알 수 없다.
  1. 타임아웃은 클라이언트의 네트워크 문제이므로 서버에는 아무 기록도 남지 않는다.
 
 
9장
인기 있는 서비스의 회원가입 페이지입니다. 전 세계에서 동시에 수만 명이 가입을 시도합니다. user123이라는 유일해야 하는 닉네임을 A유저와 B유저가 정확히 0.001초 차이로 입력하고 '가입'을 눌렀습니다. 분산 시스템 환경(DB가 여러 대)에서 단 한 명만 이 닉네임을 가져가도록 보장하기 위해 백엔드는 내부적으로 어떤 개념(또는 기술)을 사용해야 할까요? 또한, 프론트엔드 개발자는 '실패한 유저'에게 어떤 피드백을 줘야 할까요?
  • 백엔드 핵심 개념: 선형성(Linearizability)이 보장되는 비교-후-치환(CAS, Compare-and-Set) 연산 또는 **합의(Consensus)**가 필요합니다.
    • 단순히 조회 후 쓰기(get then set)를 하면 경쟁 조건(Race Condition) 때문에 둘 다 가입될 수 있습니다. "현재 user123이 비어있다면 내 걸로 등록한다"라는 연산이 원자적(Atomic)으로, 그리고 선형적으로 처리되어야 합니다.
  • FE 피드백 전략:
    • 이런 상황은 409 Conflict 등의 상태 코드로 응답이 올 가능성이 큽니다.
    • 단순히 "에러가 발생했습니다"라고 띄우면 안 됩니다. "이미 사용 중인 닉네임입니다"라고 명확하게 이유를 알려주거나, 닉네임 입력 단계에서 실시간 검증(Debounce 활용)을 통해 가입 버튼을 누르기 전에 미리 충돌 가능성을 최소화해야 합니다.

**일반적으로는 "조금이라도 늦은 사람(패자)"에게만 409 Conflict 에러가 발생하고, 먼저 처리된 사람(승자)은 성공(200/201)**합니다.
두 유저가 인간의 관점에서 "완전히 동시에" 눌렀다고 해도, 컴퓨터(데이터베이스) 내부에서는 미세한 시간 차이(나노초 단위)나 처리 순서가 반드시 존재하기 때문입니다.
이 과정을 **선형성(Linearizability)**과 원자성(Atomicity) 관점에서 구체적으로 설명해 드릴게요.

1. 정상적인 상황 (승자와 패자가 나뉘는 경우)

시스템이 제대로 설계되었다면, 데이터베이스는 이 요청을 **직렬화(Serialization)**하여 순서대로 처리합니다.
  • 상황: user123 닉네임이 비어 있는 상태.
  • T1 (0.000초): 유저 A의 요청이 DB에 먼저 도달하여 트랜잭션을 시작합니다. ("user123 있니? 없으면 넣는다.")
  • T2 (0.001초): 유저 B의 요청이 DB에 도달합니다.
  • 결과:
    • 유저 A (승자): DB가 아직 user123이 없음을 확인하고 저장을 완료(Commit)합니다. → 성공 (200 OK)
    • 유저 B (패자): DB가 저장을 시도하려는데, 방금 A가 만든 user123이 이미 존재함을 감지합니다(Unique Index 제약 조건 위반). → 실패 (409 Conflict)
이것이 9장에서 말하는 선형성의 핵심입니다. 데이터가 하나인 것처럼 동작하기 때문에, 누군가 자리를 차지하는 순간 그 뒤의 요청은 실패해야 합니다.

2. 둘 다 실패하는 경우도 있나요? (예외 케이스)

매우 드물지만, 시스템 구현 방식에 따라 둘 다 실패하는 경우도 발생할 수 있습니다.
  1. 교착 상태 (Deadlock): 두 트랜잭션이 서로 다른 자원을 쥐고 놔주지 않아서, DB가 둘 다(혹은 하나를) 강제로 취소시키는 경우. (단순 닉네임 선점에서는 드뭅니다.)
  1. 네트워크 타임아웃: 유저 A가 성공적으로 저장했지만, 성공 응답이 돌아오는 과정에서 네트워크가 끊기거나 지연되어 클라이언트가 "실패"로 오인하는 경우. (사실 서버엔 저장됨)
  1. 낙관적 락(Optimistic Locking)의 재시도 실패: 만약 서버가 "충돌 나면 자동으로 재시도해"라고 설정되어 있는데, 재시도 한계치를 초과하거나 로직 오류로 둘 다 튕겨내는 경우.

3. 프론트엔드 개발자의 대처법

결국 프론트엔드 입장에서는 **"내가 승자가 될지 패자가 될지 모른다"**는 가정하에 코드를 짜야 합니다.
  • 승자 (A): 200 OK를 받으면 "가입 환영합니다!" 페이지로 이동.
  • 패자 (B): 409 Conflict를 받으면 alert("아쉽게도 방금 다른 분이 해당 닉네임을 사용했습니다. 다른 닉네임을 입력해주세요.")와 같은 구체적인 메시지를 띄우고, 입력창을 다시 활성화.

정리하자면:
분산 시스템의 '합의'나 '유일성 제약'의 목적 자체가 **"혼란스러운 와중에도 딱 하나만 선택하는 것"**이기 때문에, 시스템은 어떻게든 순서를 정해서 한 명은 성공시키고 나머지는 거절하는 것이 기본 원칙입니다.
이 개념을 이해하면 백엔드 개발자에게 **"409 에러 메시지에 '이미 사용 중'이라는 구체적인 코드를 넣어달라"**고 요청하는 근거가 명확해집니다.
혹시 프론트엔드에서 이런 동시성 문제를 테스트해볼 수 있는 방법이나 도구가 궁금하신가요?
 
프론트엔드에서 동시성 문제를 테스트해볼 수 있는 방법 3가지
  • 백엔드가 진짜 동시성을 잘 막는지 궁금하다: Promise.all 코드로 공격해본다.
  • 내 UI가 409 에러를 예쁘게 보여주는지 확인하고 싶다: MSW로 409 응답을 하드코딩하거나, 프록시 툴로 응답을 조작한다.
크롬 개발자 도구의 'Request Blocking' 활용 (UI 대응 테스트용)
크롬 개발자 도구의 'Request Blocking' 활용 (UI 대응 테스트용)