๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Language/Python

[Python] Flask์™€ FastAPI๋กœ Python ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ | LIM

by forestlim 2022. 7. 3.
728x90
๋ฐ˜์‘ํ˜•

๐Ÿ“Œ ํŒŒ์ด์ฌ์˜ ๋™์‹œ์„ฑ

ํŒŒ์ด์ฌ์—์„œ๋Š” ์“ฐ๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” threading ๋ชจ๋“ˆ, ํƒœ์Šคํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” asyncio ๋ชจ๋“ˆ, ๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ์„ธ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” multiprocessing ๋ชจ๋“ˆ์ด ์žˆ๋Š”๋ฐ ์—ฌ๊ธฐ์„œ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ์‹ค์ œ๋กœ multiprocessing ๋ฟ์ด๋‹ค.

threading ๊ณผ asyncio ๋ชจ๋“ˆ์€ ๋ชจ๋‘ ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ์‹คํ–‰ํ•  ์ˆ˜๋ฐ–์— ์—†๋‹ค. ์„œ๋กœ context switching ๋˜๋ฉด์„œ ๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์„œ ์ž‘์—… ์‹œ๊ฐ„์„ ์ค„์ธ๋‹ค. 

 

๐Ÿ“Œ threading๊ณผ asyncio ์ฐจ์ด(concurrency)

ํŒŒ์ด์ฌ์—์„œ ๋™์‹œ์ ์ธ ์ž‘์—…์„ ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” threading๊ณผ asyncio์˜ ์ฐจ์ด์ ์€ threading์€ ๋ชจ๋“ˆ์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” ๊ฐ ์“ฐ๋ ˆ๋“œ๋ฅผ ์šด์˜์ฒด์ œ๊ฐ€ ์–ธ์ œ๋“ ์ง€ ๋ฉˆ์ถ”๊ณ  ๋‹ค๋ฅธ ์“ฐ๋ ˆ๋“œ๋ฅผ ์ง„ํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ์šด์˜์ฒด์ œ๊ฐ€ ์Šค๋ ˆ๋“œ๋ฅผ ์„ ์ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๊ฑธ ์„ ์ ํ˜• ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น(pre-emptive multitasking) ์ด๋ผ๊ณ  ํ•œ๋‹ค. 

 

๋ฐ˜๋ฉด์— asyncio ๋ชจ๋“ˆ์€ ํ˜‘๋ ฅ์‹ ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น(cooperative multitasking) ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ด ๋ฐฉ์‹์€ ๊ฐ ํƒœ์Šคํฌ๊ฐ€ ์ง์ ‘ ์–ธ์ œ ์Šค์œ„์นญ๋  ์ค€๋น„๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ๋ช…์‹œํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ์ด ๋ฐฉ์‹์˜ ์žฅ์ ์€ ์–ธ์ œ ๋‹ค๋ฅธ ํƒœ์Šคํฌ๋กœ ๋„˜์–ด๊ฐˆ์ง€๋ฅผ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ์‚ฌ๋žŒ์ด ์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. 

 

๐Ÿ“Œ ๋ณ‘๋ ฌ ์ž‘์—…์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” Multiprocessing(parallelism)

threading๊ณผ asyncio๋Š” ํ•œ ํ”„๋กœ์„ธ์„œ์—์„œ ์ด๋ฃจ์–ด์ง€๋Š” ๋™์‹œ์„ฑ์ด์—ˆ๋‹ค. multiprocessing ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๋ฉด, ํŒŒ์ด์ฌ์€ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์„œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ์ฆ‰ ์™„์ „ํžˆ ๊ฐ™์€ ์‹œ๊ฐ„์— ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง€๋Š” ๊ฒƒ์ด๋‹ค.


๐Ÿง ๋™์‹œ์„ฑ์˜ ์ข…๋ฅ˜ I/O bound,  CPU bound

I/O bound ์ž‘์—…์˜ ์˜๋ฏธ๋Š” ์ž…์ถœ๋ ฅ ์ž‘์—…์ด ์ฃผ๊ฐ€ ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. I/O bound๋ผ๊ณ  ํ•ด์„œ cpu ์ž‘์—…์ด ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์ฆ‰ I/O waiting ์‹œ๊ฐ„์ด ๋งŽ์€ ๊ฒฝ์šฐ๋‹ค. ํŒŒ์ผ ์“ฐ๊ธฐ, ๋””์Šคํฌ ์ž‘์—…, ๋””๋น„ ์ ‘๊ทผ, ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ํ•  ๋•Œ ์ฃผ๋กœ ๋‚˜ํƒ€๋‚˜๋ฉฐ ์ž‘์—…์— ์˜ํ•œ ๋ณ‘๋ชฉ์— ์˜ํ•ด ์ž‘์—… ์†๋„๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค

 

๊ทธ๋ ‡๋‹ค๋ฉด CPU bound ์ž‘์—…์˜ ์˜๋ฏธ๋Š” I/O bound ์ž‘์—…์˜ ์˜๋ฏธ์™€ ๋ฐ˜๋Œ€๋กœ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. CPU ์ž‘์—…์ด ์ฃผ๋ฅผ ์ด๋ฃจ๋Š” ๊ฒƒ์ด๋‹ค. ์ฃผ๋กœ ์—ฐ์‚ฐ์„ ํ•  ๋•Œ ๋‚˜ํƒ€๋‚˜๋ฉฐ CPU ์„ฑ๋Šฅ์— ์˜ํ•ด ์ž‘์—… ์†๋„๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค.

 

๋”ฐ๋ผ์„œ ์ ์ ˆํ•˜๊ฒŒ ์ž‘์—…์ด I/O ์ค‘์‹ฌ์ธ ์ง€ CPU ์ž‘์—… ์ค‘์‹ฌ์ธ ์ง€ ํŒŒ์•…ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์ด์™€ ๊ด€๋ จํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด๋ณด์•˜๋‹ค. 


๐Ÿ”– FastAPI์™€ Flask ๋น„๊ต๋ฅผ ํ†ตํ•œ I/O bound์™€ CPU bound ์ž‘์—… ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ

๊ธฐ๋ณธ์ ์œผ๋กœ 10๊ฐœ์˜ request๋ฅผ ํ•œ ๋ฒˆ์— ๋ณด๋‚ด๋Š” ์„ค์ •์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค. ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋Š” Apache Jmeter๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

https://mosei.tistory.com/entry/JMeter-%EC%A0%9C%EC%9D%B4%EB%AF%B8%ED%84%B0-%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95

 

[JMeter] ์ œ์ด๋ฏธํ„ฐ ์‚ฌ์šฉ๋ฐฉ๋ฒ•

JMeter๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๋ฐฉ๋ฒ• 1. Java 8 ๋‹ค์šด๋กœ๋“œ ๋ฐ ์„ค์น˜ https://java.com/ko/download/ie_manual.jsp?locale=ko Windows์šฉ Java ๋‹ค์šด๋กœ๋“œ ์‚ฌ์šฉ์ž ์ปดํ“จํ„ฐ์šฉ Java ์†Œํ”„ํŠธ์›จ์–ด ๋˜๋Š” Java Runtime Env..

mosei.tistory.com

 

I/O bound test

ํ…Œ์ŠคํŠธํ•œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋Š” ๋„์ปค๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค. 

 

flask ์ฝ”๋“œ

from flask import Flask
import os
import datetime
import time

@app.route('/test-flask', methods=['GET'])
def test():
    print(f'start-time - {datetime.datetime.now()}')
    time.sleep(10)
    print(f'finish-time - {datetime.datetime.now()}')
    return 'Done!'
    
    
if __name__ == '__main__':
	app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

 

fastAPI ์ฝ”๋“œ

from fastapi import FastAPI
import uvicorn
import os
import datetime
import time

@app.get('/test-fast')
def test():
    print(f'start-time - {datetime.datetime.now()}')
    time.sleep(10)
    print(f'finish-time - {datetime.datetime.now()}')
    return 'Done!'
    
    
 
if __name__ == '__main__':
	uvicorn.run(app, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

 

* Flask - gunicorn ์„ค์ •: worker 1, thread 5 ๊ฐœ์ธ ๊ฒฝ์šฐ *

20์ดˆ๊ฐ€ ์†Œ์š”๋˜์—ˆ๋‹ค. thread๊ฐ€ 5๊ฐœ์ด๊ธฐ ๋•Œ๋ฌธ์— request 5 ๊ฐœ๋ฅผ ์ฒ˜๋ฆฌํ•œ ํ›„ ์ด์ „ request๊ฐ€ ์ข…๋ฃŒ๋˜๋Š” ๋Œ€๋กœ ๋‚˜๋จธ์ง€ 5๊ฐœ์˜ request๊ฐ€ ์‹คํ–‰๋˜์—ˆ๋‹ค. thread๋ฅผ 10๊ฐœ๋กœ ๋Š˜๋ฆฌ๋ฉด 10๊ฐœ์˜ request๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— 11์ดˆ์— ์ข…๋ฃŒ๋œ๋‹ค. ๋กœ๊ทธ์—์„œ ๋ณด๋‹ค์‹œํ”ผ ์ฒ˜์Œ request์˜  start-time 5๊ฐœ๊ฐ€ ์‹คํ–‰์ด ๋˜๊ณ  finsih-time์ด ์ฐํžˆ๋ฉฐ ๋๋‚˜๋ฉด์„œ ๋‹ค๋ฅธ start-time ์ด ์‹œ์ž‘๋˜์—ˆ๋‹ค. 

 

*FastAPI - gunicorn with uvicorn ์„ค์ •: worker 1, thread 5๊ฐœ์ธ ๊ฒฝ์šฐ *

11์ดˆ๊ฐ€ ์†Œ์š”๋˜์—ˆ๋‹ค. fastAPI๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ async def๋ฅผ ์“ฐ์ง€ ์•Š์•„๋„ def๋งŒ์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋˜๋„๋ก FastAPI ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค. ์œ„ ์ž‘์—…์ด ํ˜„์žฌ time.sleep(10) ์„ ์‚ฌ์šฉํ•ด์„œ CPU ๊ฐ€ ์‰ฌ๊ณ  ์žˆ๋Š” I/O bound ์ž‘์—…์ด์–ด์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์—ˆ๊ณ  ์†๋„๋Š” Flask๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ–ˆ์„ ๋•Œ๋ณด๋‹ค ์‹œ๊ฐ„์ด ์ ˆ๋ฐ˜์ด ์ค„์–ด๋“  ๊ฒƒ์ด๋‹ค. thread๋ฅผ 10๊ฐœ๋กœ ๋Š˜๋ ค๋„ 11์ดˆ๋กœ ๋™์ผํ•˜๋‹ค. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ์–ผ๋งˆ๋‚˜ I/O bound ์ž‘์—…์—์„œ ์„ฑ๋Šฅ์„ ๋†’์—ฌ์ฃผ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋กœ๊ทธ์—์„œ ๋ณด๋‹ค์‹œํ”ผ ์œ„์˜ flask๋ฅผ ์‚ฌ์šฉํ•œ ๋กœ๊ทธ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ request์‹œ์ž‘์ธ start-time์ด 10๊ฐœ๊ฐ€ ์ฐํžŒ ํ›„ finish-time 10๊ฐœ๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ์ฐํžŒ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. 

 

 

CPU bound test

์œ„์˜ I/O bound ํ…Œ์ŠคํŠธ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ๊ณ„์‚ฐ์‹์„ ๋„ฃ์–ด์„œ ์—ฐ์‚ฐ ์ž‘์—…์„ ํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค.

 

flask ์ฝ”๋“œ

from flask import Flask
import os
import datetime

@app.route('/test-flask', methods=['GET'])
def test():
    print(f'start-time - {datetime.datetime.now()}')
    num_list = list(range(10000000))
    square_sum = sum(x**2 for x in num_list)
    print(f'finish-time - {datetime.datetime.now()}')
    return 'Done!'
    
    
if __name__ == '__main__':
	app.run(host='0.0.0.0', port=os.environ.get('PORT', 8080)))

 

fastAPI ์ฝ”๋“œ

from fastapi import FastAPI
import uvicorn
import os
import datetime

@app.get('/test-fast')
def test():
    print(f'start-time - {datetime.datetime.now()}')
    num_list = list(range(10000000))
    square_sum = sum(x**2 for x in num_list)
    print(f'finish-time - {datetime.datetime.now()}')
    return 'Done!'
    
    
 
if __name__ == '__main__':
	uvicorn.run(app, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

 

* Flask - gunicorn ์„ค์ •: worker 1, thread 10 ๊ฐœ์ธ ๊ฒฝ์šฐ *

25์ดˆ๊ฐ€ ์†Œ์š”๋˜์—ˆ๋‹ค. thread๊ฐ€ 10๊ฐœ์ด๊ธฐ ๋•Œ๋ฌธ์— 10๊ฐœ์˜ request๋ฅผ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•œ๋‹ค.

* Flask - gunicorn ์„ค์ •: worker 2, thread 5 ๊ฐœ์ธ ๊ฒฝ์šฐ *

worker์˜ ๊ฐœ์ˆ˜๋ฅผ ๋Š˜๋ฆฌ๋‹ˆ thread๋ฅผ ์ค„์—ฌ๋„ ์‹œ๊ฐ„์ด 14์ดˆ๋กœ ์ค„์—ˆ๋‹ค. ์ฆ‰ ์—ฐ์‚ฐ ์ž‘์—…์ธ CPU Bound ์ž‘์—…์—์„œ๋Š” worker์˜ ๊ฐœ์ˆ˜๋ฅผ ๋Š˜๋ ค์„œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ์œ„์˜ ๋กœ๊ทธ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ start-time๊ณผ finsih-time์ด ๋ฒˆ๊ฐˆ์•„ ์ฐํžˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. worker process 2๊ฐœ๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. 

 

๋‹ค์Œ์œผ๋กœ๋Š” fastAPI๋ฅผ ์ด์šฉํ•˜์—ฌ ์ง„ํ–‰ํ•ด๋ณด๊ฒ ๋‹ค.

 

*FastAPI - gunicorn with uvicorn ์„ค์ •: worker 1, thread 10๊ฐœ์ธ ๊ฒฝ์šฐ *

27์ดˆ๊ฐ€ ์†Œ์š”๋˜์—ˆ๋‹ค. ๋น„๋™๊ธฐ ์ž‘์—…์€ CPU ์—ฐ์‚ฐ ์œ„์ฃผ์˜ ์ž‘์—…์„ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— FastAPI๋ฅผ ์ด์šฉํ•ด๋„ Flask์™€ ๋‹ค๋ฅผ ๋ฐ”๊ฐ€ ์—†๋‹ค. 

 

*FastAPI - gunicorn with uvicorn ์„ค์ •: worker 2, thread 5๊ฐœ์ธ ๊ฒฝ์šฐ *

17์ดˆ๊ฐ€ ์†Œ์š”๋˜์—ˆ๋‹ค. ์—ญ์‹œ worker์˜ ๊ฐœ์ˆ˜๋ฅผ ๋Š˜๋ฆฌ๋‹ˆ ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ†ตํ•ด worker๊ฐ€ 1๊ฐœ์˜€์„ ๋•Œ๋ณด๋‹ค ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋นจ๋ผ์กŒ๋‹ค.


๐Ÿ“ ์ •๋ฆฌํ•˜๋ฉด์„œ..

์ด๋กœ์จ ํŒŒ์ด์ฌ์˜ ๋™์‹œ์„ฑ ๊ฐœ๋…์— ๋Œ€ํ•ด ์ข€ ๋” ๋ช…ํ™•ํ•ด์ง„ ๊ฒƒ ๊ฐ™๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ํŒŒ์ด์ฌ์€ GIL(Global Interpreter Lock) ์ •์ฑ…์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ CPU Bound ์ž‘์—…์—๋งŒ ํ•ด๋‹น๋˜๊ณ  I/O Bound ์ž‘์—…์—์„œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ GIL์„ ํ•ด์ œํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํŒŒ์ด์ฌ์—์„œ ์—ฐ์‚ฐ์„ ์ด์šฉํ•ด์„œ ๋™์‹œ์„ฑ์˜ ํšจ๊ณผ๋ฅผ ๋ณด๊ธฐ ์œ„ํ•ด์„œ๋Š” Multiprocessing ์„ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค. 

 

๋‹ค์Œ์—๋Š” CPU bound์ธ ์—ฐ์‚ฐ ์œ„์ฃผ์˜ ์ž‘์—…์—์„œ Multiprocessing ์„ ์ด์šฉํ•˜์—ฌ ์—ฐ์‚ฐ์˜ ์†๋„๋ฅผ ๋†’์ธ ์˜ˆ์ œ๋ฅผ ์ ์–ด๋ด์•ผ๊ฒ ๋‹ค. ์‚ฌ์‹ค ๋™์ผํ•œ ํ…Œ์ŠคํŠธ๋ฅผ M1 mac pro ์™€ Intel mac pro(32GB) ๋กœ Multiprocessing์„ ์ด์šฉํ•˜์ง€ ์•Š๊ณ (Cpu core 1๊ฐœ) ํ…Œ์ŠคํŠธํ–ˆ๋Š”๋ฐ ๋™์ผํ•œ ์—ฐ์‚ฐ์ด M1 mac pro์—์„œ๋Š” 3-4๋ถ„ ๋งŒ์— ๋๋‚ฌ๊ณ  Intel mac pro์—์„œ๋Š” 7-8๋ถ„ ๊ฑธ๋ ธ๋‹ค.

 

์ง„์งœ M1 ์นฉ์ด ์–ผ๋งˆ๋‚˜ ๋Œ€๋‹จํ•œ ์ง€ ํ•œ ๋ฒˆ ๋” ๋Š๊ผˆ๋‹ค...

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€