Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

์•ˆ๋…•ํ•˜์„ธ์š”, Habr! ์ด ๊ธ€์—์„œ๋Š” ๊ธฐ์—… DWH๋‚˜ DataLake ์ธํ”„๋ผ์—์„œ ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐ ์œ ์šฉํ•œ ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜๋ฅผ ์†Œ๊ฐœํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. Apache Airflow(์ดํ•˜ Airflow)์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Habr์—์„œ๋Š” ์ด ๋„๊ตฌ์— ๋Œ€ํ•œ ๊ด€์‹ฌ์ด ๋ถ€์กฑํ•˜์ง€๋งŒ, ๋ณธ ๊ธ€์—์„œ๋Š” ETL/ELT ํ”„๋กœ์„ธ์Šค ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์„ ํƒํ•  ๋•Œ ์ ์–ด๋„ Airflow๋Š” ๊ณ ๋ คํ•ด ๋ณผ ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ๋‹ค๋Š” ์ ์„ ๊ฐ•์กฐํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์ด์ „์— Tinkoff Bank์—์„œ ๊ทผ๋ฌดํ•  ๋•Œ DWH๋ฅผ ์ฃผ์ œ๋กœ ์—ฌ๋Ÿฌ ํŽธ์˜ ๊ธฐ์‚ฌ๋ฅผ ์ผ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ Mail.Ru ๊ทธ๋ฃน ํŒ€์— ํ•ฉ๋ฅ˜ํ•˜์—ฌ ๊ฒŒ์ž„ ๋ถ„์•ผ์˜ ๋ฐ์ดํ„ฐ ๋ถ„์„ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ์†Œ์‹๊ณผ ํฅ๋ฏธ๋กœ์šด ์†”๋ฃจ์…˜์ด ๋‚˜์˜ค๋Š” ๋Œ€๋กœ, ์ €์™€ ์ €ํฌ ํŒ€์€ ์—ฌ๊ธฐ์—์„œ ๋ฐ์ดํ„ฐ ๋ถ„์„ ํ”Œ๋žซํผ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ”„๋กค๋กœ๊ทธ

์ž, ์‹œ์ž‘ํ•ด ๋ณผ๊นŒ์š”. Airflow๋ž€ ๋ฌด์—‡์ผ๊นŒ์š”? ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ธํŠธ) ์›Œํฌํ”Œ๋กœ ๊ฐœ๋ฐœ, ๊ณ„ํš ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. Airflow์˜ ์ฃผ์š” ๊ธฐ๋Šฅ: Python ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ์„ค๋ช…(๊ฐœ๋ฐœ)ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ”„๋กœ์ ํŠธ ๋ฐ ๊ฐœ๋ฐœ ๊ด€๋ฆฌ์— ๋งŽ์€ ์ด์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ (์˜ˆ๋ฅผ ๋“ค์–ด) ETL ํ”„๋กœ์ ํŠธ๋Š” Python ํ”„๋กœ์ ํŠธ์ผ ๋ฟ์ด๋ฉฐ, ์ธํ”„๋ผ ๊ธฐ๋Šฅ, ํŒ€ ๊ทœ๋ชจ ๋ฐ ๊ธฐํƒ€ ์š”๊ตฌ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์—ฌ ์›ํ•˜๋Š” ๋Œ€๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋„๊ตฌ ์ธก๋ฉด์—์„œ๋Š” ๋ชจ๋“  ๊ฒƒ์ด ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด PyCharm + Git์„ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”. ํ›Œ๋ฅญํ•˜๊ณ  ๋งค์šฐ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค!

์ด์ œ Airflow์˜ ์ฃผ์š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ ๋ณธ์งˆ๊ณผ ๋ชฉ์ ์„ ์ดํ•ดํ•˜๋ฉด ํ”„๋กœ์„ธ์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ตœ์ ์œผ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋งˆ๋„ ์ฃผ์š” ์—”ํ‹ฐํ‹ฐ๋Š” ๋ฐฉํ–ฅ์„ฑ ๋น„์ˆœํ™˜ ๊ทธ๋ž˜ํ”„(Directed Acyclic Graph, ์ดํ•˜ DAG)์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

DAG

DAG๋Š” ํŠน์ • ์ผ์ •์— ๋”ฐ๋ผ ์—„๊ฒฉํ•˜๊ฒŒ ์ •์˜๋œ ์ˆœ์„œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•˜๋ ค๋Š” ์ž‘์—…์„ ์˜๋ฏธ์ ์œผ๋กœ ํ†ตํ•ฉํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. Airflow๋Š” DAG ๋ฐ ๊ธฐํƒ€ ์—”ํ‹ฐํ‹ฐ ์ž‘์—…์„ ์œ„ํ•œ ํŽธ๋ฆฌํ•œ ์›น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

DAG๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์ž๋Š” DAG๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ DAG ๋‚ด๋ถ€ ์ž‘์—…์ด ๊ตฌ์ถ•๋  ์—ฐ์‚ฐ์ž ์ง‘ํ•ฉ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋˜ ๋‹ค๋ฅธ ์ค‘์š”ํ•œ ์—”ํ‹ฐํ‹ฐ์ธ Airflow ์—ฐ์‚ฐ์ž๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์—ฐ์‚ฐ์ž

์—ฐ์‚ฐ์ž๋Š” ์ž‘์—… ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ธฐ๋ฐ˜์ด ๋˜๋Š” ์—”ํ‹ฐํ‹ฐ๋กœ, ์ž‘์—… ์ธ์Šคํ„ด์Šค ์‹คํ–‰ ์ค‘์— ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚ ์ง€ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. GitHub์˜ Airflow ๋ฆด๋ฆฌ์Šค ์ด๋ฏธ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์—ฐ์‚ฐ์ž ์ง‘ํ•ฉ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ:

  • BashOperator๋Š” bash ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.
  • PythonOperator๋Š” Python ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.
  • EmailOperator โ€” ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๋Š” ์—ฐ์‚ฐ์ž.
  • HTTPOperator๋Š” http ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.
  • SqlOperator๋Š” SQL ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.
  • ์„ผ์„œ๋Š” ์ด๋ฒคํŠธ(์š”๊ตฌ๋˜๋Š” ์‹œ๊ฐ„์˜ ๋„์ฐฉ, ํ•„์š”ํ•œ ํŒŒ์ผ์˜ ์ถœํ˜„, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ–‰, API์˜ ์‘๋‹ต ๋“ฑ)๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.

๋”์šฑ ๊ตฌ์ฒด์ ์ธ ์—ฐ์‚ฐ์ž๋กœ๋Š” DockerOperator, HiveOperator, S3FileTransferOperator, PrestoToMysqlOperator, SlackOperator๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•„์š”์— ๋”ฐ๋ผ ์—ฐ์‚ฐ์ž๋ฅผ ๊ฐœ๋ฐœํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, MongoDB์—์„œ Hive๋กœ ๋ฌธ์„œ๋ฅผ ๋‚ด๋ณด๋‚ด๋Š” ์—ฐ์‚ฐ์ž์ธ MongoDBToHiveViaHdfsTransfer์™€ Hive์—์„œ ์ž‘์—…ํ•˜๋Š” ์—ฌ๋Ÿฌ ์—ฐ์‚ฐ์ž๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํด๋ฆญ ํ•˜์šฐ์Šค: CHLoadFromHiveOperator์™€ CHTableLoaderOperator. ์‹ค์ œ๋กœ ๊ธฐ๋ณธ ์—ฐ์‚ฐ์ž๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ๊ฐ€ ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€๋˜๋ฉด ์ด๋ฅผ ์ƒˆ๋กœ์šด ์—ฐ์‚ฐ์ž๋กœ ๋ชจ์•„์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ถ”๊ฐ€ ๊ฐœ๋ฐœ์ด ๊ฐ„์†Œํ™”๋˜๊ณ  ํ”„๋กœ์ ํŠธ์˜ ์—ฐ์‚ฐ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ, ์ด๋Ÿฌํ•œ ๋ชจ๋“  ์ž‘์—… ์ธ์Šคํ„ด์Šค๋ฅผ ์™„๋ฃŒํ•ด์•ผ ํ•˜๋ฉฐ, ์ด์ œ ํ”Œ๋ž˜๋„ˆ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์Šค์ผ€์ค„๋Ÿฌ

Airflow์˜ ์ž‘์—… ์Šค์ผ€์ค„๋Ÿฌ๋Š” ๋‹ค์Œ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋ฉ๋‹ˆ๋‹ค. ์…€๋Ÿฌ๋ฆฌ์…€๋Ÿฌ๋ฆฌ๋Š” ํ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์ž‘์—…์˜ ๋น„๋™๊ธฐ ๋ฐ ๋ถ„์‚ฐ ์‹คํ–‰์„ ์ง€์›ํ•˜๋Š” Python ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. Airflow ์ธก์—์„œ๋Š” ๋ชจ๋“  ์ž‘์—…์ด ํ’€๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. ํ’€์€ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ’€์˜ ๋ชฉ์ ์€ ์†Œ์Šค ์ž‘์—… ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ฑฐ๋‚˜ DWH ๋‚ด์—์„œ ์ž‘์—…์„ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ’€์€ ์›น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๊ฐ ํ’€์—๋Š” ์Šฌ๋กฏ ์ˆ˜์— ์ œํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค. DAG๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ’€์ด ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค.

ALERT_MAILS =  Variable.get("gv_mail_admin_dwh")
DAG_NAME = 'dma_load'
OWNER = 'Vasya Pupkin'
DEPENDS_ON_PAST = True
EMAIL_ON_FAILURE = True
EMAIL_ON_RETRY = True
RETRIES = int(Variable.get('gv_dag_retries'))
POOL = 'dma_pool'
PRIORITY_WEIGHT = 10

start_dt = datetime.today() - timedelta(1)
start_dt = datetime(start_dt.year, start_dt.month, start_dt.day)

default_args = {
    'owner': OWNER,
    'depends_on_past': DEPENDS_ON_PAST,
    'start_date': start_dt,
    'email': ALERT_MAILS,
    'email_on_failure': EMAIL_ON_FAILURE,
    'email_on_retry': EMAIL_ON_RETRY,
    'retries': RETRIES,
    'pool': POOL,
    'priority_weight': PRIORITY_WEIGHT
}
dag = DAG(DAG_NAME, default_args=default_args)
dag.doc_md = __doc__

DAG ์ˆ˜์ค€์—์„œ ์ง€์ •๋œ ํ’€์€ ์ž‘์—… ์ˆ˜์ค€์—์„œ ์žฌ์ •์˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ณ„๋„์˜ ํ”„๋กœ์„ธ์Šค์ธ ์Šค์ผ€์ค„๋Ÿฌ๋Š” Airflow์˜ ๋ชจ๋“  ์ž‘์—…์„ ์Šค์ผ€์ค„๋งํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์Šค์ผ€์ค„๋Ÿฌ๋Š” ์ž‘์—… ์‹คํ–‰ ์„ค์ •๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ž‘์—…์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์นฉ๋‹ˆ๋‹ค.

  1. DAG์—์„œ๋Š” ์ด์ „ ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์ƒˆ๋กœ์šด ์ž‘์—…์„ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ๋Œ€๊ธฐ์—ด์€ ์ž‘์—… ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ ์ •๋ ฌ๋˜๋ฉฐ(์šฐ์„ ์ˆœ์œ„๋„ ์ œ์–ด ๊ฐ€๋Šฅ), ํ’€์— ๋นˆ ์Šฌ๋กฏ์ด ์žˆ์œผ๋ฉด ์ž‘์—…์„ ์ž‘์—…์— ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์…€๋Ÿฌ๋ฆฌ ์›Œ์ปค๊ฐ€ ์žˆ์œผ๋ฉด ์ž‘์—…์€ ํ•ด๋‹น ์…€๋Ÿฌ๋ฆฌ ์›Œ์ปค์—๊ฒŒ ์ „๋‹ฌ๋˜๊ณ , ์ž‘์—…์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•œ ์ž‘์—…์€ ํ•œ ๋ช… ๋˜๋Š” ์—ฌ๋Ÿฌ ๋ช…์˜ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

์ถฉ๋ถ„ํžˆ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

์Šค์ผ€์ค„๋Ÿฌ๋Š” ๋ชจ๋“  DAG ์„ธํŠธ์™€ DAG ๋‚ด์˜ ๋ชจ๋“  ์ž‘์—…์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

Scheduler๊ฐ€ DAG์™€ ํ•จ๊ป˜ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๋ ค๋ฉด DAG์— ์ผ์ •์ด ์ง€์ •๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

dag = DAG(DAG_NAME, default_args=default_args, schedule_interval='@hourly')

์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ ์‚ฌ์ „ ์„ค์ • ์„ธํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. @once, @hourly, @daily, @weekly, @monthly, @yearly.

Cron ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

dag = DAG(DAG_NAME, default_args=default_args, schedule_interval='*/10 * * * *')

์‹คํ–‰ ๋‚ ์งœ

Airflow์˜ ์ž‘๋™ ๋ฐฉ์‹์„ ์ดํ•ดํ•˜๋ ค๋ฉด DAG์˜ ์‹คํ–‰ ๋‚ ์งœ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. Airflow์—์„œ DAG๋Š” ์‹คํ–‰ ๋‚ ์งœ ์ฐจ์›์„ ๊ฐ€์ง€๋Š”๋ฐ, ์ด๋Š” DAG์˜ ์ผ์ •์— ๋”ฐ๋ผ ๊ฐ ์‹คํ–‰ ๋‚ ์งœ๋งˆ๋‹ค ์ž‘์—… ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ฐ ์‹คํ–‰ ๋‚ ์งœ๋งˆ๋‹ค ์ž‘์—…์„ ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, DAG๋Š” ์—ฌ๋Ÿฌ ์‹คํ–‰ ๋‚ ์งœ์— ๋™์‹œ์— ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ ๊ทธ๋ฆผ์—์„œ ๋ช…ํ™•ํ•˜๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๋ถˆํ–‰ํžˆ๋„ (ํ˜น์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹คํ–‰์ผ ์ˆ˜๋„ ์žˆ์ง€๋งŒ) DAG์—์„œ ์ž‘์—… ๊ตฌํ˜„์ด ์ˆ˜์ •๋˜๋ฉด ์ด์ „ ์‹คํ–‰ ๋‚ ์งœ์˜ ์‹คํ–‰์— ์ด๋ฏธ ์กฐ์ • ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ƒˆ๋กœ์šด ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ๊ธฐ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ข‹์ง€๋งŒ, ๊ฒฐ๊ณผ์˜ ์žฌํ˜„์„ฑ์ด ์†์‹ค๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (๋ฌผ๋ก  Git์—์„œ ํ•„์š”ํ•œ ๋ฒ„์ „์˜ ์†Œ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ํ•„์š”ํ•œ ์ž‘์—…์„ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์„ ์•„๋ฌด๋„ ๋ง‰์„ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค.)

์ž‘์—… ์ƒ์„ฑ

DAG ๊ตฌํ˜„์€ Python ์ฝ”๋“œ์ด๋ฏ€๋กœ, ์˜ˆ๋ฅผ ๋“ค์–ด ์ƒค๋”ฉ๋œ ์†Œ์Šค ์ž‘์—… ์‹œ ์ฝ”๋“œ ์–‘์„ ๋งค์šฐ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์„ธ ๊ฐœ์˜ MySQL ์ƒค๋“œ๋ฅผ ์†Œ์Šค๋กœ ์‚ฌ์šฉํ•˜๊ณ  ๊ฐ ์ƒค๋“œ์— ์ ‘๊ทผํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ž‘์—…์€ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณ‘๋ ฌ๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. DAG์˜ Python ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

connection_list = lv.get('connection_list')

export_profiles_sql = '''
SELECT
  id,
  user_id,
  nickname,
  gender,
  {{params.shard_id}} as shard_id
FROM profiles
'''

for conn_id in connection_list:
    export_profiles = SqlToHiveViaHdfsTransfer(
        task_id='export_profiles_from_' + conn_id,
        sql=export_profiles_sql,
        hive_table='stg.profiles',
        overwrite=False,
        tmpdir='/data/tmp',
        conn_id=conn_id,
        params={'shard_id': conn_id[-1:], },
        compress=None,
        dag=dag
    )
    export_profiles.set_upstream(exec_truncate_stg)
    export_profiles.set_downstream(load_profiles)

DAG๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์–ป์Šต๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

์„ค์ •์„ ์กฐ์ •ํ•˜๊ณ  DAG๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์ƒค๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽธ๋ฆฌํ•˜์ฃ !

๋” ๋ณต์žกํ•œ ์ฝ”๋“œ ์ƒ์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด DB ํ˜•ํƒœ์˜ ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•˜๊ณ , ํ…Œ์ด๋ธ” ์ž‘์—… ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ •์˜ํ•˜๊ณ , DWH ์ธํ”„๋ผ์˜ ๊ธฐ๋Šฅ์„ ๊ณ ๋ คํ•˜์—ฌ N๊ฐœ์˜ ํ…Œ์ด๋ธ”์„ ์Šคํ† ๋ฆฌ์ง€์— ๋กœ๋“œํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๋ชฉ๋ก ํ˜•ํƒœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ž‘์—…์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์ด ๋ชฉ๋ก์— ๋”ฐ๋ผ DAG(๋‹ค์ค‘ ๊ทธ๋ฃน)์—์„œ N๊ฐœ์˜ ์ž‘์—…์„ ์ƒ์„ฑํ•˜๊ณ , API ์š”์ฒญ์˜ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ํ’€๋กœ ์ œํ•œํ•˜๊ณ , API์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์Šคํฌ๋ž˜ํ•‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ ์—ฐํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์ €์žฅ์†Œ

Airflow๋Š” ์ž์ฒด ๋ฐฑ์—”๋“œ ์ €์žฅ์†Œ์ธ DB(MySQL ๋˜๋Š” Postgres ์ค‘ ํ•˜๋‚˜, ์ €ํฌ๋Š” Postgres๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์—ฌ๊ธฐ์—๋Š” ์ž‘์—… ์ƒํƒœ, DAG, ์—ฐ๊ฒฐ ์„ค์ •, ์ „์—ญ ๋ณ€์ˆ˜ ๋“ฑ์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. Airflow์˜ ์ €์žฅ์†Œ๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜๋ฉฐ(์•ฝ 20๊ฐœ์˜ ํ…Œ์ด๋ธ”) ์ž์ฒด ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” Informatica ์ €์žฅ์†Œ์— 100500๊ฐœ์˜ ํ…Œ์ด๋ธ”์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์œผ๋กœ ๊ธฐ์–ตํ•˜๋Š”๋ฐ, ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— ์ด๋ฅผ ์ดํ•ดํ•˜๋Š” ๋ฐ ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค.

๋ชจ๋‹ˆํ„ฐ๋ง

์ €์žฅ์†Œ์˜ ๋‹จ์ˆœ์„ฑ์„ ๊ณ ๋ คํ•˜๋ฉด, ์‚ฌ์šฉ์ž์—๊ฒŒ ํŽธ๋ฆฌํ•œ ์ž‘์—… ๋ชจ๋‹ˆํ„ฐ๋ง ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Zeppelin์˜ ๋…ธํŠธ๋ถ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—… ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

์ด๋Š” Airflow ์ž์ฒด์˜ ์›น ์ธํ„ฐํŽ˜์ด์Šค์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Airflow๋Š” ์ผ๊ด„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŽธ๋ฆฌํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

Airflow ์ฝ”๋“œ๋Š” ์˜คํ”ˆ ์†Œ์Šค์ด๋ฏ€๋กœ Telegram์— ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰ ์ค‘์ธ ๋ชจ๋“  ์ž‘์—… ์ธ์Šคํ„ด์Šค์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ „์ฒด ๊ฐœ๋ฐœ ๋ฐ ์ง€์›ํŒ€์ด ์ฐธ์—ฌํ•˜๋Š” Telegram ๊ทธ๋ฃน์— ์ŠคํŒธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ๊ฒฝ์šฐ Telegram์„ ํ†ตํ•ด ์‹ ์†ํ•œ ๋‹ต๋ณ€์„ ๋ฐ›๊ณ , Zeppelin์„ ํ†ตํ•ด Airflow์˜ ์ „๋ฐ˜์ ์ธ ์ž‘์—… ์ƒํ™ฉ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ „์ฒด๋กœ

Airflow๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์˜คํ”ˆ์†Œ์Šค์ด๋ฏ€๋กœ ๊ธฐ์ ์„ ๊ธฐ๋Œ€ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š” ์†”๋ฃจ์…˜์„ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด ์‹œ๊ฐ„๊ณผ ๋…ธ๋ ฅ์„ ํˆฌ์žํ•  ๊ฐ์˜ค๋ฅผ ํ•˜์„ธ์š”. ๋ชฉํ‘œ๋Š” ๋‹ฌ์„ฑ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ฏฟ์–ด ์˜์‹ฌ์น˜ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์†๋„, ์œ ์—ฐ์„ฑ, ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค ์ถ”๊ฐ€์˜ ์šฉ์ด์„ฑ ๋“ฑ Airflow์˜ ์žฅ์ ์ด ๋ถ„๋ช… ๋งˆ์Œ์— ๋“œ์‹ค ๊ฒ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ๊ณผ Airflow ์ž์ฒด์˜ ์•ˆ์ •์„ฑ์—๋„ ๋งŽ์€ ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์ ์€ ์ผ์–ด๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ Airflow๋Š” ๋งค์ผ ์‹คํ–‰๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์•ฝ 6,5์ฒœ ๊ฐœ์˜ ์ž‘์—…. ์ด ๋‘˜์€ ๋ณธ์งˆ์ ์œผ๋กœ ๋งค์šฐ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•˜๊ณ  ๋งค์šฐ ๊ตฌ์ฒด์ ์ธ ์†Œ์Šค์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”์ธ DWH๋กœ ๋กœ๋“œํ•˜๋Š” ์ž‘์—…, ๋ฉ”์ธ DWH ๋‚ด๋ถ€์—์„œ ์‡ผ์ผ€์ด์Šค๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ์ž‘์—…, ๋น ๋ฅธ DWH์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒŒ์‹œํ•˜๋Š” ์ž‘์—… ๋“ฑ ๋งค์šฐ ๋‹ค์–‘ํ•œ ์ž‘์—…๋“ค์ด ์žˆ๋Š”๋ฐ, Airflow๋Š” ์ด ๋ชจ๋“  ์ž‘์—…์„ ๋งค์ผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ˆซ์ž๋กœ ์ด์•ผ๊ธฐํ•˜์ž๋ฉด, 2,3 ์ฒœ๋ช… DWH(Hadoop) ๋‚ด์—์„œ ๋‹ค์–‘ํ•œ ๋ณต์žก์„ฑ์„ ์ง€๋‹Œ ์ž‘์—…์˜ ELT, ๋Œ€๋žต 2,5๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ถœ์ฒ˜์— ๋”ฐ๋ฅด๋ฉด, ์ด๊ฒƒ์€ ํŒ€์ž…๋‹ˆ๋‹ค 4๋ช…์˜ ETL ๊ฐœ๋ฐœ์žDWH ๋‚ด๋ถ€์—์„œ์˜ ETL ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์™€ DWH ๋‚ด๋ถ€์—์„œ์˜ ELT ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋กœ ๊ตฌ๋ถ„๋˜๋ฉฐ ๋ฌผ๋ก  ๋” ๋งŽ์€ ํ•œ ๋ช…์˜ ๊ด€๋ฆฌ์ž์„œ๋น„์Šค ์ธํ”„๋ผ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๊ธฐ๊ด€์ž…๋‹ˆ๋‹ค.

์•ž์œผ๋กœ์˜ ๊ณ„ํš

ํ”„๋กœ์„ธ์Šค ์ˆ˜๋Š” ํ•„์—ฐ์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๋ฉฐ, Airflow ์ธํ”„๋ผ ์ธก๋ฉด์—์„œ ์šฐ๋ฆฌ๊ฐ€ ํ•  ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ผ์€ ํ™•์žฅ์ž…๋‹ˆ๋‹ค. Airflow ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ , Celery Worker๋ฅผ ์œ„ํ•œ ๋ ˆ๊ทธ(leg)๋ฅผ ํ• ๋‹นํ•˜๊ณ , ์ž‘์—… ์Šค์ผ€์ค„๋ง ํ”„๋กœ์„ธ์Šค์™€ ์ €์žฅ์†Œ๋ฅผ ๊ฐ–์ถ˜ ๋ณต์ œ ํ—ค๋“œ(duplicate head)๋ฅผ ๋งŒ๋“ค๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์ปคํŠผ์ฝœ

๋ฌผ๋ก , ์ด๊ฒŒ ์ œ๊ฐ€ Airflow์— ๋Œ€ํ•ด ๋ง์”€๋“œ๋ฆฌ๊ณ  ์‹ถ์€ ์ „๋ถ€๋Š” ์•„๋‹ˆ์ง€๋งŒ, ํ•ต์‹ฌ๋งŒ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹์š•์€ ๋จน์œผ๋ฉด ๋”ฐ๋ผ์˜ค๋Š” ๋ฒ•์ด์ฃ . ํ•œ๋ฒˆ ๋“œ์…” ๋ณด์„ธ์š”. ๋ถ„๋ช… ๋งˆ์Œ์— ๋“œ์‹ค ๊ฑฐ์˜ˆ์š” ๐Ÿ™‚

์ถœ์ฒ˜ : habr.com

DDoS ๋ณดํ˜ธ, VPS VDS ์„œ๋ฒ„๊ฐ€ ์žˆ๋Š” ์‚ฌ์ดํŠธ๋ฅผ ์œ„ํ•œ ์•ˆ์ •์ ์ธ ํ˜ธ์ŠคํŒ… ๊ตฌ์ž… ๐Ÿ”ฅ DDoS ๊ณต๊ฒฉ ๋ฐฉ์ง€ ๊ธฐ๋Šฅ์ด ํƒ‘์žฌ๋œ ์•ˆ์ •์ ์ธ ์›น์‚ฌ์ดํŠธ ํ˜ธ์ŠคํŒ…, VPS ๋ฐ VDS ์„œ๋ฒ„๋ฅผ ๊ตฌ๋งคํ•˜์„ธ์š” | ProHoster