Home > Blog > tech

Feature Flags คืออะไร? สอน Progressive Delivery และ Feature Toggle สำหรับ DevOps 2026

feature flags progressive delivery guide
Feature Flags Progressive Delivery Guide 2026
2026-04-10 | tech | 3600 words

Feature Flags (หรือ Feature Toggles) เป็นเทคนิคที่ทรงพลังที่สุดอย่างหนึ่งใน Modern Software Delivery ช่วยให้คุณ Deploy code ขึ้น Production ได้ทุกวัน โดยไม่ต้องเปิดฟีเจอร์ให้ User ทุกคนเห็นทันที ทำให้สามารถ Release อย่างปลอดภัย ทดสอบกับ User จริง และ Rollback ได้ทันทีโดยไม่ต้อง Redeploy

บทความนี้จะสอน Feature Flags ครบทุกเรื่อง ตั้งแต่แนวคิดพื้นฐาน, Progressive Delivery, A/B Testing, Platform ยอดนิยม ไปจนถึง Best Practices สำหรับทีม DevOps

Feature Flags คืออะไร?

Feature Flags (Feature Toggles) คือเทคนิคที่ใช้ Configuration เพื่อควบคุมว่า Feature ใดจะเปิดหรือปิดใน Runtime โดยไม่ต้องแก้ Code หรือ Deploy ใหม่ เปรียบเหมือน "สวิตช์" ที่เปิด-ปิดฟีเจอร์ได้ทันที

// ตัวอย่างพื้นฐาน
if (featureFlags.isEnabled('new-checkout')) {
  showNewCheckout()
} else {
  showOldCheckout()
}

ประเภทของ Feature Flags

ประเภทวัตถุประสงค์อายุตัวอย่าง
Release Flagซ่อนฟีเจอร์ที่ยังไม่เสร็จสั้น (days-weeks)new-dashboard, v2-api
Experiment FlagA/B Testingปานกลาง (weeks)checkout-variant-b, pricing-test
Ops Flagควบคุม operational behaviorยาว (permanent)enable-cache, rate-limit-mode
Permission Flagจำกัดสิทธิ์การเข้าถึงยาว (permanent)beta-access, premium-feature

Progressive Delivery คืออะไร?

Progressive Delivery คือแนวทางการ Release ซอฟต์แวร์แบบค่อยๆ เปิดให้ User เข้าถึง แทนที่จะ Release ทีเดียวให้ทุกคน (Big Bang Release) ประกอบด้วยเทคนิค:

ทำไมต้อง Progressive Delivery? ลดความเสี่ยงจากการ Release, ค้นหา Bug ก่อนกระทบ User ทั้งหมด, ทดสอบ Performance กับ Traffic จริง, และ Rollback ได้ทันทีเมื่อเจอปัญหา

Canary Release กับ Feature Flags

// Canary: เปิดให้ 5% ของ User ก่อน
const flag = {
  name: 'new-payment-flow',
  enabled: true,
  rolloutPercentage: 5,  // 5% ของ users
  targetGroups: ['internal-testers']
}

function shouldShowFeature(userId, flag) {
  // 1. เช็ก target groups ก่อน
  if (flag.targetGroups.includes(getUserGroup(userId))) {
    return true
  }
  // 2. เช็ก percentage rollout
  const hash = hashUserId(userId) % 100
  return hash < flag.rolloutPercentage
}

A/B Testing กับ Feature Flags

// A/B Test: ทดสอบ 2 variants
const experiment = {
  name: 'checkout-redesign',
  variants: [
    { key: 'control', weight: 50 },     // A: แบบเดิม
    { key: 'treatment', weight: 50 }     // B: แบบใหม่
  ]
}

function getVariant(userId, experiment) {
  const hash = hashUserId(userId) % 100
  let cumulative = 0
  for (const variant of experiment.variants) {
    cumulative += variant.weight
    if (hash < cumulative) return variant.key
  }
  return experiment.variants[0].key
}

// ใช้งาน
const variant = getVariant(userId, experiment)
if (variant === 'treatment') {
  renderNewCheckout()
  trackEvent('checkout_view', { variant: 'treatment' })
} else {
  renderOldCheckout()
  trackEvent('checkout_view', { variant: 'control' })
}

User Targeting

// Target by attribute
const flag = {
  name: 'beta-feature',
  rules: [
    // เปิดให้ทุก internal users
    { attribute: 'email', operator: 'endsWith', value: '@company.com', enabled: true },
    // เปิดให้ users ในประเทศไทย
    { attribute: 'country', operator: 'equals', value: 'TH', enabled: true },
    // เปิดให้ premium users
    { attribute: 'plan', operator: 'in', value: ['pro', 'enterprise'], enabled: true },
    // เปิดให้ specific users
    { attribute: 'userId', operator: 'in', value: ['user-123', 'user-456'], enabled: true }
  ],
  defaultValue: false  // ปิดสำหรับ users ที่ไม่ตรง rules
}

Feature Flag Platforms เปรียบเทียบ

Platformประเภทราคาจุดเด่น
LaunchDarklySaaSเริ่ม $10/moEnterprise-grade, SDK ครบ, Experimentation
UnleashOpen Source / SaaSFree (OSS)Self-hosted, Open Source, ยืดหยุ่น
FlagsmithOpen Source / SaaSFree (OSS)Remote Config + Flags, Edge support
ConfigCatSaaSFree tierSimple, ราคาถูก, Config management
PostHogOpen Source / SaaSFree tierFeature Flags + Analytics + Session Replay
StatsigSaaSFree tierExperimentation platform + Auto metrics

Implementing Feature Flags — React

// React + LaunchDarkly
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk'

function CheckoutPage() {
  const { newCheckout, showPromoBar } = useFlags()

  return (
    <div>
      {showPromoBar && <PromoBanner />}
      {newCheckout ? <NewCheckout /> : <OldCheckout />}
    </div>
  )
}
// React + Custom Hook (ไม่ใช้ platform)
import { createContext, useContext, useState, useEffect } from 'react'

const FlagContext = createContext({})

export function FlagProvider({ children }) {
  const [flags, setFlags] = useState({})

  useEffect(() => {
    fetch('/api/flags')
      .then(r => r.json())
      .then(setFlags)
  }, [])

  return (
    <FlagContext.Provider value={flags}>
      {children}
    </FlagContext.Provider>
  )
}

export function useFlag(flagName) {
  const flags = useContext(FlagContext)
  return flags[flagName] ?? false
}

// ใช้งาน
function MyComponent() {
  const showNewUI = useFlag('new-ui')
  return showNewUI ? <NewUI /> : <OldUI />
}

Implementing Feature Flags — Node.js

// Node.js + Unleash
import { initialize } from 'unleash-client'

const unleash = initialize({
  url: 'https://unleash.example.com/api',
  appName: 'my-api',
  customHeaders: { Authorization: process.env.UNLEASH_TOKEN }
})

// Express middleware
function featureFlag(flagName) {
  return (req, res, next) => {
    const context = {
      userId: req.user?.id,
      properties: {
        email: req.user?.email,
        country: req.headers['x-country']
      }
    }
    req.flagEnabled = unleash.isEnabled(flagName, context)
    next()
  }
}

// ใช้งาน
app.get('/api/dashboard',
  featureFlag('new-dashboard'),
  (req, res) => {
    if (req.flagEnabled) {
      return res.json(getNewDashboardData())
    }
    return res.json(getOldDashboardData())
  }
)

Implementing Feature Flags — Python

# Python + Custom implementation
import hashlib
import json
from functools import wraps

class FeatureFlagService:
    def __init__(self, config_path='flags.json'):
        with open(config_path) as f:
            self.flags = json.load(f)

    def is_enabled(self, flag_name, user_id=None, attributes=None):
        flag = self.flags.get(flag_name)
        if not flag or not flag.get('enabled'):
            return False

        # Global kill switch
        if flag.get('kill_switch'):
            return False

        # Percentage rollout
        if user_id and 'percentage' in flag:
            hash_val = int(hashlib.md5(
                f"{flag_name}:{user_id}".encode()
            ).hexdigest(), 16) % 100
            return hash_val < flag['percentage']

        return True

# Flask decorator
flags = FeatureFlagService()

def feature_required(flag_name):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if not flags.is_enabled(flag_name, user_id=g.user_id):
                abort(404)
            return f(*args, **kwargs)
        return wrapper
    return decorator

@app.route('/new-feature')
@feature_required('new-feature')
def new_feature():
    return render_template('new_feature.html')

Feature Flags ใน Microservices

// Centralized Flag Service Architecture
//
// [Flag Service] ← Admin Dashboard
//       ↓ (SDK / API)
//  ┌────┼────┐
//  ↓    ↓    ↓
// [Web] [API] [Worker]
//
// ทุก Service ใช้ flag state เดียวกัน

// Flag evaluation ใน API Gateway
async function apiGatewayMiddleware(req, res, next) {
  const flags = await flagService.getAllFlags({
    userId: req.user?.id,
    service: req.params.service
  })

  // Inject flags เข้า request header ส่งต่อไป downstream services
  req.headers['x-feature-flags'] = JSON.stringify(flags)
  next()
}

// Downstream service อ่าน flags จาก header
function getFlags(req) {
  return JSON.parse(req.headers['x-feature-flags'] || '{}')
}

Flag Lifecycle Management

Feature Flags มี Lifecycle ที่ต้องจัดการ ไม่เช่นนั้นจะกลายเป็น Technical Debt:

  1. Creation — สร้าง Flag พร้อม description, owner, expiry date
  2. Development — ใช้ Flag wrap ฟีเจอร์ใหม่ในระหว่าง development
  3. Testing — ทดสอบทั้ง flag on/off
  4. Rollout — ค่อยๆ เปิด (Canary → Percentage → Full)
  5. Full Release — เปิด 100%
  6. Cleanup — ลบ Flag code ออก (สำคัญมาก!)
// ตัวอย่าง Flag metadata
{
  "new-checkout": {
    "enabled": true,
    "percentage": 100,
    "owner": "team-checkout",
    "created": "2026-03-01",
    "expires": "2026-05-01",
    "description": "New checkout flow with Apple Pay",
    "jira": "PROJ-1234",
    "status": "full-rollout",    // ready-for-cleanup
    "stale_after_days": 30
  }
}
Technical Debt Warning: Feature Flags ที่ไม่ลบออกจะสะสมเป็น Technical Debt อย่างรวดเร็ว ตั้งกฎว่า Flag ที่ rollout 100% แล้ว ต้อง cleanup ภายใน 2 สัปดาห์ ใช้ automated alerts เตือน stale flags

Kill Switches สำหรับ Incidents

// Kill Switch — ปิดฟีเจอร์ทันทีเมื่อเกิดปัญหา
const KILL_SWITCHES = {
  'external-payment': true,    // ปิด external payment เมื่อ gateway ล่ม
  'ai-recommendations': true,  // ปิด AI เมื่อ model service มีปัญหา
  'search-service': true,      // ปิด search เมื่อ Elasticsearch ล่ม
}

// ใน code
async function processPayment(order) {
  if (!flagService.isEnabled('external-payment')) {
    // Fallback: queue สำหรับ retry ภายหลัง
    await paymentQueue.add(order)
    return { status: 'queued', message: 'Payment will be processed shortly' }
  }
  return await paymentGateway.charge(order)
}

Trunk-based Development + Feature Flags

Feature Flags ช่วยให้ทีมใช้ Trunk-based Development ได้อย่างมั่นใจ:

// Workflow
// 1. Developer สร้าง flag "new-search"
// 2. เขียน code wrap ด้วย flag
// 3. Commit เข้า main → Deploy to production (flag ปิด)
// 4. ทดสอบใน production กับ internal users (flag เปิดเฉพาะ internal)
// 5. Canary release 5% → 25% → 50% → 100%
// 6. ลบ flag code → commit → deploy

// ข้อดี:
// - ไม่มี merge conflicts จาก long-lived branches
// - ทุก commit ผ่าน CI/CD ทันที
// - ฟีเจอร์ถูก test กับ production traffic จริง

Testing กับ Feature Flags

// ทดสอบทั้ง flag on และ off
describe('Checkout', () => {
  it('shows new checkout when flag is ON', () => {
    // Mock flag service
    jest.spyOn(flagService, 'isEnabled')
      .mockReturnValue(true)

    render(<CheckoutPage />)
    expect(screen.getByTestId('new-checkout')).toBeInTheDocument()
  })

  it('shows old checkout when flag is OFF', () => {
    jest.spyOn(flagService, 'isEnabled')
      .mockReturnValue(false)

    render(<CheckoutPage />)
    expect(screen.getByTestId('old-checkout')).toBeInTheDocument()
  })
})

// Integration test matrix
// ทดสอบ combinations ของ flags ที่อาจกระทบกัน
const flagCombinations = [
  { 'new-checkout': true, 'promo-bar': true },
  { 'new-checkout': true, 'promo-bar': false },
  { 'new-checkout': false, 'promo-bar': true },
  { 'new-checkout': false, 'promo-bar': false },
]

Monitoring Feature Flag Impact

// วัดผลกระทบของ flag ต่อ metrics
function trackFlagImpact(flagName, variant, metrics) {
  analytics.track('flag_evaluation', {
    flag: flagName,
    variant: variant,
    // Business metrics
    conversion_rate: metrics.conversion,
    revenue: metrics.revenue,
    // Technical metrics
    latency_p99: metrics.latencyP99,
    error_rate: metrics.errorRate,
    // User experience
    page_load_time: metrics.pageLoadTime,
    bounce_rate: metrics.bounceRate
  })
}

// Dashboard queries
// - เปรียบเทียบ conversion rate ระหว่าง control vs treatment
// - ดู error rate ก่อน/หลังเปิด flag
// - ดู latency impact ของ flag
// - ดู user engagement metrics

Best Practices

DoDon't
ตั้ง expiry date ให้ทุก flagปล่อย flag ค้างไว้ไม่มีกำหนด
ใช้ naming convention ชัดเจนตั้งชื่อ flag แบบ "flag1", "test123"
ทดสอบทั้ง on/off pathsทดสอบแค่ happy path
มี kill switch สำหรับ critical featuresไม่มีทางปิด feature ได้ทันที
Cleanup flags ที่ rollout 100% แล้วสะสม stale flags เป็นร้อยๆ
ใช้ centralized flag serviceกระจาย flag config ไปทั่ว codebase
Log flag evaluations สำหรับ debuggingไม่มี visibility ว่า flag ไหนเปิดอยู่
ใช้ server-side evaluation สำหรับ sensitive flagsส่ง flag logic ไปฝั่ง client ทั้งหมด

Anti-patterns ที่ต้องระวัง

Flag-driven Development Workflow

// Step-by-step workflow สำหรับทีม
//
// 1. Product: สร้าง feature flag ใน dashboard
//    → ตั้งชื่อ, description, owner, expiry
//
// 2. Dev: เขียน code wrap ด้วย flag
//    → commit เข้า main ทุกวัน
//
// 3. QA: ทดสอบ flag on/off ใน staging
//    → ทดสอบ edge cases, combinations
//
// 4. Release Manager: เปิด flag ให้ internal users
//    → Dogfooding ภายใน 1-2 วัน
//
// 5. Gradual Rollout: 5% → 25% → 50% → 100%
//    → Monitor metrics ทุก stage
//
// 6. Cleanup: ลบ flag code + config
//    → PR review + merge ภายใน 2 สัปดาห์

สรุป

Feature Flags เป็นเครื่องมือสำคัญสำหรับ Modern Software Delivery ช่วยให้ทีม Deploy ได้บ่อยขึ้น Release อย่างปลอดภัย และ Rollback ได้ทันทีเมื่อเกิดปัญหา เมื่อรวมกับ Progressive Delivery ทำให้สามารถ Release ฟีเจอร์ใหม่ให้ User กลุ่มเล็กก่อน ทดสอบกับ Traffic จริง แล้วค่อยขยายไปทั้งระบบ

เริ่มต้นง่ายๆ: เลือก Platform ที่เหมาะกับทีม (Unleash สำหรับ self-hosted, LaunchDarkly สำหรับ enterprise, PostHog สำหรับ flags + analytics) สร้าง flag แรก wrap ฟีเจอร์ใหม่ แล้วลอง rollout ทีละ 10% — คุณจะเข้าใจทันทีว่าทำไม Feature Flags ถึงเปลี่ยนวิธีที่เรา deliver software


Back to Blog | iCafe Forex | SiamLanCard | Siam2R