분산시스템으로 사고 확장하기 (리눅스부터 DB, 분산 시스템 까지) [1] 리눅스

D5를 향해서 Chapter 1–1. 리눅스

SoniaComp
14 min readJan 1, 2024

2024 첫 프로젝트: D5를 향해서!!

후엥.. 벌써 4년차라니! 3년차에서 4년차로 넘어오는 과정은 험난했다. 소프트웨어로 해결해야할 일들의 규모도 복잡성도 엄청 커졌다. 그에 따라 알아야할 것들이 점점 많아지고 있다. 꺾이지 않고 한층 더 성장한 D5 개발자가 되기 위해서, 올해 첫 프로젝트를 시작해보고자 한다!!

D5를 향해서!!
- Technology: Masters(has very deep knowledge about the whole technology stack of the system)
- System: Evolves(evolves the architecture to support future requirements and defines its SLAs)
- People: Mentors(mentors others to accelerate their career-growth and encourages them to the changes)
- Process: Adjusts(adjusts the team process, listening to feedback and guiding the team through the changes)
- Influence: Multiple Teams(makes an impact not only on the whole team but also on other teams)

분산 시스템으로 사고 확장하기

빅데이터를 다루기 위한 시스템 솔루션은 참 다양하다. 참… 🥲 눈물 좀 닦고…
하지만 나는 알고 있다! Computer Science 의 역사가 짧은 만큼 솔루션들이 만들어지는 아이디어는 싱글 머신과 크게 다르지 않다는 것을!!

바쁘다 바빠 현대 사회에서 쉽고 빠르게 공부해보자! 초딩 동생들도 이해할만큼!!

****
1. 리눅스 OS
1.1 CPU
1.2 RAM
1.3 Disk I/O
1.4 Netowrk I/O
****

2. 데이터베이스 등장!
2.1 데이터베이스가 해결한 문제
2.2 데이터베이스 특성

3. 데이터베이스: RDB, NoSQL
3.1 데이터 저장구조와 액세스 방법
3.2 B Tree 구조
3.3 데이터 포맷
3.4 트랜잭션 처리와 복구
3.5 로그구조 스토리지
3.6 통신 방법: Connection Pool
3.7 분산 데이터베이스

4. 분산 파일 시스템 등장!
4.1 HDFS가 해결한 문제
4.2 HDFS 특성

5. 오브젝트 스토리지 등장!
5.1 오브젝트 스토리지가 해결한 문제
5.2 레퍼런스

6. 검색엔진
6.1 루씬
6.2 솔라
6.3 ES
6.4 hbase

7. Hadoop 에코시스템
7.1 Impala
7.2 Hive
7.3 HBase

참고 자료

리눅스는 송태웅 강사님의 리얼리눅스 강의 수강을 추천한다! 비전공자도 실무에서 반드시 알아야 할 깊은 CS 지식을 실습을 통해 쉽게 배울 수 있다.

1. 리눅스 OS

  • 사용자 application 관리
  • HW 자원 관리 (cpu, RAM, disk, network)
1.1 CPU
1.2 RAM
1.3 Disk I/O
1.4 Netowrk I/O

OS의 구성

  • Core 부분: PM(Process Management), MM(Memory Management), IRQ(interrupt request), exception 처리, locking
  • I/O 처리: Network(L4, L3, L2-device driver), Storage(VFS/FS/Block)
출처: https://commons.wikimedia.org/wiki/File:Linux_kernel_diagram.png

1.1 CPU

CPU 연산을 필요로 하는 함수의 종류: 유저 함수(어플리케이션, 라이브러리), 커널 함수
=> ABI (Application Binary Interface)
=> CPU, OS, 컴파일러: PA(Physical Address)를 VA(Virtual Address)로 변환해서 하드웨어 자원을 사용

프로그램은 가상 메모리 주소(VA)를 사용해 작성
=> MMU: VA-PA 매핑 테이블(변환) — OS는 MMU를 사용해 가상 메모리를 제공
=> 동적 링크 방식은 MMU를 통한 공유 라이브러리를 사용

가상 메모리 시스템
=> swap out: 요청받은 메모리가 사용 가능한 물리적 메모리의 크기보다 크면, OS는 현재 필요하지 않은 메모리 페이지를 디스크로 옮김
=> swap in: swap out 한 메모리 페이지에 프로그램이 접근하면 OS는 다시 메모리 공간을 확보하고 데이터를 불러옴

프로그램이 실행되는 과정
참고: Main Memory,

  1. ABI 에 맞춰서 VA 기준으로 프로그램 컴파일
  2. 컴파일 된 프로그램이 메모리에 로드: 운영체제로부터 가상 메모리 공간을 할당 받음
    => 코드 영역
    => 데이터 영역
    => 스택 영역: 컴파일 단계에서 메모리를 차지하는 크기가 결정되는 데이터(정적)
    => 힙 영역: 런타임에서 메모리를 차지하는 크기가 결정되는 데이터(동적 데이터)
  3. CPU: VA의 offset 을 보면서 instruction을 순차적으로 실행
    1) fetch: 메모리에서 CPU 내부의 PC 레지스터로 인스트럭션을 가져옴
    2) decode: 단순한 바이너리일 뿐인 인스트럭션을 해석
    3) fetch operands: argument 에 메모리 값이 필요할 경우 메모리에서 가져옴
    4) execute: 실행
    5) store: 결과물을 저장
  4. swap out/swap in 발생

CPU는 함수단위로 프로그램을 실행
=> 유저함수(일반, 라이브러리), 커널함수

커널함수의 진입점: irq, exception
1) 예외처리: 시스템 콜, pagefault
2) 인터럽트: USB 연결, network card
3) 커널 스레드(태스크): kworker, kswapd0, ksoftirqd

Processes: 533 total, 2 running, 531 sleeping, 3357 threads                  15:39:04
Load Avg: 1.17, 1.59, 1.72 CPU usage: 4.15% user, 4.15% sys, 91.69% idle
SharedLibs: 549M resident, 105M data, 33M linkedit.
MemRegions: 912381 total, 6285M resident, 146M private, 1563M shared.
PhysMem: 15G used (1755M wired, 4180M compressor), 68M unused.
VM: 283T vsize, 4171M framework vsize, 77889(4) swapins, 179626(0) swapouts.
Networks: packets: 85345286/86G in, 20656902/6690M out.
Disks: 33499727/693G read, 29662621/522G written.

PID COMMAND %CPU TIME #TH #WQ #PORT MEM PURG CMPRS PGRP PPID
354 WindowServer 23.2 20:47:26 23 6 4082+ 1838M- 32M+ 1232M 354 1
0 kernel_task 11.6 12:13:51 598/9 0 0 2320K 0B 0B 0 0

시스템 정보
=> uptime: 컴퓨터 켜놓은 시간
=> loadavg: 시스템 부하 상태. 1, 5, 15분 평균. 실행중인 프로세스/전체프로세스
=> tasks: running(실행중인 프로세스), sleeping(I/O나 event 기다리는 프로세스), stopped(stop 시그널을 받은 프로세스), zombie(프로세스가 종료되었지만 OS 내부 시스템 자원 해지가 안된 프로세스)

CPU 사용량
=> us: user-mode > 높은 우선순위 nice 0 또는 음수
=> sy: kernal-mode
=> ni: user-mode > 낮은 우선순위 nice 양수
=> id: idle(시스템 얼마나 여유로운지)
=> wa: I/O wait(처리 완료를 기다리는 시간)
=> hi: hard irq(인터럽트 전반부 처리(빠른 처리))
=> si: soft irq(인터럽트 후반부 처리(지연 처리))
=> st: 가상 CPU 구동(Guest OS 를 위한 VM 처리)

메모리 사용량
=> total: 총 메모리 사용량
=> free: 남는 메모리 공간
=> used: 사용중인 메모리 공간
=> buff/cache: 디스크 데이터를 메모리에 캐시
=> available: 프로세스가 처음 시작시 사용 가능한 메모리 공간(회수하면 얻을 수 있는 공간 포함)
=> swap: 메모리의 내용을 저장해둘 수 있는 Disk 공간

프로세스(task)별 세부 정보
=> PID: 프로세스 ID
=> PR: 20+nice
=> NI: nice (친절도)
=> VIRT: 가상 메모리 공간
=> RES: 실제 사용중인 물리 메모리 공간
=> SHR: 공유 메모리 공간
=> S: 프로세스 상태(D-uninterruptible sleep, I-idle, T-stopped by job control signal, t: stopped by debugger during trace, Z: zombie)

컴파일러: 전체 소스 내용을 한번에 실행파일(바이너리 파일)로 변환
인터프리터: 소스 파일 1라인씩 바이트코드로 변환해서 실행

CPU (x86) 의 주요 레지스터
코드(함수) 실행 주요 레지스터
* rip(instruction pointer): 다음 실행할 명령어 주소를 담는 레지스터
* rsp(stack pointer): 스택의 최상위 주소 — 현재 사용중인 공간 끝 주소
* rbp(base pointer): 함수 단위로 스택의 시작 주소 — 함수 단위 땅 시작 주소
함수인자 처리전용 레지스터
* rdi: 함수 첫번째 인자
* rsi: 함수 두번째 인자

PLT(Procedure Linkage Table): got 테이블을 통해서 라이브러리 함수로 연결하는 코드
GOT(Global Offset Table): 사용하는 라이브러리 함수 주소를 적어두는 테이블

1.2 메모리

$ free
total used free shared buff/cache available
Mem: 131806808 30096884 89911220 3608 11798704 100485184
Swap: 4194300 4 4194296
  • used: stack/heap (프로그램)
  • buffers: 파일 정보 (inode block 등)
  • cached: 파일 내용 (data block)

가상 메모리 장점
1) 메모리 절약
2) 보안
3) ABI
4) swap 영역: 메모리 확장

메모리 액세스의 특징
* 프로그램은 실행되려면 메모리에 로드 되어야 한다.
=> CPU가 접근할 수 있는 저장 공간은 두 개
1) 메모리: CPU 사이클 여러번 — 메모리 버스 타고 읽어서 다시 옴
2) 레지스터: CPU 사이클 1회
* 메모리는 Context oblivious
=> 메모리 유닛(메모리 + 컨트롤러)에는 접근해야할 메모리의 주소들이 계속 stream으로 들어옴. 어떻게 발생한 요청인지, 주소에 있는 데이터가 어떤 내용인지 Context 를 신경쓰지 않음

RSS: resident set size
VSZ: virtual memory size

CPU 캐시 라인: CPU가 메모리로부터 데이터를 가져올 때는 바이트 단위로 가져오지 않고 캐시라인을 가득 채울 만큼의 데이터를 가져옴 => 일반적인 애플리케이션의 경우 인접한 바이트들을 사용하는 경우가 많기 때문에 CPU의 메모리 접근 횟수를 줄여 성능을 향상 시키기 위함
스택 메모리 할당: cpu cache line size 가 16바이트의 배수로 이루어져 있어서, rsp 가 16byte 단위로 증가한다.

1.3 Disk I/O

리눅스는 모든 것을 파일로 생각하고, 파일 단위로 관리한다.
이때, 계층적으로 접근한다.
User Space: User Applications >> Kernel Space: System call interface > VFS(Virtual File system, Inode/Dentry cache) > FS(File system, Buffer/Page Cache) > Block(Block Layer, Device Driver)

블록 사이즈: 커널이 블록 디바이스를 읽고 쓰는 단위
섹터 사이즈: 디스크가 읽고 쓰는 단위

파일 시스템 포맷: 디스크에 물리적인 블록을 어떻게 Read/Write 할지에 대한 전략 (df -Th)
마운트: 디스크를 폴더에 연결(디스크의 파일 시스템 포맷에 기반하여 파일을 생성하고 사용)

파일 I/O는 기본적으로 메모리를 사용하는 Buffered I/O
=> 페이지 캐시: 메모리에 로드된 파일 내용 — 파일 정보나 파일 내용을 유지하는 용도(Buffers — file의 inode block 과 같은 meta 데이터/Cached — file의 datablock 같은 데이터 캐시)
=> Anonymous Page: Stack 이나 heap 공간으로 활용

WRITE 방법
1) 페이지 캐시 준비: write_begin
2) 유저 데이터 복사: 페이지 캐시에 데이터 write
3) 페이지 캐시 dirty 표시: write_end
4) 일정 조건(기본적으로 5초 주기)마다 dirty page 동기화(write back)

READ 방법
1) 페이지 캐시 탐색
2) 페이지 캐시 HIT: 메모리에서 READ
3) 페이지 캐시 Miss
3–1) 페이지 캐시 준비
3–2) 디스크 I/O 요청
3–3) 해당 프로그램 sleep
3–4) I/O 처리 완료 후 IRQ
3–5) wake up: 메모리에서 READ

페이징: 고정 분할 기법(내부 단편화)
세그먼트: 동적 분할 기법(외부 단편화)

Direct I/O
=>
WRITE 방법: 직접 저장 매체에 저장
=> READ 방법: 캐시에 접근하지 않고 디스크를 직접 접근한다. 이러한 접근은 요청 스레드의 상태를 sleep 상태로 전환시키고 디스크 컨트롤러가 커널을 우회하여 데이터를 직접 유저공간에 복사하도록 한다.

출처: https://dhkoo.github.io/2019/03/05/ios/

Block I/O 과정 (참고: https://jongmingim.tistory.com/54)

  1. remap: 파티션(sda2) => 디스크(sda)로 매핑(remap)
  2. queue: Block I/O 처리 단위 struct bio 의 enq 시점
  3. Get request: bio 를 담는 request 생성
  4. Insert: Block I/O SW 큐에 request 추가
  5. Dispatch: HW 큐의 request 들을 Block device driver 로
  6. Complete: 디스크(SDD/HDD)의 처리 끝나고 인터럽트 후 처리 시점 upcall

1.4 Network I/O

웹 클라이언트와 웹 서버 통신 과정

  1. DNS 통신: DNS 서버
  2. TCP/IP 통신: 라우터
  3. HTTP 통신: 웹 서버

리눅스 네트워크 처리 함수: L4(TCP), L3(IP), L2(Device Driver)

ICMP 통신(Internet Control Message Protocol): L3 통신
=> 라우터를 포함한 네트워크 장치가 다른 IP 주소와 통신할 때 성공 또는 실패를 나타내는 오류 메시지 및 운영 정보를 보내는데 사용

ARP 통신(Address Resolution Protocol): L2 통신
=> IP 네트워크 주소를 데이터 링크 프로토콜에서 사용하는 하드웨어 주소(Mac address)에 매핑하기 위해 인터넷 프로토콜에서 사용하는 프로토콜

TCP 통신(Transmission Control Protocol): L4 통신
=> 응용 프로그램이 데이터를 교환할 수 있는 네트워크 대화를 설정하고 유지하는 방법 정의

tcpdump와 wireshark 로 패킷 분석하기
참고: https://jinane.tistory.com/6, https://forl.tistory.com/133

# tcp 자주 사용되는 옵션
-i : network interface 설정 (eth)
-A : 패킷을 ASCII 로 출력
-nn: dns resolving 을 하지 않는 옵션. 불필요한 도메인과 포트가 안보이도록 설정
-l : ascii buffer - 이 옵션을 끄면 pipe 처리 불가능
-s1500: packet size 설정(1500 byte 는 MTU: Maximum Transfer Unit)
-w : 파일로 저장(pcap 파일로 저장하면 wireshark에서 읽을 수 있다)

--

--

SoniaComp
SoniaComp

Written by SoniaComp

Data Engineer interested in Data Infrastructure Powering Fintech Innovation (https://www.linkedin.com/in/sonia-comp/)

No responses yet