파이콘 한국 2019 - 파이썬으로 서버를 극한까지 끌어다 쓰기: Async I/O의 밑바닥Seomgi Han
서버 자원의 효율성을 극대화하기 위해서는 흔히 비동기 프로그래밍 방식을 적용합니다. 파이썬에서도 비동기 프로그래밍 방식으로 웹서비스를 제공하기 위해서 여타 웹프레임워크 프로젝트가 탄생했습니다. Tornado부터 시작하여, Aiohttp, Sanic, Vibora 등과 같은 프로젝트가 이러한 목적을 갖고 있는 프로젝트입니다. 그 중에서도 Vibora나 Sanic 등과 같은 프로젝트는 서로 자기가 처리 능력이 좋다며 자랑하고 있습니다. 그 이유로 비동기 방식을 활용하기 때문이라고 하는데, 설명을 들여다보면 빠지지 않고 나오는 단어가 있습니다. 바로 uvloop입니다.
uvloop이 무엇인지 궁금하여 알아보면, 다시 libuv라는 라이브러리를 만날 수 있습니다.
이 프로그램에서는 libuv가 어떤 방향성을 갖고 디자인되었으며, 어떤 기능을 갖고 있기에 다수의 프로젝트에서 사용하게 되었는지, 파이썬의 기본 라이브러리인 asyncio와는 어떤 차이점이 있는지를 알아봅니다. 또한 파이썬으로 만들어진 몇몇 웹프레임워크가 어떤 처리 능력을 보여주는지 직접 테스트해본 결과를 공유합니다.
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅Youngmin Koo
Python 프로그램을 디버깅하실 때 어떤 툴을 사용하시나요? 아무래도 가장 많이 사용하고 계신 툴은 PyCharm이 아닐까 싶습니다. PyCharm은 JetBrains에서 만든 GUI 환경에서 사용할 수 있는 Python IDE입니다.
PyCharm은:
로컬 컴퓨터에서 디버깅 모드로 (PyDev로) Python 프로세스를 실행할 수 있습니다.
로컬 컴퓨터에서 실행 중인 Python 프로세스에 Attach해 디버깅할 수 있습니다.
원격 서버에서 디버깅 모드로 (PyDev로) Python 프로세스를 실행할 수 있습니다.
하지만 원격에서 동작하고 있는 프로세스에 디버거를 Attach하는 기능은 제공하지 않고 있습니다. 또한 Python 디버깅 모듈인 pdb를 사용하여도 동작하고 있는 프로세스에 Attach하는 것은 지원하고 있지 않습니다.
로컬 환경에서는 문제 없이 돌아갔던 Python 프로그램이 원격 서버에서는 아무런 로그 없이 멈춰 버리는 경우 어디서부터 손을 대야 할지 정말 막막합니다. 이 때 GDB와 strace를 이용하면 어디에서 문제가 발생했는지 진단할 수 있습니다. 이 세션에서는 GDB와 strace를 이용해 디버깅하여 원격 리눅스 서버에서 Python Process가 Hang 되어 버리는 문제를 진단하고 해결했던 경험을 공유하려고 합니다.
4. 사용자의 증가 > 동시 접속자 증가
A. 연산을 더 빠르게
1. 하드웨어 Upgrade
2. Low level Language
B. 동시성을 늘린다
1. Parallelism
2. Concurrency
5. 사용자의 증가 > 동시 접속자 증가
A. 연산을 더 빠르게
1. 하드웨어 Upgrade
2. Low level Language
B. 동시성을 늘린다
1. Parallelism : 물리적인 level 에서 실제로 동시에 (병렬)처리
2. Concurrency : 논리적/software level에서 동시에 실행하는 것 처럼
6. 사용자의 증가 > 동시 접속자 증가
A. 연산을 더 빠르게
1. 하드웨어 Upgrade ≒ more money
2. Low level Language
B. 동시성을 늘린다
1. Parallelism ≒ more money
2. Concurrency
8. Thread and Process
Thread Process
Process 보다 적은 overhead 와 자원 사용 Thread 보다 더 많은 자원(cpu, memory ..) 사용
멀티코어 활용이 어려움 (concurrency) 멀티코어를 활용할 수 있음 (Parallelism)
Data 공유가 쉬움 Data 동기화가 어려움
공유 자원 접근의 위험성 (교착상태, 기아상태 …)
9. 식사하는 철학자들 문제
다섯 명의 철학자가 원탁에 앉아 있고
• 철학자들은 생각하거나, 밥을 먹거나, 배가 고프거나 3가지 상
태를 가질 수 있다
• 각자의 앞에는 스파게티가 있고 양 옆에 포크가 하나씩 있다
• 각각의 철학자는 다른 철학자에게 말을 할 수 없다
• 철학자가 스파게티를 먹기 위해서는 양 옆의 포크를 동시에 들
어야 한다
10. 식사하는 철학자들 문제
Solution 1
1. 왼쪽 포크가 사용 가능해질 때까지 생각을 한다. 만약 사용 가능해
지면 집어 든다.
2. 오른쪽 포크가 사용 가능해질 때까지 생각을 한다. 만약 사용 가능
해지면 집어 든다.
3. 양쪽의 포크를 잡으면 정해진 시간만큼 식사를 한다.
4. 오른쪽 포크를 내려놓는다.
5. 왼쪽 포크를 내려놓는다.
6. 다시 1번으로 돌아간다.
11. 식사하는 철학자들 문제
Solution 1
동시에 왼쪽 포크를 모두 집어 들었다면?
계속 생각만 하는 상태에 빠지게 된다.
=> deadlock (교착상태) 발생!!
12. 식사하는 철학자들 문제
Solution 2
1. 왼쪽 포크가 사용 가능해질 때까지 생각을 한다. 만약 사용 가능해
지면 집어 든다.
2. 오른쪽 포크를 들 수 있나 보고 안되면 왼쪽 포크를 내려놓고 1번
으로 되돌아간다.
3. 양쪽의 포크를 잡으면 정해진 시간만큼 식사를 한다.
4. 오른쪽 포크를 내려놓는다.
5. 왼쪽 포크를 내려놓는다.
6. 다시 1번으로 돌아간다.
13. 식사하는 철학자들 문제
Solution 2
동시에 왼쪽 포크를 모두 집어 들었다면?
행위를 반복하다 보면 적게 먹는 사람이 생긴다.
=> Thread간의 불균등한 자원 공유! (기아상태)
14. 식사하는 철학자들 문제
How to solve it?
자원 관리를 위한 software 적인 안전장치와
동시성 프로그래밍에 대한 skill 이 필요함
=> 개발이 복잡하고 어려워 짐
17. Coroutine
• 서브루틴 (sub routine)
반복되는 특정 기능을 모아 별도로 묶어 놓아 이름을 붙여 놓은 것
우리에게 익숙한 이름! 함수, 메서드
• 코루틴 (coroutine)
루틴을 진행하는 중간에 멈추어서 특정 위치로 돌아갔다가 다시
원래 위치로 돌아와 나머지 루틴을 수행할 수 있다
???
19. Coroutine
• 서브루틴 (sub routine)
반복되는 특정 기능을 모아 별도로 묶어 놓아 이름을 붙여 놓은 것
우리에게 익숙한 이름! 함수, 메서드
• 코루틴 (coroutine)
루틴을 진행하는 중간에 멈추어서 특정 위치로 돌아갔다가 다시
원래 위치로 돌아와 나머지 루틴을 수행할 수 있다
비동기와 결합하면 더욱 강력해진다!
20. 동기(Synchronous)와 비동기(Asynchronous)
• 동기: 시간 관계를 동시에 또는 동일하게 유지하는 처리 방식
(sync를 맞추다, 동기화 하다)
Ex: 대부분의 FPS 게임 – 유저 데이터의 시간 관계를 맞춰야 함
비디오와 자막의 싱크
• 비동기: sync를 맞추지 않아도 되는 처리 방식
(순차적이어도 되고 비순차적이어도 된다)
Ex: 듀랑고 – 비동기식으로 유저 데이터 수용
https://www.slideshare.net/sublee/spof-mmorpg
31. So, How to make asynchronous program?
좋은 건 알겠는데..
프로세스를 비동기식으로 만들기 위해서는?
32. Non-block 의 등장
Block <-> Non-block
명확한 논리적 기준으로 구분 가능한 반대 개념
Block:
• 함수가 끝날 때 까지 기다린다.
• 함수의 결과를 return 한다.
Non block:
• 함수를 바로 return 한다.
• 함수의 결과를 따로 저장한다.
33. Non-block 의 등장
function
Main
wait
Main Non block
Object
function
return
is_finish
False
is_finish
result
True
빈 시간 동안 다른 작업을 할 수 있다
Run concurrency
Block Non block
34. Non-block 의 등장
논블록 함수의 결과를 얻으려면 언제 끝나는지 물어봐야 한다
언제 끝나는지 계속 물어보는 과정이 필요함
Event Loop!
35. Event Loop
• 외부 환경에서 이벤트를 받는다.
• 이벤트가 발생하면 해당 이벤트에 대한 루틴을 실행시킨다.
36. Non block + Event Loop => Async !
Non block 함수가 완료되었는지 아닌지를 검사하는 event loop
• 원시적인 asynchronous loop는 작업이 완료되었는지 아닌지를
event loop가 주기적으로 물어본다
• 더 효율적인 asynchronous loop는 어떤 작업의 상태가 바뀌었
는지를 select 한다
• 완전 효율적인 asynchronous loop는 작업의 상태가 바뀌면
event loop에게 알려준다 (커널의 도움이 필요함)
여러가지 I/O Multiplexing 기술들
37. Async
• 그럼 비동기 loop에 non block이 아닌 block 함수가 들어오면?
Block 함수가 실행되는 동안 event loop가 멈춘다!
성능의 저하 발생
• 가장 좋은 방법은 비동기 loop에서 non block 함수만 실행하는
것. 그러나 피치 못할 사정으로 block 함수를 실행해야 할 때가
있다.
Block 함수를 Thread나 Process로 concurrency 하게 실행해서 non block
처럼 만든다.
39. 다시 shared data 접근의 위험 발생
• 여러 스레드가 같은 데이터를 참조 할 때
Thread1
Thread2
Thread3
프로세스의 흐름
data
40. 도와줘요 코루틴
• 코루틴은 중간에 루틴을 중단했다 다시 시작할 수 있다.
• 코루틴을 어떻게 비동기로 동작 시킬 수 있을까
41. 여러 개의 http 요청 보내기
• 한 개의 http 요청을 보내는 과정
1. http 프로토콜 형식에 맞게 문자열을 만든다.
2. 해당 문자열을 서버로 보낸다.
3. 서버한테 답장이 올 때 까지 기다린다.
4. 답장이 오면 결과를 저장한다.
42. 여러 개의 http 요청 보내기
• 한 개의 http 요청을 보내는 과정
1. http 프로토콜 형식에 맞게 문자열을 만든다.
2. 해당 문자열을 서버로 보낸다.
3. 서버한테 답장이 올 때 까지 기다린다.
4. 답장이 오면 결과를 저장한다.
매우 적은 시간
많은 시간 소요
매우 적은 시간
43. 여러 개의 http 요청 보내기
• 한 개의 http 요청을 보내는 과정
1. http 프로토콜 형식에 맞게 문자열을 만든다.
2. 해당 문자열을 서버로 보낸다.
3. 서버한테 답장이 올 때 까지 기다린다.
4. 답장이 오면 결과를 저장한다.
매우 적은 시간
많은 시간 소요
시간
중단 재개
Coroutine
매우 적은 시간
1 2 3 4
44. 여러 개의 http 요청 보내기
시간
Coroutine
Single thread with Async Loop
Coroutine
Coroutine
Coroutine
46. Coroutine + Async
• 동시성을(concurrent) 구현 But! Single thread
Single thread에서 실행되기 때문에 shared data 접근의 충돌의 걱정
이 아얘 사라짐!
동시성에 대한 안전장치를 만들지 않아도 되기때문에 코드 작성이 쉬움
개발 효율 증가
• 동시성이 있지만 또 다른 thread나 process를 만들 필요가 없음
메모리를 아낄 수 있음
Thread나 Process를 만드는 것에 대한 overhead가 없음. 시간 단축
48. Coroutine + Async
그러나 코루틴은 만병통치약이 아님
• 블록킹 함수 또는 CPU bound 작업을 할때 성능이 저하 됨
한 작업을 할 때에는 다른 작업을 처리할 수 없다.
블록킹 함수는 thread로 처리하자.
그 블록킹 함수가 shared data를 참조해야 하는 경우에는 결국 동시성
프로그래밍에 대한 지식과 software적 안전장치를 마련 해야함.
49. CPU bound? IO bound?
Bound: 밀접한 관계에 있는
• CPU bound: 연산이나 처리량이 많은 경우
ex) 문자열 연산, 사칙연산, 영상/이미지 처리
• IO bound: 입력과 출력이 많은 경우
• 보통 IO작업은 대기시간이 길고 CPU를 거의 사용하지 않는다
ex) file I/O, network I/O
50. 언제 비동기를 써야 할까
• 처리 순서의 시간 관계가 관련이 없는 작업이 많을 때
• IO bound 작업이 많을 때
• 여러 개의 작업을 처리 해야 할 때