การแปลงข้อมูลระดับโปรด้วย dbt-core
4.1 กระบวนการเขียนตารางข้อมูลวิเคราะห์ด้วย dbt-core
dbt (Data Build Tool) ทำหน้าที่เป็นแกนหลักในส่วนการแปลงข้อมูล (Transform) ของท่อส่งข้อมูล โดยทำหน้าที่คอมไพล์ความสัมพันธ์โมเดล (DAG) และนำโครงสร้างซอฟต์แวร์ เช่น ยูนิตเทส การควบคุมเวอร์ชัน และการตรวจสอบสกีมา มาจัดการในชั้นข้อมูล SQL
1. วิศวกรรมข้อมูลวิเคราะห์และการคอมไพล์กราฟแสดงความสัมพันธ์ (DAG Compilation)
ทฤษฎีและกลไกการทำงาน (How it works): dbt-core ทำงานในฐานะคลังเก็บโค้ด SQL ที่จัดรูปเป็นแบบระบุปลายทาง (Declarative) โดยใช้คำสั่งระบุความสัมพันธ์เป้าหมายด้วยคำฟังก์ชัน `ref()` และ `source()`. เมื่อสั่งรันคำสั่ง ตัว dbt จะสแกนโฟลเดอร์ไฟล์โมเดลทั้งหมดเพื่อทำการสร้างและคอมไพล์ **Directed Acyclic Graph (DAG)** เพื่อแสดงความสัมพันธ์การไหลของตารางข้อมูล ก่อนจะสร้างคำสั่ง DDL (Data Definition Language) ยิงไปประมวลผลบนคลังข้อมูลปลายทางโดยตรงตามลำดับชั้นความสัมพันธ์ที่ถูกต้อง
-- Defining dependencies inside dbt models using Jinja functions
-- models/marts/fct_weekly_sales.sql
WITH daily_orders AS (
SELECT * FROM {{ ref('stg_orders') }} -- Tells dbt compiler to compute stg_orders first
),
customer_details AS (
SELECT * FROM {{ ref('dim_customers') }}
)
SELECT ...
Use Case ในชีวิตจริง (Real-world Scenario): คลังข้อมูลของบริษัทประกันภัยที่มีโมดูล SQL เชื่อมต่อกันกว่า 500 ตาราง การเปลี่ยนโครงสร้างฟิลด์ในตารางฐานล่าง dbt ช่วยระบุความเกี่ยวข้องข้ามสายและไล่แปลงค่าตารางในชั้นถัดไปตามลำดับโดยไม่สร้างความขัดแย้งเชิงโครงสร้าง
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การเกิดโครงสร้างความสัมพันธ์แบบลูปวงกลม (Circular Dependency เช่น ตาราง A เรียก B และ B เรียก A) ซึ่งจะทำให้คอมไพเลอร์ของ dbt รันไม่ผ่าน แก้ไขโดยถอดถอนตรรกะประมวลผลย่อยออกมาสร้างตารางสะพานตัวกลาง (Bridge model) เพื่อตัดการเชื่อมโยงวงกลม
2. ยุทธวิธีการบันทึกตารางทางกายภาพ (Materialization Strategies)
ทฤษฎีและกลไกการทำงาน (How it works): dbt สนับสนุนการทำ Materializations ซึ่งคือการเลือกแปลงรหัส SQL ไปจัดเก็บเป็นสเตทจริงบนฐานข้อมูล 4 รูปแบบ: 1. **View**: สร้างวิวคิวรีจำลองขึ้นมา ไม่เปลืองพื้นที่ดิสก์ แต่จะคำนวณใหม่ทุกครั้งที่มีการเรียกสืบค้นข้อมูล 2. **Table**: บิวด์สร้างตารางดิสก์จริงเต็มรูปแบบ โดยจะรันคำสั่งลบและบิวด์ตารางใหม่ทั้งหมดในทุกครั้งที่รัน (Drop and Recreate) ปลอดภัยต่อข้อมูลนิ่งแต่กินเวลาการประมวลผลสูง 3. **Ephemeral**: แปลงโมเดลเป็นโครงสร้างย่อย (Common Table Expression - CTE) ไปสอดแทรกในโมเดลถัดไปแทนการเขียนลงฐานข้อมูล 4. **Incremental**: ประมวลผลและแทรกเฉพาะข้อมูลพาร์ทิชันแถวใหม่ที่เพิ่มขึ้น ช่วยเซฟเวลาและงบประมาณการดึงข้อมูล OLAP คลาวด์ได้สูงสุด
# Configuration inside dbt model block defining materialization type
# models/staging/stg_orders.sql
{{ config(
materialized='table',
indexes=[{'columns': ['order_id'], 'type': 'btree'}]
) }}
Use Case ในชีวิตจริง (Real-world Scenario): การนำข้อมูลดิบล็อกการกดเว็บระดับหลายกิกะไบต์มาทรานฟอร์ม วิศวกรข้อมูลเลือกใช้โมเดลประเภท `incremental` รันงานอัปเดตสรุปพฤติกรรมลูกค้าทุกชั่วโมง เพื่อเลี่ยงภาระค่าประมวลผลจากการที่ต้องรันสแกนข้อมูลเดิมซ้ำไปมา
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การรันโมเดลแบบ Table บนฐานข้อมูลขนาดใหญ่เป็นร้อยล้านแถวทำให้เกิดความล่าช้าและเปลืองค่าคอมพิวต์คลาวด์ แก้ไขโดยปรับเปลี่ยนสเปคโมเดลเหล่านั้นให้มาใช้กลไก `incremental` โดยกรองดักเฉพาะฟิลด์แก้ไขล่าสุด
3. กลไกยุทธวิธีประมวลผลส่วนต่าง (Incremental Models - Append-Only & Merge)
ทฤษฎีและกลไกการทำงาน (How it works): ในโหมดการรันแบบส่วนต่าง dbt จะประยุกต์ใช้งาน 2 รูปแบบย่อยเพื่อซิงก์ข้อมูลเข้าตารางเป้าหมาย: - **Append-only**: นำระเบียนข้อมูลใหม่ไปเขียนต่อท้ายตารางโดยตรงอย่างรวดเร็ว เหมาะสำหรับตารางล็อกเหตุการณ์ที่ไม่เคยมีการแก้ไขย้อนหลัง - **Merge**: นำคีย์หลัก (Unique Key) ของข้อมูลชุดใหม่มาเทียบชนกับตารางหลัก หากคีย์ตรงกันจะสั่งอัปเดตฟิลด์ (Update) หากไม่เจอจะสั่งแทรกแถวใหม่ (Insert) ช่วยรักษาความสะอาดไม่ให้เกิดรายการแถวซ้ำซ้อน
-- Incremental configuration using merge strategy
-- models/marts/fct_orders.sql
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge'
) }}
SELECT * FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
-- Filter records updated within the last lookback window
WHERE order_updated_at >= (SELECT MAX(order_updated_at) FROM {{ this }})
{% endif %}
Use Case ในชีวิตจริง (Real-world Scenario): ตารางประวัติยอดขายสินค้าของระบบอีคอมเมิร์ซที่สถานะคำสั่งซื้อสามารถเปลี่ยนจาก "กำลังจัดส่ง" ไปเป็น "จัดส่งสำเร็จ" การใช้ Merge Strategy ในโหมด Incremental ช่วยอัปเดตสถานะของออร์เดอร์เดิมได้อย่างถูกต้องครบถ้วน
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): ความผิดพลาดของผลรวมยอดในตารางปลายทางหากมีข้อมูลเก่าที่ยกเลิกไปแล้วไม่ถูกตามไปล้างทิ้ง แก้ไขโดยเปิดใช้งานคำสั่งรันแบบล้างประวัติใหม่บางส่วน (Full refresh rebuild) สัปดาห์ละครั้ง
4. การจัดการข้อมูลตกหล่นหรือมาสายด้วย Lookback Windows ใน SQL
ทฤษฎีและกลไกการทำงาน (How it works): ปัญหาคลาสสิกของท่อส่งแบบส่วนต่างคือ **Late-arriving Data** ข้อมูลเกิดเมื่อวานแต่เพิ่งส่งมาถึงคลังวันนี้เนื่องจากระบบเน็ตเวิร์กของแอปพลิเคชันสะดุด หากเรากรองดึงเฉพาะข้อมูลที่เกิดขึ้นหลังเวลาสูงสุดปัจจุบันของตาราง ข้อมูลที่มาสายกลุ่มนี้จะหลุดรอดการประมวลผลถาวร วิธีแก้ไขคือการเขียนตรรกะเผื่อเวลาคิวรี่ย้อนหลังเรียกว่า **Lookback Window** โดยขยับเวลาเริ่มต้นการดึงข้อมูลย้อนหลังไปเผื่อ เช่น 3 วันล่าสุดเสมอ และพึ่งพา Merge strategy เพื่อลบอัปเดตแถวเก่าซ้ำซ้อน
-- Implementing a 3-day lookback window inside dbt incremental logic
SELECT * FROM {{ ref('stg_events') }}
{% if is_incremental() %}
-- Scan last 3 days to capture delayed telemetry events
WHERE event_timestamp >= (SELECT DATEADD(day, -3, MAX(event_timestamp)) FROM {{ this }})
{% endif %}
Use Case ในชีวิตจริง (Real-world Scenario): แอปพลิเคชันมือถือออฟไลน์ของลูกค้าที่จดบันทึกยอดใช้งานเก็บในโทรศัพท์และเพิ่งมาซิงก์ส่งข้อมูลเมื่อลูกค้าหาสัญญาณเน็ตเจอ ระบบ Lookback Window ช่วยให้แต้มสะสมและสถิติยอดใช้ของแอปถูกคำนวณรวมย้อนหลังได้อย่างไม่ผิดพลาด
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การกำหนด Lookback Window กว้างเกินไป (เช่น สแกนย้อนหลัง 30 วันในทุกๆ ชั่วโมง) จะทำให้ท่อประมวลผลใช้เวลานานขึ้นและเสียค่าคอมพิวต์สูงเกินความจำเป็น แก้ไขโดยการประเมินสถิติเวลาล่าช้าเฉลี่ย (SLA tracking) เพื่อกำหนดระยะ Lookback ให้เหมาะสมที่สุด
5. การตรวจสอบความถูกต้องสกีมาและการทำเอกสารแผนผังข้อมูล Lineage
ทฤษฎีและกลไกการทำงาน (How it works): dbt เสริมสร้างความมั่นใจในคุณภาพข้อมูลโดยการนำคุณสมบัติ Schema Testing มาใช้ โดยเราสามารถระบุข้อบังคับความถูกต้องลงในไฟล์ YAML ของโมเดลเพื่อรันคำสั่งเช็คความถูกต้องอัตโนมัติ (เช่น ตรวจสอบความยูนิกของคีย์หลัก, ค่าห้ามเป็น Null, หรือความถูกต้องของความเชื่อมโยงตารางคีย์นอก) นอกจากนี้ dbt ยังสร้างเอกสารและแผนผังเส้นทางความเชื่อมโยงข้อมูล **Data Lineage** ให้ทีมพัฒนาสามารถตรวจสอบการเดินทางของข้อมูลย้อนกลับไปถึงจุดตั้งต้น (Source) ได้ทันที
# Configuration scheme for model assertions and schema testing in YAML
# models/schema.yml
version: 2
models:
- name: dim_customers
columns:
- name: customer_key
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values: ['active', 'inactive', 'pending']
Use Case ในชีวิตจริง (Real-world Scenario): ทุกครั้งที่วิศวกรข้อมูลรวมโค้ดและส่งขึ้นระบบโปรดักชัน ระบบ CI/CD จะสั่งรันคำสั่ง `dbt test` เพื่อตรวจสอบความสมบูรณ์ของยอดสถิติตาราง หากพบว่ามีข้อมูลแถวใดที่คีย์หลักซ้ำซ้อนหรือติดค่า Null ระบบจะสั่งยกเลิกเพื่อไม่ให้ข้อมูลพังไหลขึ้นระบบจริง
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การรันระบบทดสอบที่ซับซ้อนเกินไปบนข้อมูลขนาดใหญ่ระดับร้อยล้านแถวอาจเปลืองค่าประมวลผลคิวรีสูง แก้ไขโดยหลีกเลี่ยงการทดสอบแบบ Join ตารางในชั้นนี้ และย้ายความรับผิดชอบนี้ไปควบคุมที่ขั้นตอน Data Contracts ตั้งแต่ขาเข้าท่อส่งข้อมูล
🛠️ Weekend Sandbox Challenge: Configure Incremental Model in dbt
โจทย์ปฏิบัติการ: จงเขียนระเบียบโมเดล SQL ใน dbt เพื่อบันทึกข้อมูลแบบเพิ่มส่วนต่าง (Incremental Model) โดยระบุคีย์หลักในการทำ Merge และกรองขอบเขตวันเวลารันย้อนหลังเพื่อดึงข้อมูลอย่างคุ้มค่า
-- weekly_sales.sql
{{ config(
materialized='incremental',
unique_key='week_id',
incremental_strategy='merge'
) }}
SELECT
DATE_TRUNC('week', order_date) as week_id,
SUM(amount) as total_revenue
FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
WHERE order_date >= (SELECT DATEADD(day, -3, MAX(week_id)) FROM {{ this }}) -- 3-day lookback window
{% endif %}
GROUP BY 1
💼 Senior Technical Interview Q&A
Q1: อะไรคือความแตกต่างเชิงกำลังและค่าคิวรี่ระหว่างกลไก Table Materialization และ Incremental Materialization ใน dbt?
A1: Table materialization จะสร้างตารางขึ้นใหม่หมดทุกรอบการรัน (Drop & Recreate) ซึ่งประมวลผลข้อมูลปริมาณยักษ์ซ้ำซาก สิ้นเปลืองทั้งเวลาและค่าใช้จ่ายคลาวด์มหาศาล ในขณะที่ Incremental materialization ดึงเฉพาะแถวข้อมูลใหม่หรือแถวที่เปลี่ยนสเปคมาทับตารางเป้าหมายเดิมผ่านคำสั่ง `MERGE` ช่วยสปีดและประหยัดงบระบบได้สูงสุด
Q2: เราควรดีไซน์แก้ไขปัญหาท่อส่งดึงข้อมูลล่าช้า (Late-arriving data) ในโครงสร้าง Incremental ของ dbt อย่างไร?
A2: กำหนดกรอบการดึงข้อมูลย้อนหลังเผื่อเวลา (Lookback Window) ในคำสั่งกรอง โดยคำนวณย้อนหลัง เช่น `MAX(date) - INTERVAL '3 DAYS'` แทนที่จะดึงเฉพาะจุดล่าสุด และใช้คุณสมบัติ `unique_key` ร่วมกับ `merge` strategy เพื่อให้ dbt ทำลายแถวประวัติเดิมทิ้งก่อนแทรกค่าแก้ไขใหม่
ข้อดี / จุดเด่น & ข้อเสีย / ข้อควรระวัง
ข้อดี / จุดเด่น
- • ทำให้การพัฒนา SQL เป็นระเบียบและลดการเขียนโค้ดซ้ำซ้อนได้ดีเยี่่ยม
- • มีระบบเขียนเช็คคุณภาพข้อมูลและบันทึกเอกสารความสัมพันธ์อัตโนมัติในตัว
ข้อเสีย / ข้อควรระวัง
- • ทำได้แค่ด้านแปลงข้อมูล (T) เท่านั้น ไม่ได้รองรับการดึงข้อมูลจากต้นทางข้ามแหล่ง
- • ต้องอาศัยการวางแผนการประมวลผลรันเครื่องเซิร์ฟเวอร์คลังข้อมูลจริงในการคำนวณ
ปฏิบัติการจริง (Lab Practice)
แล็บ: พัฒนาและรันชุดโมเดลวิเคราะห์ข้อมูลผ่าน dbt
💡 คำแนะนำ & ทริกเด็ด
สำหรับโปรเจกต์ขนาดใหญ่ แนะนำให้เขียนโมเดลทดสอบไว้ในสภาพแวดล้อมจำลอง (Dev schema) เพื่อไม่ให้คิวรีเขียนไปทับตารางจริงบนโปรดักชันจนกว่ารหัสจะผสานสายหลัก