P
PipeCraft

Roadmap Progress

ข้อมูลเบื้องต้นเกี่ยวกับวิศวกรรมข้อมูล (Introduction to Data Engineering) Python สำหรับ Data Engineering พื้นฐาน Linux & CLI สำหรับวิศวกรข้อมูล Git สำหรับระบบงานข้อมูลและทีมพัฒนา ระบบเครือข่ายและระบบประมวลผลแบบกระจายศูนย์ (Networking & Distributed Systems) SQL ขั้นสูง (Window Functions & Optimization) ฐานข้อมูลไม่ใช่เชิงสัมพันธ์ (NoSQL Databases & Modern Stores) การออกแบบโครงสร้างข้อมูล (Star/Snowflake & SCD) โครงสร้างพื้นฐานเครือข่ายระบบคลาวด์ (Cloud-based Networking) สถาปัตยกรรม Data Lakehouse (Apache Iceberg & MinIO) Local-first Data Engineering ด้วย DuckDB โครงสร้างพื้นฐานในรูปแบบโค้ด (IaC ด้วย Terraform) การแปลงข้อมูลระดับโปรด้วย dbt-core ข้อตกลงร่วมด้านข้อมูล (Data Contracts) การบริการและการส่งต่อข้อมูลวิเคราะห์ (Data Serving & Reverse ETL) การประมวลผลข้อมูลขนาดใหญ่แบบกระจายด้วย Apache Spark ระบบจัดการเวิร์กโฟลว์ข้อมูล (Airflow, Dagster & Prefect) การประมวลผลข้อมูลแบบเรียลไทม์ด้วย Kafka & Redpanda การจัดการคอนเทนเนอร์และคลัสเตอร์ (Containers & Kubernetes) ระบบ CI/CD และการเฝ้าระวังคุณภาพข้อมูล (CI/CD, Monitoring & Testing) ระบบความปลอดภัยและการควบคุมข้อมูล (Security, Governance & Privacy) ระบบปฏิบัติการและประมวลผลโมเดล (Machine Learning & MLOps)
บทที่ 5: ระบบประมวลผลขนาดใหญ่และการจัดลำดับงาน (Orchestration, Scale & Streaming)

ระบบจัดการเวิร์กโฟลว์ข้อมูล (Airflow, Dagster & Prefect)

5.2 workflow Orchestration ด้วย Apache Airflow

ระบบจัดการสายงาน (Workflow Orchestration) ทำหน้าที่สแตนด์บายประสานเวลารันและลำดับขั้นจราจรของท่อส่งข้อมูลหลากหลายแบบให้เป็นไปตามเงื่อนไขอย่างราบรื่น

1. โครงสร้างภายใน Scheduler, Database Backend, และรอบการคัดกรองงานของ Executors

ทฤษฎีและกลไกการทำงาน (How it works): Apache Airflow ประกอบด้วยแกนทำงานหลัก: - **Scheduler**: สแกนไดเรกทอรีไฟล์รหัส DAG เพื่อถอดสูตร และทำการบันทึกจัดเก็บประวัติสถานะลงใน **Metadata Database (เช่น PostgreSQL)** เพื่อค้นหา Task ที่พร้อมรัน จากนั้นยิงคำสั่งไปต่อคิวในระบบข้อความ (Redis/RabbitMQ) - **Executor (เช่น Celery, Kubernetes)**: ดึงคำสั่งจากคิวข้อความส่งต่อกระจายให้ **Workers** ที่มีหน้าที่เปิดรันโพรเซสทำงานจริง

# Conceptual architecture of Airflow execution loop
[DAG Definitions] -> [Scheduler Engine] <-> [PostgreSQL Metadata]
                               |
                        (Pushes to Queue)
                               |
                               v
                       [Celery/K8s Queue] -> [Worker Node 1 / Pod 1]
                    

Use Case ในชีวิตจริง (Real-world Scenario): สายงานการสกัดข้อมูลบัตรเครดิตลูกค้าที่ต้องคอยรอบรันทุกคืน ตัว Scheduler จะคอยเฝ้าเช็คประวัติใน PostgreSQL และเมื่อถึงเวลารันตีหนึ่งตรง จะสั่งจอง Workers เพื่อเปิดเครื่องประมวลผลทันที

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): สภาวะ **Database Bottleneck** เมื่อ Scheduler สแกนโฟลเดอร์ DAG ถี่เกินไปและยิงล็อกฐานข้อมูล PostgreSQL พังเสียหาย แก้ไขโดยตั้งค่าขยายเวลารอบสแกนไฟล์ `min_file_process_interval` ในไฟล์ config เพื่อลดภาระการเขียนอ่านลงคลังสเตท

2. การออกแบบ DAG ให้มีคุณสมบัติทำงานซ้ำได้ผลลัพธ์คงเดิมและการย้อนเวลารันจ๊อบย้อนหลัง (Idempotency & Backfilling)

ทฤษฎีและกลไกการทำงาน (How it works): ท่อส่งที่ดีต้องมีคุณสมบัติ **Idempotent** หมายความว่ารันโค้ดประมวลผลข้อมูลกี่สิบรอบด้วยพารามิเตอร์วันเดิม จะต้องได้ผลลัพธ์ปลายทางคงเดิมเสมอ ห้ามเกิดปัญหายอดข้อมูลซ้อนหรือผลลัพธ์งอกเพิ่ม นอกจากนี้ Airflow รองรับระบบ **Backfill** โดยเราสามารถระบุสั่งรันย้อนเวลาตามประวัติวันเวลารัน (Execution Date) ย้อนหลังแบบอัตโนมัติได้เมื่อต้องการเก็บข้อมูลย้อนหลังแก้โครงสร้างตาราง

# Idempotent Airflow DAG with logical execution date filters
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime

with DAG(
    "idempotent_etl_pipeline",
    start_date=datetime(2026, 7, 1),
    schedule_interval="@daily",
    catchup=False # Prevents automatic run of unexecuted past DAG runs
) as dag:
    
    # Process only partition corresponding to the execution date using Jinja templates
    process_daily_data = BashOperator(
        task_id="run_pipeline_partition",
        bash_command="python3 /opt/etl.py --date {{ ds }}"
    )
                    

Use Case ในชีวิตจริง (Real-world Scenario): ระบบวิเคราะห์ยอดขายพบคิวรีล่มในประวัติ 5 วันที่แล้ว วิศวกรข้อมูลสามารถสั่งคำสั่งรันแก้สเปคสคริปต์ และทำ Backfill เฉพาะข้อมูลพาร์ทิชัน 5 วันนั้นใหม่ได้อย่างปลอดภัยโดยไม่ทำลายโครงสร้างตารางวันอื่น

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การกำหนดวันเวลารันโดยดึงค่าโมดูลเวลาปัจจุบัน `datetime.now()` ในเนื้อโค้ดประมวลผลโดยตรง ซึ่งจะทำให้คุณสมบัติ Idempotency และระบบ Backfill ใช้งานไม่ได้ แก้ไขโดยใช้ตัวแปรแฝงระบบของ Airflow `{{ ds }}` (Logical Date) เสมอ

3. การคัดแยกความปลอดภัยในกระบวนการทำงาน: BashOperator vs. KubernetesPodOperator

ทฤษฎีและกลไกการทำงาน (How it works): ในการเรียกใช้งาน Task ย่อยของสคริปต์ประมวลผลข้อมูล: - **BashOperator**: รันสคริปต์ในสภาพแวดล้อมระบบปฏิบัติการเดียวกับเซิร์ฟเวอร์ Airflow Worker ตรงๆ มีความยืดหยุ่นต่ำ เสี่ยงต่อปัญหาไลบรารีโค้ดขัดแย้งกันข้าม Task - **KubernetesPodOperator (KPOs)**: สั่งสร้างกล่องคอนเทนเนอร์ Pod แยกต่างหากบนคลัสเตอร์ Kubernetes มั่นคงปลอดภัย แยกสิทธิ์ใช้งานแรมและ CPU อย่างเป็นสัดส่วน และช่วยคัดแยกความเสี่ยงของรหัสหลุดล่มออกไปโดยสิ้นเชิง

# Declaring a secure task using KubernetesPodOperator
from airflow.providers.cncf.kubernetes.operators.pod import KubernetesPodOperator

run_spark_container = KubernetesPodOperator(
    task_id="secure_data_extraction",
    name="data-extract-pod",
    namespace="airflow-tasks",
    image="pipecraft/etl-extractor:v2.1",
    cmds=["python", "extract.py"],
    arguments=["--target-partition", "{{ ds }}"],
    get_logs=True
)
                    

Use Case ในชีวิตจริง (Real-world Scenario): ท่อส่งหนึ่งใช้ Python 3.9 ประมวลผลข้อมูล ขณะที่อีกท่อใช้ Python 3.11 รันประมวลผล MLOps การใช้งาน KPOs ช่วยให้ทั้งคู่สามารถรันงานพร้อมกันบนโหนดเดียวกันได้โดยไร้ปัญหา Dependency ขัดแย้งกัน

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การเริ่มต้นเปิดคอนเทนเนอร์ Pod ใหม่ในทุก Task ย่อยสร้างความล่าช้า (Pod startup latency overhead) แก้ไขโดยหลีกเลี่ยง KPOs กับงานประเภทประมวลผลขนาดสั้นต่ำกว่าวิ (Sub-second tasks)

4. การจัดการความสัมพันธ์ข้ามสายการรัน (Cross-DAG Dependencies & Triggers)

ทฤษฎีและกลไกการทำงาน (How it works): ในแพลตฟอร์มระดับองค์กร ท่อส่งข้อมูลมักไม่ได้รันจบอยู่ในไฟล์เดียว แต่ต้องแยกสายงานออกจากกัน เช่น ท่อโหลดข้อมูลลูกค้ารันเสร็จก่อน จึงค่อยสั่งรันสายงานคำนวณเงินปันผล Airflow สนับสนุนการเชื่อมโยงข้ามไฟล์ผ่านเครื่องมือ **TriggerDagRunOperator** หรือเฝ้ารอคอยความสมบูรณ์ผ่าน **ExternalTaskSensor**

# Triggering another pipeline file using TriggerDagRunOperator
from airflow.operators.trigger_dagrun import TriggerDagRunOperator

trigger_reporting_pipeline = TriggerDagRunOperator(
    task_id="trigger_downstream_report",
    trigger_dag_id="financial_reporting_dashboard_pipeline",
    execution_date="{{ execution_date }}",
    reset_dag_run=True,
    wait_for_completion=False
)
                    

Use Case ในชีวิตจริง (Real-world Scenario): ทีมวิจัยและทีมการตลาดแยกกันดูแลจ๊อบการทำงานของระบบตนเอง เมื่อทีมข้อมูลหลักโหลดประวัติลูกค้าเข้ามาเสร็จสิ้น จึงจะยิงสัญญาณปลุกท่อวิจัยวิเคราะห์ MLOps ให้ตื่นขึ้นมารันคำนวณแคมเปญโฆษณา

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): ปัญหาวงจรรันระบบรอสายค้างคา (Deadlocks) เมื่อ Sensor เฝ้ารอคอยสายงานอื่นนานเกินไปแย่งโควต้า Workers รันค้างเต็มระบบ แก้ไขโดยตั้งค่าโหมดประหยัดทรัพยากร `mode='reschedule'` เพื่อให้ Sensor ปล่อยแรม Workers ไปทำจ๊อบอื่นระหว่างคอยตรวจเช็ครอบใหม่

5. กลไกกู้ภัยสายงานล้มเหลว: การเซ็ตอัป Retries, Backoff, และ Custom Failure Callbacks

ทฤษฎีและกลไกการทำงาน (How it works): เพื่อเสถียรภาพสูงสุดในการให้บริการ Airflow จัดเตรียมคุณสมบัติช่วยแก้ปัญหาระบบหลุดชั่วคราว: - **Retries**: ตั้งค่าจำนวนรอบแก้ตัวใหม่เมื่อภารกิจพังเสียหาย - **Retry Delay / Exponential Backoff**: หน่วงเวลาก่อนสู้ใหม่เป็นทวีคูณเพื่อรอลดปัญหาเน็ตเวิร์กหนาแน่น - **Failure Callbacks**: ฟังก์ชันตรวจจับเออเร่อ เพื่อสั่งยิงสัญญาณด่วนดักจับรหัสข้อผิดพลาดแจ้งวิศวกรผ่าน Slack Webhook ทันที

# Retries configuration template with Custom Failure Callback
def notify_incident_slack(context):
    exception = context.get("exception")
    task_id = context.get("task_instance").task_id
    # Logic to send Slack alert payload with error detail
    print(f"Incident: Task {task_id} failed with error: {exception}")

default_args = {
    "owner": "data_ops",
    "retries": 3,
    "retry_delay": timedelta(minutes=5),
    "retry_exponential_backoff": True,
    "on_failure_callback": notify_incident_slack
}
                    

Use Case ในชีวิตจริง (Real-world Scenario): ท่อส่งโหลด API อัตราแลกเปลี่ยนเงินต่างประเทศล้มเหลวเนื่องจากเซิร์ฟเวอร์ธนาคารต้นทางขัดข้องชั่วขณะ การตั้ง Retry Exponential Backoff ช่วยให้ระบบพักรอ 5 นาทีและกลับไปดึงใหม่จนผ่านได้โดยไม่ต้องเรียกพนักงานตามแก้หน้างานกลางดึก

ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การแจ้งเตือนความผิดพลาดแบบพร่ำเพรื่อ (Alert Fatigue) จาก Task ย่อยที่ไม่สำคัญพังบ่อย แก้ไขโดยเปิดใช้งานการส่งเสียงเตือนเฉพาะ Task สำคัญระดับคอขวด (Critical Path End-nodes) เท่านั้น

🛠️ Weekend Sandbox Challenge: Build an Idempotent DAG with Slack Notification

โจทย์ปฏิบัติการ: จงเขียนไฟล์ Airflow DAG กำหนดความเสถียรของงาน โดยตั้งค่า `catchup=False`, การรันแก้ตัวใหม่ (Retries) และฟังก์ชันส่งข้อความเตือนเมื่อจ๊อบพังล้มเหลว (Failure Callback)

# local_airflow_alert_dag.py
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime, timedelta

def alert_failure(context):
    print(f"Slack Notification: Task {context.get('task_instance').task_id} failed!")

default_args = {
    'owner': 'data_ops',
    'retries': 2,
    'retry_delay': timedelta(seconds=30),
    'on_failure_callback': alert_failure
}

with DAG(
    'monitored_sales_pipeline',
    default_args=default_args,
    start_date=datetime(2026, 7, 1),
    schedule_interval='@daily',
    catchup=False
) as dag:
    run_etl = BashOperator(
        task_id='execute_etl_script',
        bash_command='python3 /opt/pipecraft/run_etl.py --date {{ ds }}'
    )
                    

💼 Senior Technical Interview Q&A

Q1: เพราะเหตุใดท่อส่งข้อมูลที่ดีต้องยึดมั่นคุณสมบัติการรันซ้ำได้ผลเดิม (Idempotency) และควรหลีกเลี่ยงการใช้คำสั่ง `datetime.now()` ใน DAG อย่างไร?

A1: คุณสมบัติ Idempotency การันตีว่าไม่ว่าจะสั่งรันจ๊อบย้อนหลังกี่ครั้งด้วยพารามิเตอร์วันเดิม ข้อมูลในตารางปลายทางจะต้องไม่บวมซ้ำและถูกต้องเท่าเดิม การใช้ `datetime.now()` จะดึงเวลาจริงของระบบขณะกดปุ่มรัน ทำให้ประวัติพาร์ทิชันผิดเพี้ยนไปจากวันเวลาจริงของรอบธุรกรรมนั้น แก้ไขโดยใช้ตัวแปรแฝงระบบของ Airflow `{{ ds }}` เสมอ

Q2: จงวิเคราะห์ความแตกต่างของขอบเขตทรัพยากรระหว่างการรันโปรเซสด้วย CeleryExecutor และ KubernetesExecutor ในระบบ Airflow ขนาดใหญ่?

A2: CeleryExecutor ต้องการเปิดเซิร์ฟเวอร์ Workers ทิ้งไว้สแตนด์บายตลอดเวลา (Static resources pool) มีข้อจำกัดในการสเกลเครื่องและเสี่ยงต่อปัญหาไลบรารีโค้ดขัดแย้งข้ามจ๊อบ ในขณะที่ KubernetesExecutor จะสั่งเปิด Pod ใหม่ขนานบนคลัสเตอร์ขึ้นมารันงานรายจ๊อบย่อยเฉพาะกิจและทำลายโหนดทิ้งทันทีเมื่อจบงาน (Dynamic auto-scaling) คัดแยกแรม CPU ปลอดภัย

⏱️ Interactive DAG Workflow Orchestrator

Interactive DAG

เป้าหมาย: กำหนดลำดับการทำงาน (Dependencies) ของเวิร์กโฟลว์ข้อมูลให้ถูกต้อง โดยงาน Clean จะทำงานหลังจาก Extract สำเร็จ และงาน Load จะทำงานหลังจาก Clean สำเร็จ

extract_data Idle
clean_data Idle
load_warehouse Idle
🐍 เลือกคำสั่งจับคู่ทิศทางเวิร์กโฟลว์ (Task Flow Operators):
extract_data clean_data load_warehouse

ข้อดี / จุดเด่น & ข้อเสีย / ข้อควรระวัง

ข้อดี / จุดเด่น

  • สามารถจัดการลำดับความเชื่อมโยงของงานที่สลับซับซ้อนได้อย่างน่าเชื่อถือ
  • มีระบบ User Interface คอยมอนิเตอร์และสั่งทำงานย้อนหลัง (Backfilling) ได้โดยง่าย

ข้อเสีย / ข้อควรระวัง

  • ต้องการทรัพยากรจัดเตรียมระบบสูง (ฐานข้อมูล Metadata, Web Server, Scheduler)
  • มีความยุ่งยากในการพัฒนาและรันชุดทดสอบความถูกต้องของสคริปต์ที่เครื่องตนเอง

ปฏิบัติการจริง (Lab Practice)

Bilingual Guide

แล็บ: สร้างและรันสคริปต์ควบคุม DAG สำหรับท่อส่งข้อมูล

💡 คำแนะนำ & ทริกเด็ด

ห้ามใช้โหนด Worker ของ Airflow ในการคำนวณหรือดึงข้อมูลปริมาณมากตรงๆ ให้ใช้คำสั่ง Operators ไปสั่งรันงานหนักภายนอก (เช่น KubernetesPodOperator หรือ BigQueryInsertJobOperator) แทน เพื่อไม่ให้ Airflow Scheduler แฮงก์