1. Pipeline Performance Optimization
การ optimize pipeline performance เป็นสิ่งสำคัญในการลดเวลารอคอยและเพิ่ม productivity ของทีมพัฒนา เราจะมาดูกันทีละหัวข้อ
Pipeline Performance Optimization Flow
1.1 วัดเวลา Pipeline (Pipeline Analytics)
GitLab มี Pipeline Analytics ในตัวสำหรับวัดประสิทธิภาพ pipeline
# เข้าถึง Pipeline Analytics
# Navigate to: CI/CD > Pipelines > Analytics
# ดูข้อมูลที่ได้:
# - Average pipeline duration
# - Success rate
# - Failed jobs breakdown
# - Slowest jobs identification
# ใช้ GitLab API สำหรับดึงข้อมูล
curl --header "PRIVATE-TOKEN: " \
"https://gitlab.com/api/v4/projects/:id/pipelines?statistics=1"
Tip: ตั้งเป้าหมายลดเวลา pipeline ลง 20-30% จากค่าเฉลี่ยเดิม
1.2 ลดเวลา Build
ใช้ Docker Image ที่เหมาะสม
- ใช้ slim/alpine images
- Pre-install dependencies
- Create custom base images
Minimize Dependencies
- ใช้ npm ci แทน npm install
- Remove unused packages
- Use workspace features
# .gitlab-ci.yml - Build Optimization
build:
stage: build
image: node:20-alpine # ✅ ใช้ alpine (เล็กกว่า 5x)
before_script:
- npm ci --prefer-offline --no-audit # ✅ เร็วกว่า npm install
script:
- npm run build
cache:
key: ${CI_COMMIT_REF_SLUG}-node
paths:
- node_modules/
policy: pull-push
variables:
NODE_ENV: production
npm_config_cache: "$CI_PROJECT_DIR/.npm" # ✅ Cache npm downloads
1.3 Parallelization Strategies
| Strategy | Use Case | Example |
|---|---|---|
parallel: |
แบ่ง job เดียวเป็นหลาย instances | Test matrix |
matrix: |
รันหลาย configurations | Multi-version testing |
needs: |
DAG pipelines | Out-of-order execution |
# .gitlab-ci.yml - Parallelization Examples
# 1. Matrix Jobs - ทดสอบหลายเวอร์ชันพร้อมกัน
test:
stage: test
image: node:${NODE_VERSION}
parallel:
matrix:
- NODE_VERSION: [18, 20, 22]
TEST_SUITE: [unit, integration]
script:
- npm run test:${TEST_SUITE}
cache:
key: ${NODE_VERSION}-${TEST_SUITE}
# 2. DAG Pipeline - รัน jobs ไม่ต้องรอ stage
lint:
stage: test
script: npm run lint
test:unit:
stage: test
needs: [build] # ✅ ไม่ต้องรอ lint
script: npm run test:unit
test:e2e:
stage: test
needs: [build] # ✅ รันพร้อม test:unit
script: npm run test:e2e
# 3. Parallel Jobs - แบ่งงานเป็นส่วนๆ
test:parallel:
stage: test
parallel: 5 # ✅ แบ่งเป็น 5 instances
script:
- npm run test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
1.4 ใช้ Cache อย่างมีประสิทธิภาพ
# .gitlab-ci.yml - Advanced Caching
# Global cache configuration
cache:
key: ${CI_COMMIT_REF_SLUG}-${CI_JOB_NAME}
paths:
- node_modules/
- .npm/
- .next/cache/
policy: pull-push
# Job-specific cache with fallback
variables:
CACHE_KEY: node-modules-${CI_COMMIT_REF_SLUG}
install:
stage: build
cache:
key: ${CACHE_KEY}
paths:
- node_modules/
policy: pull-push
script:
- |
if [[ -d node_modules ]]; then
echo "Cache found, checking for updates..."
npm ci --prefer-offline
else
echo "No cache, fresh install..."
npm ci
fi
# Read-only cache for downstream jobs
test:
cache:
key: ${CACHE_KEY}
policy: pull # ✅ ไม่อัพเดท cache
script:
- npm test
Warning: อย่า cache sensitive files เช่น .env หรือ credentials
1.5 Fail Fast กับ allow_failure
# .gitlab-ci.yml - Fail Fast Strategy
stages:
- quick-check
- build
- test
- deploy
# ✅ Quick checks ที่ fail เร็ว
lint:
stage: quick-check
script: npm run lint
allow_failure: false # ❌ หยุด pipeline ถ้า fail
type-check:
stage: quick-check
script: npm run type-check
allow_failure: false
security:audit:
stage: quick-check
script: npm audit
allow_failure: true # ⚠️ ให้ผ่านได้แต่แจ้งเตือน
# ✅ Build only if quick checks pass
build:
stage: build
needs: [lint, type-check]
script: npm run build
# ใช้ rules เพื่อ skip งานที่ไม่จำเป็น
deploy:staging:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
script: npm run deploy:staging
Performance Optimization Checklist
2. YAML Best Practices
2.1 ใช้ Anchors และ Aliases (&, *)
YAML anchors ช่วยลดการ duplicate code และทำให้ maintenance ง่ายขึ้น
# .gitlab-ci.yml - Anchors & Aliases
# ✅ Define reusable configurations
.node_job_template: &node_job_config
image: node:20-alpine
before_script:
- npm ci --cache .npm --prefer-offline
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
.deploy_template: &deploy_config
image: alpine:latest
before_script:
- apk add --no-cache curl bash
only:
- main
- develop
# ✅ Use aliases to reference configurations
test:unit:
<<: *node_job_config # Merge template
stage: test
script:
- npm run test:unit
test:integration:
<<: *node_job_config
stage: test
script:
- npm run test:integration
coverage: '/Lines\s*:\s*(\d+.?\d*)%/'
deploy:production:
<<: *deploy_config
stage: deploy
environment:
name: production
script:
- ./deploy.sh production
2.2 YAML extends และ includes
# .gitlab-ci.yml - Extends & Includes
# ✅ Include external templates
include:
- project: 'my-org/ci-templates'
ref: main
file: '/templates/node.yml'
- project: 'my-org/ci-templates'
ref: main
file: '/templates/docker.yml'
- component: 'my-org/components/security-scanner@1.0'
inputs:
severity_level: high
# ✅ Use extends for local inheritance
.build_template:
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
build:app:
extends: .build_template
stage: build
variables:
APP_ENV: production
script:
- echo "Building for $APP_ENV"
- !reference [.build_template, script] # Include parent script
2.3 การจัดโครงสร้างไฟล์
project/
├── .gitlab-ci.yml # Main pipeline file
├── .gitlab/
│ ├── ci-templates/ # Reusable templates
│ │ ├── node.yml
│ │ ├── docker.yml
│ │ └── deploy.yml
│ └── jobs/ # Job definitions
│ ├── build.yml
│ ├── test.yml
│ └── security.yml
└── scripts/
└── ci/ # CI scripts
├── setup.sh
└── deploy.sh
# .gitlab-ci.yml - Modular Structure
# Global variables
variables:
NODE_VERSION: "20"
DOCKER_REGISTRY: "registry.example.com"
# Include modular components
include:
- local: '/.gitlab/jobs/build.yml'
- local: '/.gitlab/jobs/test.yml'
- local: '/.gitlab/jobs/security.yml'
- local: '/.gitlab/jobs/deploy.yml'
# Global defaults
default:
tags:
- docker
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
# Workflow rules
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "develop"
2.4 Linting .gitlab-ci.yml
# วิธี lint .gitlab-ci.yml
# 1. ใช้ GitLab CI Lint (UI)
# Navigate to: CI/CD > Editor > Validate
# 2. ใช้ GitLab API
curl --header "PRIVATE-TOKEN: " \
--header "Content-Type: application/json" \
--data @.gitlab-ci.yml \
"https://gitlab.com/api/v4/ci/lint"
# 3. ใช้ gitlab-ci-lint tool
npm install -g gitlab-ci-lint
gitlab-ci-lint .gitlab-ci.yml
# 4. ใช้ pre-commit hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.0
hooks:
- id: check-gitlab-ci
files: \.gitlab-ci\.yml$
3. Security Best Practices
Security is Critical
การรั่วไหลของ credentials สามารถนำไปสู่การโจมตีระบบได้ ต้องปฏิบัติตาม security best practices เสมอ
3.1 ไม่ Hardcode Secrets
❌ ไม่ควรทำ
DB_PASS=mysecretpass123
✅ ควรทำ
DB_PASS=$DATABASE_PASSWORD
# .gitlab-ci.yml - Secure Variable Usage
deploy:
stage: deploy
script:
# ✅ ใช้ variables จาก GitLab
- echo "Deploying to $DEPLOY_ENV"
- kubectl config set-credentials deploy-user --token=$K8S_TOKEN
# ❌ อย่าทำแบบนี้!
# - export PASSWORD="hardcoded-password"
# ✅ ใช้ files สำหรับ sensitive data
- echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
variables:
# ✅ Override ใน GitLab UI > Settings > CI/CD > Variables
DEPLOY_ENV: staging
3.2 ใช้ Protected Variables
| Variable Type | When to Use | Example |
|---|---|---|
| Protected | Production secrets, deploy keys | PROD_DB_PASSWORD |
| Masked | API keys, tokens (single line) | API_TOKEN |
| File | SSH keys, certificates | SSH_KEY_FILE |
# .gitlab-ci.yml - Protected Variables Usage
deploy:production:
stage: deploy
rules:
- if: $CI_COMMIT_REF_NAME == "main" # ✅ Protected branch only
script:
- echo "Deploying to production..."
# Variables below should be marked as Protected + Masked
- aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
- aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
- aws s3 sync ./dist s3://$S3_BUCKET
environment:
name: production
url: https://app.example.com
# GitLab UI Setup:
# Settings > CI/CD > Variables > Add Variable
# ☑ Protected (only available on protected branches/tags)
# ☑ Mask (hide in job logs)
# Variable type: File (for multi-line content)
3.3 Scan Vulnerabilities
# .gitlab-ci.yml - Security Scanning
# Enable GitLab Ultimate security features
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
# Custom security job
security:audit:
stage: test
image: node:20-alpine
script:
- npm audit --audit-level=high
- npx better-npm-audit audit --level=high
allow_failure: true
artifacts:
reports:
dependency_scanning: gl-dependency-scanning-report.json
# Container scanning
container_scan:
stage: test
image: docker:latest
services:
- docker:dind
script:
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy image $IMAGE_NAME:$CI_COMMIT_SHA
allow_failure: true
Security Checklist
4. Cost Optimization
💰 CI/CD Cost Savings
การ optimize อย่างถูกวิธีสามารถลดค่าใช้จ่ายได้ 40-60%
4.1 วัด CI/CD Minutes Usage
# ตรวจสอบ CI/CD minutes usage
# 1. ผ่าน GitLab UI
# Navigate to: Settings > CI/CD > CI/CD minutes
# 2. ผ่าน GitLab API
curl --header "PRIVATE-TOKEN: " \
"https://gitlab.com/api/v4/namespaces/:id/ci_minutes_usage"
# 3. ตั้ง quota alerts
# Settings > CI/CD > CI/CD minutes > Set notification threshold
# 4. วิเคราะห์ usage ตาม project
curl --header "PRIVATE-TOKEN: " \
"https://gitlab.com/api/v4/projects/:id/pipelines?statistics=1" \
| jq '.[] | {duration: .duration, created_at: .created_at}'
4.2 ใช้ Shared Runners อย่างคุ้มค่า
Shared Runners
Pay per minute
Good for variable load
Self-hosted
Fixed cost
Good for high volume
Hybrid
Best of both
Cost optimized
# .gitlab-ci.yml - Runner Optimization
# ใช้ tags เพื่อ route ไปยัง runner ที่เหมาะสม
job:
tags:
- self-hosted # ใช้ self-hosted runner สำหรับงานหนัก
- linux
- docker
# ใช้ resource groups เพื่อ limit concurrent jobs
deploy:
stage: deploy
resource_group: production # ✅ Only one deploy at a time
script:
- ./deploy.sh
# ใช้ rules เพื่อ skip unnecessary jobs
test:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never # ❌ Skip scheduled pipelines
- changes:
- src/**/*.{js,ts}
- tests/**/*
4.3 Auto-cancel และ Resource Optimization
# .gitlab-ci.yml - Cost Optimization
# Auto-cancel redundant pipelines
workflow:
rules:
- if: $CI_COMMIT_BRANCH =~ /^feature\//
auto_cancel:
on_new_commit: conservative # Cancel older pipelines
# Skip unnecessary pipelines
workflow:
rules:
# Skip for draft MRs
- if: $CI_MERGE_REQUEST_TITLE =~ /^(Draft|WIP):/
when: never
# Skip for docs-only changes
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_MESSAGE =~ /\[skip ci\]/
when: never
- when: always
# Use interruptible for long-running jobs
test:e2e:
interruptible: true # ✅ Can be cancelled if new commit pushed
script:
- npm run test:e2e
# Limit job timeout
build:
timeout: 15 minutes # ✅ Prevent runaway jobs
script:
- npm run build
# Use needs to start jobs early (less waiting = less runner time)
deploy:staging:
needs: [build] # Don't wait for all test jobs
script:
- ./deploy.sh staging
| Optimization | Savings | Implementation Effort |
|---|---|---|
| Auto-cancel redundant pipelines | 20-30% | Low |
| Enable caching | 15-25% | Low |
| Skip draft MRs | 10-15% | Low |
| Self-hosted runners (high volume) | 40-60% | Medium |
| Parallelization | Time: 50%+ | Medium |
5. Code Quality Gates
# .gitlab-ci.yml - Quality Gates Configuration
# Quality Gate Stages
stages:
- quality # Gate 1: Code Quality
- test # Gate 2: Testing
- security # Gate 2: Security
- build
- deploy
# ============ GATE 1: Code Quality ============
lint:
stage: quality
script:
- npm run lint
allow_failure: false # ❌ Block merge if fail
format:check:
stage: quality
script:
- npm run format:check
allow_failure: false
type-check:
stage: quality
script:
- npm run type-check
allow_failure: false
# ============ GATE 2: Testing ============
test:unit:
stage: test
script:
- npm run test:unit -- --coverage
coverage: '/Lines\s*:\s*(\d+.?\d*)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: on_success
allow_failure: false
# Coverage threshold check
coverage:threshold:
stage: test
script:
- |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
echo "✅ Coverage $COVERAGE% meets threshold"
# ============ GATE 3: Security ============
security:sast:
stage: security
include:
- template: Security/SAST.gitlab-ci.yml
rules:
- exists:
- '**/*.js'
- '**/*.ts'
# Merge Request Settings
# Settings > Merge requests > Merge checks
# ☑ Pipelines must succeed
# ☑ All discussions must be resolved
# ☑ Require approval (Code Owners)
| Metric | Threshold | Action if Fail |
|---|---|---|
| Linting Errors | 0 |
Block |
| Code Coverage | ≥ 80% |
Block |
| Security Vulnerabilities (Critical/High) | 0 |
Block |
| Unit Tests | 100% pass |
Block |
| Code Review Approval | ≥ 1 |
Block |
6. Monitoring & Observability
Pipeline Metrics
# .gitlab-ci.yml - Pipeline Metrics Collection
# Custom metrics reporting
metrics:report:
stage: .post
script:
- |
echo "📊 Pipeline Metrics:"
echo "Duration: $CI_PIPELINE_DURATION seconds"
echo "Jobs: $CI_JOB_TOTAL"
echo "Status: $CI_PIPELINE_STATUS"
# Send to monitoring system
curl -X POST $METRICS_ENDPOINT \
-H "Content-Type: application/json" \
-d '{
"pipeline_id": "'$CI_PIPELINE_ID'",
"duration": "'$CI_PIPELINE_DURATION'",
"status": "'$CI_PIPELINE_STATUS'",
"branch": "'$CI_COMMIT_REF_NAME'",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}'
rules:
- when: always # Run even on failure
Alerting สำหรับ Failed Pipelines
# .gitlab-ci.yml - Alerting Configuration
# Alert on failure
notify:failure:
stage: .post
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: on_failure
script:
# Slack notification
- |
curl -X POST $SLACK_WEBHOOK_URL \
-H "Content-Type: application/json" \
-d '{
"text": "🚨 Pipeline Failed!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Pipeline Failed* on `'"$CI_COMMIT_REF_NAME"'`\nProject: '"$CI_PROJECT_NAME"'\n<'"$CI_PIPELINE_URL"'|View Pipeline>"
}
}
]
}'
# Email notification (GitLab built-in)
# Settings > Integrations > Pipeline status emails
# Alert on long-running pipeline
check:duration:
stage: .post
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: always
script:
- |
DURATION=$(echo $CI_PIPELINE_DURATION | cut -d'.' -f1)
if [ "$DURATION" -gt 600 ]; then
curl -X POST $ALERT_WEBHOOK \
-d "Pipeline took ${DURATION}s - exceeds 10min threshold"
fi
GitLab Observability
GitLab Observability (Ultimate) มี features:
- • Tracing & Distributed Tracing
- • Metrics & Dashboards
- • Log Management
- • Error Tracking
- • On-call Scheduling
7. Team Collaboration
Merge Request Integration
# .gitlab-ci.yml - MR Integration
# MR-specific jobs
mr:preview:
stage: deploy
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
environment:
name: preview/mr-${CI_MERGE_REQUEST_IID}
url: https://mr-${CI_MERGE_REQUEST_IID}.preview.example.com
on_stop: mr:preview:stop
script:
- ./deploy-preview.sh $CI_MERGE_REQUEST_IID
mr:preview:stop:
stage: deploy
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
environment:
name: preview/mr-${CI_MERGE_REQUEST_IID}
action: stop
script:
- ./cleanup-preview.sh $CI_MERGE_REQUEST_IID
# Report results to MR
report:coverage:
stage: .post
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- |
curl -X POST "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
-H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body": "## 📊 Coverage Report\n\n**Coverage:** 85%\n\n[View Full Report]('"$CI_PAGES_URL"'/coverage)"}'
Pipeline Templates สำหรับทีม
Template Structure
├── node-app.yml
├── react-app.yml
├── docker-build.yml
└── kubernetes-deploy.yml
Usage
- project: 'org/templates'
file: '/node-app.yml'
Documentation Best Practices
# .gitlab-ci.yml Documentation Template
# ============================================
# PROJECT: My Awesome App
# DESCRIPTION: CI/CD Pipeline for Node.js app
# MAINTAINER: DevOps Team
# LAST UPDATED: 2026-02-16
# ============================================
#
# WORKFLOW:
# 1. On MR: Run lint, test, preview deploy
# 2. On main: Run full test suite, deploy staging
# 3. On tag: Deploy to production
#
# REQUIRED VARIABLES (set in GitLab UI):
# - AWS_ACCESS_KEY_ID
# - AWS_SECRET_ACCESS_KEY
# - DOCKER_REGISTRY_TOKEN
#
# OPTIONAL VARIABLES:
# - SKIP_E2E: Set to "true" to skip E2E tests
# ============================================
stages:
- lint
- test
- build
- deploy
# ... rest of pipeline
8. Common Mistakes to Avoid
Hardcoding Secrets
❌ ปัญหา: ใส่ passwords, API keys ใน code
✅ แก้ไข: ใช้ GitLab CI/CD Variables
Not Using Cache
❌ ปัญหา: npm install ทุกครั้ง +3-5 min/build
✅ แก้ไข: เปิดใช้ cache สำหรับ node_modules, vendor
Running All Tests Sequentially
❌ ปัญหา: รัน test ทีละอัน เสียเวลานาน
✅ แก้ไข: ใช้ parallel, matrix, หรือ needs:
No Pipeline for Draft MRs
❌ ปัญหา: รัน pipeline ทุก MR แม้ยังไม่พร้อม
✅ แก้ไข: Skip draft MRs ด้วย workflow rules
Overly Large Docker Images
❌ ปัญหา: ใช้ image ใหญ่ เช่น node:20 (1GB+)
✅ แก้ไข: ใช้ alpine/slim versions (200MB)
No Fail Fast Strategy
❌ ปัญหา: รันต่อแม้ lint fail
✅ แก้ไข: ตั้ง allow_failure: false สำหรับ critical jobs
No Resource Limits
❌ ปัญหา: Jobs รันได้นานไม่จำกัด
✅ แก้ไข: ตั้ง timeout สำหรับแต่ละ job
Missing Quality Gates
❌ ปัญหา: merge code ได้โดยไม่ต้องผ่าน tests
✅ แก้ไข: ตั้ง "Pipelines must succeed" ใน MR settings
Not Using DAG Pipelines
❌ ปัญหา: รอทุก job ใน stage เสร็จก่อน stage ถัดไป
✅ แก้ไข: ใช้ needs: เพื่อ start jobs ทันทีที่ dependencies พร้อม
No Security Scanning
❌ ปัญหา: Deploy code ที่มี vulnerabilities
✅ แก้ไข: เปิด SAST, Dependency Scanning, Container Scanning
9. CI/CD Maturity Model
CI/CD Maturity Model ช่วยประเมินระดับความพร้อมของกระบวนการ CI/CD ในองค์กร และวางแผนพัฒนาได้อย่างเป็นระบบ
| Criteria | Level 1 | Level 2 | Level 3 | Level 4 | Level 5 |
|---|---|---|---|---|---|
| Build Process | Manual | Semi-auto | Automated | Optimized | Intelligent |
| Testing | Ad-hoc | Unit tests | Full suite | Gated | AI-driven |
| Deployment | Manual | Scripts | CD Pipeline | Blue-green | Canary+AI |
| Monitoring | None | Basic logs | Dashboards | Alerts | Predictive |
| Security | None | Manual | Scanning | Gated | Shift-left |
สรุป (Summary)
🎯 Key Takeaways
- Optimize pipeline performance ด้วย caching, parallelization
- ใช้ YAML best practices: anchors, extends, includes
- Security first: ไม่ hardcode, ใช้ protected variables
- Cost optimization: auto-cancel, resource limits
- Quality gates: lint, test, coverage, security
📚 Next Steps
- 1 ประเมิน CI/CD maturity level ปัจจุบัน
- 2 เลือก 2-3 best practices มา implement ก่อน
- 3 วัดผลและปรับปรุงอย่างต่อเนื่อง
- 4 อ่าน Part 5: Advanced Topics