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)'이나 '소켓 연결 끊김'으로 나타납니다."
프론트엔드 개발자가 주의할 점:
- 메모리 누수(Memory Leak) 주의:
useEffect에서 이벤트 리스너를 해제하지 않거나, 전역 변수에 데이터를 계속 쌓으면 GC가 처리해야 할 쓰레기가 많아져서 **멈춤 시간(Stop-the-World)**이 길어집니다.
- 대량의 객체 생성 지양: 스크롤 이벤트 같은 고빈도 작업에서 매번 새로운 객체를 생성하면(
new Object,[...]등), GC가 너무 자주 일어나 화면이 버벅댑니다.
- 크롬 개발자 도구 활용:
Performance탭에서 녹화를 떠보면 **Major GC*라고 표시되면서 메인 스레드가 멈춘 구간을 눈으로 확인할 수 있습니다. 이것이 바로 책에서 말한 그 '범인'입니다
사용자가 결제 버튼을 클릭하여 POST /payment 요청을 보냈습니다. 하지만 10초 동안 응답이 없어 브라우저에서 타임아웃(Timeout) 에러가 발생했습니다. 이때 서버의 상태에 대한 설명으로 가장 적절한 것은?
- 타임아웃이 발생했으므로 서버는 요청을 받지 못한 것이 확실하다. 즉시 재요청을 보내도 안전하다.
- 서버가 요청을 처리하던 중 에러가 발생하여 롤백된 상태이다.
- 서버가 요청을 성공적으로 처리했는지, 아예 받지 못했는지, 처리 중인지 알 수 없다.
- 타임아웃은 클라이언트의 네트워크 문제이므로 서버에는 아무 기록도 남지 않는다.
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. 둘 다 실패하는 경우도 있나요? (예외 케이스)
매우 드물지만, 시스템 구현 방식에 따라 둘 다 실패하는 경우도 발생할 수 있습니다.
- 교착 상태 (Deadlock): 두 트랜잭션이 서로 다른 자원을 쥐고 놔주지 않아서, DB가 둘 다(혹은 하나를) 강제로 취소시키는 경우. (단순 닉네임 선점에서는 드뭅니다.)
- 네트워크 타임아웃: 유저 A가 성공적으로 저장했지만, 성공 응답이 돌아오는 과정에서 네트워크가 끊기거나 지연되어 클라이언트가 "실패"로 오인하는 경우. (사실 서버엔 저장됨)
- 낙관적 락(Optimistic Locking)의 재시도 실패: 만약 서버가 "충돌 나면 자동으로 재시도해"라고 설정되어 있는데, 재시도 한계치를 초과하거나 로직 오류로 둘 다 튕겨내는 경우.
3. 프론트엔드 개발자의 대처법
결국 프론트엔드 입장에서는 **"내가 승자가 될지 패자가 될지 모른다"**는 가정하에 코드를 짜야 합니다.
- 승자 (A):
200 OK를 받으면 "가입 환영합니다!" 페이지로 이동.
- 패자 (B):
409 Conflict를 받으면alert("아쉽게도 방금 다른 분이 해당 닉네임을 사용했습니다. 다른 닉네임을 입력해주세요.")와 같은 구체적인 메시지를 띄우고, 입력창을 다시 활성화.
정리하자면:
분산 시스템의 '합의'나 '유일성 제약'의 목적 자체가 **"혼란스러운 와중에도 딱 하나만 선택하는 것"**이기 때문에, 시스템은 어떻게든 순서를 정해서 한 명은 성공시키고 나머지는 거절하는 것이 기본 원칙입니다.
이 개념을 이해하면 백엔드 개발자에게 **"409 에러 메시지에 '이미 사용 중'이라는 구체적인 코드를 넣어달라"**고 요청하는 근거가 명확해집니다.
혹시 프론트엔드에서 이런 동시성 문제를 테스트해볼 수 있는 방법이나 도구가 궁금하신가요?
프론트엔드에서 동시성 문제를 테스트해볼 수 있는 방법 3가지
- 백엔드가 진짜 동시성을 잘 막는지 궁금하다:
Promise.all코드로 공격해본다.
- 내 UI가 409 에러를 예쁘게 보여주는지 확인하고 싶다:
MSW로 409 응답을 하드코딩하거나, 프록시 툴로 응답을 조작한다.
