티스토리 뷰

반응형

한 사람의 영향도가 너무 큰 시스템은 성공하기 어렵다. 초기설계가 완료되고 상당히 견고해지면 여러사람이 다양한 관점을 가지고 각각 실험을 진행하면서 테스트는 시작된다. - 도널드 커누스

요청, 응답, 질의, 결과

현대 데이터 시스템에서 가정하고 있는 데이터 처리 방식은 먼저 시스템에 요청하거나 지시를 보낸 후 잠시 뒤에 해당 시스템으로부터 결과를 반환받는 방식이다. 데이터베이스, 캐시, 검색 색인, 웹 서버 등 그 밖의 많은 시스템이 이 같은 방식으로 동작한다.

온라인 시스템은 브라우저가 특정 페이지를 요청하든 서비스가 원격 API를 호출하든 일반적으로 사람이 사용자로서 요청을 보내고 응답을 기다린다고 가정한다.

사용자는 오래 기다릴 수 없기 때문에 이런 시스템에서는 응답 시간 단축에 노력을 많이 기울인다.

HTTP/REST 기반 API 때문에 요청/응답 방식의 상호작용이 매우 흔해져 이를 당연한 것으로 여기기 쉽다. 하지만 시스템을 구축하는 유일한 방법은 아니며, 시스템은 세 가지 유형으로 분류된다.

서비스(온라인 시스템)

서비스는 클라이언트로부터 요청이나 지시가 올 때 까지 기다리고 요청 하나가 들어오면 서비스는 가능한 빨리 요청을 처리해서 응답을 되돌려 보내려한다. 응답 시간은 서비스 성능을 측정할 때 중요한 지표이다.

일괄 처리 시스템(오프라인 시스템)

일괄 처리 시스템은 매우 큰 입력 데이터를 받아 데이터를 처리하는 작업을 수행하고 결과 데이터를 생산한다. 일괄 처리 작업은 수 분에서 때론 수 일이 걸리기 때문에 사용자가 작업이 끝날때가지 대기하지 않는다. 대신 대부분 하루에 한번 수행과 같이 반복적인 일정으로 수행한다. 일괄 처리 작업의 주요 성능 지표로는 처리량이 대표적이다. 처리량은 입력 데이터 중 특정 크기만큼 처리할 때 걸리는 시간으로 나타낸다.

스트림 처리 시스템(준실시간 시스템)

스트림 처리는 온라인과 오프라인/일괄 처리 사이의 어딘가에 있기 때문에 준실시간 near-real-time processing, nearline processing라 불린다. 스트림 처리 시스템은 일괄 처리 시스템과 마찬가지로 요청에 대해 응답하지 않으며 입력 데이터를 소비하고 출력 데이터를 생산한다. 그러나 일괄 처리 작업은 정해진 크기의 입력데이터를 대상으로 작동하지만 스트림 처리는 입력 이벤트가 발생한 직후 바로 작동한다. 이런 차이 때문에 스트림 처리 시스템은 같은 작업을 하는 일괄 처리 시스템보다 지연 시간이 낮다.

일괄처리 시스템 vs 스트림 처리 시스템
일괄 처리는 입력으로 파일 집합을 읽어 출력으로 새로운 파일 집합을 생성하는 기술이다. 검색 색인이나, 추천 시스템, 분석 등에서 사용된다. 데이터 셋은 어떤 식으로든 절대 “완료”되지 않는다. 일괄 처리 프로세서는 인위적으로 일정 기간씩 데이터 청크를 나눠야한다. 예를 들어 하루가 끝나는 시점에 하루 분량 데이터 또는 매 시간이 끝나는 시점에 그 한 시간 분량의 데이터를 처리한다. 일간 일괄 처리의 문제점은 변화가 하루가 지나야 반영된다는 것인데, 성격 급한 사용자가 느끼기에는 너무 느리고, 이런 지체를 줄이려면 좀 더 자주 처리를 실행해야한다. 매 초가 끝나는 시점에 1초 분량의 데이터를 처리하거나 고정된 시간 조각 이라는 개념을 완전히 버리고 단순히 이벤트 가 발생할 때마다 처리해야한다. 스트림 처리의 기본 개념이다.

일괄 처리는 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션을 구축하는데 매우 중요한 구성요소다. 그래서, 구글은 2004년 구글을 대규모로 확장가능하게 만든 알고리즘인(1) 맵리듀스(2)를 발표했다. 맵 리듀스는 그 이후에 하둡과 카우치DB, 몽고DB 등 다양한 오픈소스 시스템에서 구현되었다.

1 구글을 대규모로 확장가능하게 만든 알고리즘

Without understanding functional programming, you can’t invent MapReduce, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their 6.001-equivalent programming class that purely functional programs have no side effects and are thus trivially parallelizable…

https://www.joelonsoftware.com/2005/12/29/the-perils-of-javaschools-2/

 

The Perils of JavaSchools

Lazy kids. Whatever happened to hard work? A sure sign of my descent into senility is bitchin’ and moanin’ about “kids these days,” and how they won’t or can’t d…

www.joelonsoftware.com

2 맵리듀스 paper

https://static.googleusercontent.com/media/research.google.com/ko//archive/mapreduce-osdi04.pdf

맵 리듀스의 중요성이 떨어지고 있지만 왜 일괄처리가 유용한지 명확하게 그림을 그려주기 때문에 맵리듀스를 이해할 가치는 충분하다.

일괄 처리는 사실 컴퓨터 연산에 있어 매우 오래된 형태다. 디지털 컴퓨터가 발명되기 훨씬 전 1890년 미국 인구 조사에서 사용된 홀러리스 머신(3)과 같은 천공 카드 집계기는 대량의 입력 데이터로부터 통계를 집계하기 위해 반자동 형태의 일괄처리를 구현했다. 그리고 맵리듀스는 1940년대와 1950년대에 비즈니스 데이터 처리에 널리 사용된 전기 기계식 IBM 카드 분류기와도 신기할 정도로 비슷하다. 늘 그렇듯 역사는 반복된다.

3 홀러리스 머신

https://www.census.gov/history/www/innovations/technology/the_hollerith_tabulator.html 

 

The Hollerith Machine - History - U.S. Census Bureau

The Hollerith Machine 1888 Competition View larger imageHollerith's electronic tabulator, 1902. Following the 1880 census, the Census Bureau was collecting more data than it could tabulate. As a result, the agency held a competition in 1888 to find a more

www.census.gov

http://www.officemuseum.com/data_processing_machines.htm

 

Tabulating Machines

Antique Data Processing Machines Punched Card Tabulating Machines   Hollerith Electric Tabulator, US Census Bureau, Washington, DC, 1908, Photograph by Waldon Fawcett. Library of Congress, LC-USZ62-45687. Mid-1880s to 1900 The first commercial data proces

www.officemuseum.com

https://www.youtube.com/watch?v=17On5ItcrBA&t=233s&ab_channel=MichaelHolzheu

3 1890미국인구조사

https://en.wikipedia.org/wiki/1890_United_States_census

 

1890 United States census - Wikipedia

From Wikipedia, the free encyclopedia 11th US national census The 1890 United States census was taken beginning June 2, 1890. The census determined the resident population of the United States to be 62,979,766, an increase of 25.5 percent over the 50,189,2

en.wikipedia.org

4 IBM 카드 분류기

미국 인구 조사로부터 시작된 배치처리는 IBM 카드분류기를 통해 지단히 사용화되었고 발전해왔다.

http://bitsavers.trailing-edge.com/pdf/ibm/punchedCard/Sorter/A24-1034-1_82-83-84_sorters.pdf

https://www.youtube.com/watch?v=L7jAOcc9kBU&t=557s&ab_channel=TheCentreforComputingHistory

https://www.youtube.com/watch?v=liXI4441j00&ab_channel=PeriscopeFilm

 

이제 본격적으로 UNIX를 활용한 배치처리에 대해 살펴볼 것이다. 

유닉스 도구로 일괄 처리하기

웹 서버가 하나 있고 들어온 요청이 처리될 때마다 로그 파일에 한 줄씩 추가한다고 가정한다. 아래의 예는 로그 중 한 줄인데 nginx의 기본 액세스 로그 형식을 사용한다.

172.17.0.1 - - [09/Jul/2023:17:07:50 +0000] 
"GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [09/Jul/2023:17:07:51 +0000] 
"GET /favicon.ico HTTP/1.1" 404 555 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"
2023/07/09 17:07:51 [error] 30#30: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost", referrer: "http://localhost/"

로그 형식의 정의는 아래와 같다.

$remote_addr - $remote_user [$time_local] “$request”
$status $body_bytes_sent “$http_referer” “$http_user_agent”
root@6716da41cbd9:/etc/nginx# cat /var/log/nginx/access.log
172.17.0.1 - - [09/Jul/2023:17:41:49 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [09/Jul/2023:17:41:58 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"
root@6716da41cbd9:/etc/nginx# cat /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 5
      2 /
root@6716da41cbd9:/etc/nginx# cat /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -n 5
      2 172.17.0.1

파이썬으로도 작성할 수도 있다. 유닉스 파이프라인과 루비 스크립트와의 차이는 해시테이블의 유무이다.

from collections import defaultdict
counts = defaultdict(int)
with open('/var/log/nginx/acess.log') as lines:
    for line in lines:
        counts[line.split()[6]] += 1

top5 = sorted(counts.items(), key=lambda x: x[1])
for url,counts in top5:
    print(f'#{counts}: {url}')

어떤 접근법이 더 좋은지는 다른 URL이 얼마나 되느냐에 따라 다르다. 중소 규모의 웹사이트 대부분은 고유 URL과 해당 URL 카운트를 대략 1GB 메로리에 담을 수 있다. 백만 개의 로그 항목도 모두 같은 URL 하나만을 가리킨다면 해시 테이블에 필요한 공간은 한 개의 URL과 그 카운트 값을 저장할 만큼만 필요하다. 인 메모리 해시테이블도 잘 동작한다. 작업량이 크다면 정렬 접근법을 사용하는 것이 좋다. 데이터 청크를 메모리에서 정렬하고 청크를 세그먼트 파일로 디스크에 저장한다. 그 다음 각각 정렬된 세그먼트 파일 여러개를 한 개의 큰 정렬 파일로 병합한다. 병합 정렬은 순차적 접근 패턴을 따르고 이 패턴은 디스크 상에서 좋은 성능을 낼 수 있다. GNU Coreutils(리눅스)에 포함된 sort 유틸리티는 메모리보다 큰 데이터셋을 자동으로 디스크로 보내고 자동으로 여러 CPU 코어에서 병렬로 정렬한다. 유닉스 파이프 명령이 메모리 부족 없이 손쉽게 큰 데이터 셋으로 확장 가능하다는 것이다.

지금부터는, 여러분이 작성한 코드보다 왜 유닉스의 기본 명령이 훌륭하며, 배치처리에 진심인지 알아 볼 것이다.

유닉스 철학

doug mcllory는 1964년 유닉스 파이프를 처음으로 이와 같이 설명했다. “다른 방법으로 데이터 처리가 필요할 때 정원 호스와 같이 여러 다른 프로그램을 연결하는 방법이 필요하다. 이것은 I/O 방식이기도 하다.” 배관 공사와 비슷한 점에 착안해 파이프 브로그램을 연결하는 아이디어를 냈고, 이 아이디어는 지금은 유닉스 철학의 일부가 됐다. 

doug mcllory는 sort(1), spell(1), diff(1), join(1), graph(1), speak(1), and tr(1) 등의 명령어를 초기 작성하였다

To put my strongest concerns into a nutshell
1. We should have some ways of connecting programs like garden hose--screw in another segment when it becomes when it becomes necessary to massage data in another way. This is the way of IO also.

Advice from Doug Mcllroy: https://9p.io/cm/cs/who/dmr/mdmpipe.html

 

Prophetic Petroglyphs

Advice from Doug Mcilroy Attached by magnet to the wall of my office is a yellowed sheet of paper, evidently the tenth page of an internal Bell Labs memo by Doug McIlroy. Unfortunately, I don't have the rest of the note. When this was written, in 1964, Bel

9p.io

 

현재 유닉스 철학으로 알려진 이 설계 원리는 개발자와 유닉스 사용자 사이에서 더욱 대중화됐다. 1978년에 기술된 유닉스 철학은 아래와 같다.

  1. 프로그램이 한 가지 일만 하도록 작성하라. 새 작업을 하려면 기존 프로그램을 고쳐 새로운 “기능”을 추가해 프로그램을 복잡하게 만들기보다는 새로운 프로그램을 작성하라.
  2. 든 프로그램의 출력은 아직 알려지지 않은 다른 프로그램의 입력으로 쓰일 수 있다고 생각하라. 불필요한 정보로 출력이 너저분해서는 안 된다. 입력 형식으로 엄격하게 열을 맞춘다거나 이진 형태를 사용하지 마라. 대화형 입력을 고집하지 마라.
  3. 소프트웨어를 빠르게 써볼 수 있게 설계하고 구축하라. 심지어 운영체제도 마찬가지다. 수 주 안에 끝내는 것이 이상적이다. 거슬리는 부분은 과감히 버리고 새로 구축하라.
  4. 프로그래밍 작업을 줄이려면 미숙한 도움보단 도구를 사용하라. 도구를 빌드하기 위해 한참 둘러가야 하고 게다가 사용 후 바로 버린다고 할지라도 도구를 써라.

자동화, 빠른 프로토타이핑, 증분 반복, 실험 친화, 큰 프로젝트를 청크로 나누어 처리하기와 같은 방법은 오늘날의 애자일 및 데브옵스 와 매우 흡사하고 놀랍게도 40년이 지났지만 거의 바뀌지 않았다.

sort는 한 가지 일을 잘 해내는 프로그램의 훌륭한 예다. sort의 구현은 대부분의 프로그래밍 언어에 포함된 표준 라이브러리보다 확실히 뛰어나다. 표준 라이브러리는 처리하는 데이터를 디스크로 내리지 않고, 다중 스레드를 활용하지 않는다. sort는 uniq 같은 다른 유닉스 도구와 조합해서 사용했을 때 비로소 강력해진다.

uniq

https://www.lesstif.com/lpt/linux-uniq-95879394.html

$ sort input.txt | uniq

abc
Abc
ABc
apple
BALL
bat

bash 같은 유닉스 셸을 사용하면 작은 프로그램들을 가지고 놀랄 만큼 강력한 데이터 처리 작업을 쉽게 구성할 수 있다. 이런 프로그램 중 다수는 다른 그룹에 속하는 사람들이 만들었지만 유연한 방식으로 함께 조합할 수 있다. 유닉스에 이런 결합성을 부여하는 것은 무엇일까?

동일 인터페이스

모두가 같은 입출력 인터페이스를 사용해야한다

어떤 프로그램의 출력을 다른 프로그램의 입력으로 쓰고자 한다면 이들 프로그램은 같은 데이터 형식을 사용해야 한다. 호환 가능한 인터페이스를 써야 한다. 특정 프로그램이 다른 어떤 프로그램과도 연결 가능하려면 프로그램 모두가 같은 입출력 인터페이스를 사용해야한다.

유닉스에서의 인터페이스는 파일(파일 디스크립터)이다. 파일은 단지 순서대로 정렬된 바이트의 연속이다. 파일은 이처럼 단순해서 같은 인터페이스로 파일시스템의 실제 파일, 프로세스 간의 통신 채널(유닉스 소켓, stdin, stdout) 장치 드라이버(/dev/audio, /dev/lp0), TCP 연결을 나타내는 소켓 등 다른 여러 가지 것을 표현할 수 있다. 당연하게 받아들이기 쉽지만 이질적인 것들이 하나의 동일 인터페이스를 공유한다는 점은 사실 꽤나 주목할 만 하다

awk, sort, uniq, head는 입력 파일을 \n 개행문자로 분리된 레코드로 다룬다. \n로 선택된 이유는 딱히 없다. 애초에 이런 목적으로 사용하려고 만든 0x1e(’RS’)가 있기도 하다. 어쨌든 같은 레코드 분리자를 사용해 표준화했기 때문에 이 모든 프로그램이 상호 운용 가능하다.

그리고, 대개 한 줄(레코드)을 공백이나 탭 문자로 분리해 필드로 만든다. 하지만 CSV, 파이프 |, 등의 다른 부호화 방법도 사용한다. 완벽하지도 않음에도 심지어 수십 년이 지났어도 유닉스의 동일 인터페이스는 여전히 대단하다. 유닉스 도구만큼 상호 운용 또는 구성 면에서 뛰어난 소프트웨어는 많지 않다. 이메일 내용과 온라인 쇼핑 내역을 파이프로 연결해서 맞춤형 분석 도구를 사용해 스프레드시트에 넣고 그 결과를 사회 연결망 또는 위키에 올리는 것을 쉽게 할 수 없다. 오늘날 유닉스 도구 처럼 매끄럽게 협동하는 프로그램이 있다는 건 정상이 아니라 예외적이다.

로직과 연결의 분리

1 유닉스 도구의 특징으로 또, 표준 입력과 표준 출력을 사용한다는 점을 들 수 있다. 프로그램을 실행하고 아무것도 설정하지 않는다면 stdin은 키보드, stdout은 화면으로 출력한다. 혹은 파일에서 입력을 가져와 다른 파일로 출력을 재전송할 수 있다.

2 파이프는 한 프로세스의 stdout을 다른 프로세스의 stdin과 연결한다. 이때 중간 데이터를 디스크에 쓰지 않고 작은 인메모리 버퍼를 사용해 프로세스 간 데이터를 전송한다.

3 stdin, stdout으로만 처리하고 싶다면 유닉스 접근법이 가장 좋다. 프로그램은 입력이 어디서부터 들어오는지 출력이 어디로 나가는지 신경 쓰거나 알 필요조차 없다. 느슨한 결합(loose couping), 지연 바인딩(late binding), 제어 반전(inversion of control) 이라고도 한다. inversion of control https://martinfowler.com/bliki/InversionOfControl.html

 

bliki: InversionOfControl

a bliki entry for InversionOfControl

martinfowler.com

4 stdin 으로 입력받고, stdout 으로 출력하도록 작성하면 파이프라인에 바로 끼워 사용할 수 있다.

투명성과 실험

유닉스 명령에 들어가는 입력 파일은 불변으로 처리된다. 어떤 옵션에 명령을 쓰더라도 입력 파일은 불변이다.

어느 시점이든 파이프라인을 중단하고 출력을 파이프를 통해 less로 보내 원하는 형태의 출력이 나오는지 디버깅 가능

특정 파이프라인 단계의 출력을 파일에 쓰고 그 파일을 다음 단계의 입력으로 사용할 수 있다. 이렇게 하면 전체 파이프라인을 다시 시작하지 않고 다음 단계부터 재시작할 수 있다. 

sed "10q;d" file.text > tenth-line.txt | grep "hello world"

file.txt의 10번째 라인을 파일로 쓰고 다음 단계의 입력으로 사용하는 경우.

전반적으로 멱등성을 보장하며, 시스템의 유기성을 강조한다.

유닉스 도구는 관계형 데이터베이스의 질의 최적화 도구와 비교하면 상당히 불친절하고 단순하지만 이처럼 놀라울 정도로 유용하다. 특히 실험용으로 매우 좋다. 유닉스 도구를 사용하는데 가장 큰 제약은 단일 장비에서만 실행된다는 점이다. 그래서 하둡과 같은 도구가 필요하다.

 

다음장에서 계속...

 

반응형