နားလည်ထားရမယ့် အချက်
နှေးတဲ့ pipeline ဟာ အချိန်ကုန်ရုံ မဟုတ်ဘူး — လူတွေရဲ့ အပြုအမူကို ပြောင်းစေတယ်။ ၁၀ မိနစ်ကျော်လာရင် လူတွေက ရလဒ်ကို မစောင့်တော့ဘူး၊ ပြောင်းလဲမှု အများကြီးကို push တစ်ခုထဲ စုထည့်လာတယ်၊ check တွေ ကျော်နိုင်တဲ့ merge ခွင့်ကို တောင်းလာတယ်။ အဲဒီအခါ pipeline က ဘာမှ မကာကွယ်ပေးနိုင်တော့ဘူး — လူတွေက သူ့ဘေးက လှည့်ဖြတ်သွားလို့ပါ။ မြန်ဖို့ဆိုတာ သက်တောင့်သက်သာရှိဖို့ မဟုတ်ဘဲ ကိုယ့် process ရဲ့ မှန်ကန်မှု ဂုဏ်သတ္တိ တစ်ခုပါ။
တိုင်းတာပြီးမှ optimize လုပ်ပါ။ run တိုင်းမှာ job အလိုက်၊ step အလိုက် အချိန်ကို ပြထားတယ်၊ ခန့်မှန်းမနေဘဲ ကြည့်ပါ — အချိန်ဘယ်မှာ ကုန်နေလဲဆိုတာ ခံစားချက်က မကြာခဏ မှားတတ်တယ်။ repository အများစုမှာ အဖြေက dependency install, incremental မဖြစ်တဲ့ build, ရှည်လျားတဲ့ test suite တစ်ခု ဖြစ်ပြီး၊ တစ်ခုချင်းစီအတွက် ဖြေရှင်းနည်း မတူပါဘူး။
cache က install နဲ့ build ကို ကိုင်တွယ်တယ်။ actions/cache နဲ့ setup action တွေရဲ့ cache: option က lockfile hash ကို key အဖြစ်သုံးပြီး directory တစ်ခုကို ပြန်ထည့်ပေးတယ်၊ ဒါကြောင့် မပြောင်းလဲတဲ့ dependency tree ကို run တိုင်း ပြန်မဆွဲတော့ဘူး။ ဘာကို cache လုပ်လဲက အရေးကြီးတယ် — ~/.npm ဒါမှမဟုတ် package manager store က လုံခြုံတယ်၊ node_modules တစ်ခုလုံး ကူးထားတာက platform ဒါမှမဟုတ် version ပြောင်းတဲ့အခါ ပဲ့သွားတတ်တယ်။ key ကို content အပေါ် အခြေခံပါ၊ တစ်ပိုင်းတစ်စ ရနိုင်ဖို့ restore-keys ထည့်ပါ၊ ပြီးတော့ run တိုင်း key ပြောင်းနေတဲ့ cache ဟာ ဘာမှ မသက်သာစေဘူး ဆိုတာ မှတ်ထားပါ။
parallelism က test ကို ကိုင်တွယ်တယ်။ needs: နဲ့ အစဉ်လိုက် မချိတ်ထားရင် job တွေက တစ်ပြိုင်တည်း run တယ်၊ ဒါကြောင့် lint, type-check, build တွေ အစဉ်လိုက် ဖြစ်နေဖို့ အကြောင်းမရှိဘူး။ strategy.matrix shard က suite တစ်ခုကို runner အများဆီ ခွဲပေးတယ်၊ အချိန်က shard အရေအတွက်နဲ့ စားလိုက်တဲ့ ပမာဏ အပေါင်း သတ်မှတ် setup အချိန် ဖြစ်သွားတယ် — ဒါကြောင့် shard များလာရင် တစ်ချိန်မှာ အကျိုးမရှိတော့ဘူး။
နောက်ဆုံးအနေနဲ့ အပြောင်းအလဲတိုင်းအတွက် အားလုံး run စရာ မလိုဘူး။ path filter နဲ့ conditional job တွေက documentation ပဲ ပြောင်းတဲ့ PR တွေမှာ ဈေးကြီးတဲ့ end-to-end suite ကို ချန်ထားပေးတယ်။ စည်းကမ်းက coverage ကို ဖျက်ချတာ မဟုတ်ဘဲ risk အလိုက် scope ချတာပါ — PR မှာ ကျော်လိုက်တဲ့ဟာက production မရောက်ခင် တစ်နေရာမှာ ပြန် run ဖြစ်ရမယ်။
SEQUENTIAL VERSUS PARALLEL PIPELINE
-----------------------------------
SEQUENTIAL (one job, each step after the last)
|--install--|--build--|--------unit tests--------|---e2e---|
start -------------------- wall clock -------------------> T
PARALLEL (fan out after a cached install)
|--install (cache hit)--|
|--lint---------|
|--build--------|
|--unit shard 1------|
|--unit shard 2------|
|--unit shard 3------|
|--e2e (skipped on docs-only PRs)--|
start ---> wall clock is now the SLOWEST branch, not the sum
LIMIT
fixed setup (checkout + install) is paid by every parallel job,
so beyond a certain shard count you buy minutes, not speed.လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
ဒီ workflow မှာ job သုံးမျိုးက တစ်ပြိုင်တည်း စတယ် — lint, unit, e2e ကြားမှာ needs: မရှိလို့ပါ။ ဒါက အလွယ်ဆုံး အမြန်ဆုံး အနိုင်ရမှုပါ — job တစ်ခုတည်းထဲမှာ ရေးထားလို့ အစဉ်လိုက် run နေခဲ့တဲ့ pipeline အများစုကို အရင် ခွဲကြည့်ပါ။
setup-node ရဲ့ cache: npm က npm ရဲ့ download cache ကို package-lock.json hash နဲ့ ချိတ်ပေးတယ်၊ ဒါကြောင့် dependency မပြောင်းသရွေ့ npm ci က network ကနေ အားလုံး ပြန်မဆွဲတော့ဘူး။ သီးခြား actions/cache က build cache directory ကို ကိုင်တယ်၊ restore-keys ကြောင့် lockfile ပြောင်းသွားရင်တောင် အနီးစပ်ဆုံး cache ကို ရနိုင်ပြီး တစ်ခါလုံး အစကနေ ပြန်မဆောက်ရဘူး။
unit job က matrix shard လေးခုနဲ့ suite ကို ခွဲတယ်။ fail-fast: false က အရေးကြီးတယ် — မထားရင် shard တစ်ခု ကျတာနဲ့ ကျန်တာတွေ ရပ်သွားပြီး၊ ပြင်ပြီး ပြန် run တဲ့အခါမှ နောက်ထပ် failure တွေကို တစ်ခုချင်း ရှာရတဲ့ သံသရာ ဝင်သွားမယ်။
e2e job က path စစ်ပြီးမှ ဆက်လုပ်တယ်။ ဒီနေရာမှာ သတိထားရမှာက skip လုပ်လိုက်တဲ့ job ဟာ required check ဖြစ်နေရင် branch protection က ဘယ်လို မှတ်လဲ ဆိုတာပါ — ဒါကြောင့် job ကို လုံးဝ မ run ဘဲ ချန်တာထက် job ကို run ပြီး အထဲက step တွေကို condition နဲ့ ကျော်တာက pull request status ကို ပိုရှင်းစေတယ်။
အတူတူ စမ်းရေးကြည့်မယ်
name: CI
on:
pull_request:
branches:
- main
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- run: npm ci
- run: npm run lint
unit:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard:
- 1
- 2
- 3
- 4
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- run: npm ci
- uses: actions/cache@v4
with:
path: .cache/build
key: build-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
build-${{ runner.os }}-
- name: Run one quarter of the unit suite
run: npm test -- --shard=${{ matrix.shard }}/4
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Decide whether end-to-end tests are needed
id: filter
run: ./scripts/paths-changed.sh src/ e2e/
- uses: actions/setup-node@v4
if: steps.filter.outputs.changed == 'true'
with:
node-version: '20'
cache: npm
- if: steps.filter.outputs.changed == 'true'
run: npm ci
- name: Run the expensive suite only when code changed
if: steps.filter.outputs.changed == 'true'
run: npm run test:e2e
pull request တစ်ခု ဖွင့်လိုက်ရင် lint, unit, e2e ဆိုတဲ့ job တွေက တစ်ပြိုင်တည်း စတယ် — needs: မရှိလို့ပါ။ unit က shard လေးခုအဖြစ် ခွဲပြီး runner လေးလုံးပေါ်မှာ တစ်ပြိုင်တည်း run တယ်၊ fail-fast: false ကြောင့် တစ်ခု ကျပေမယ့် ကျန်သုံးခုက ဆက် run ပြီး failure အားလုံးကို တစ်ခါတည်း မြင်ရမယ်။ dependency မပြောင်းရင် setup-node ရဲ့ cache က npm ci ကို ပိုမြန်စေတယ်၊ build cache ကတော့ key တိတိကျကျ မကိုက်ရင်တောင် restore-keys နဲ့ အနီးစပ်ဆုံးကို ပြန်ရနိုင်တယ်။ e2e job ကတော့ အမြဲ စပေမယ့် path filter က changed=true မထုတ်ရင် အထဲက ဈေးကြီးတဲ့ step တွေကို ကျော်သွားပြီး job က အောင်မြင်တဲ့ အနေအထားနဲ့ ပြီးသွားတယ် — required check အနေနဲ့ ကြာရှည် စောင့်နေစရာ မဖြစ်တော့ဘူး။၅ မိနစ် စမ်းကြည့်
လက်ရှိ pipeline ရဲ့ နောက်ဆုံး run ၁၀ ခုကို ကြည့်ပြီး job အလိုက် အချိန်ကို ဇယားချပါ — အရှည်ဆုံး တစ်ခုကို သာ ရွေးပါ။ အဲဒါကို shard နှစ်ခုအဖြစ် ခွဲကြည့်ပြီး wall clock ဘယ်လောက် လျော့လဲ တိုင်းပါ။ ပြီးရင် shard လေးခု၊ ရှစ်ခု တိုးကြည့်ပြီး တိုးတက်မှု ရပ်သွားတဲ့ အမှတ်ကို ရှာပါ — အဲဒီအမှတ်က ကိုယ့် repository ရဲ့ setup overhead ကို ပြောပြနေတာပါ။
သတိလေးတစ်ချက်
cache key ထဲမှာ ${{ github.sha }} လို run တိုင်း ပြောင်းတဲ့ တန်ဖိုး ထည့်မိတာ — cache က ဘယ်တော့မှ hit မဖြစ်ဘဲ upload အချိန်ပဲ ပိုကုန်တယ်
path filter နဲ့ job တစ်ခုလုံးကို skip လုပ်ထားပြီး အဲဒီ job ကို branch protection မှာ required check အဖြစ် ထားမိတာ — PR က ဘယ်တော့မှ merge လုပ်လို့ မရတော့ဘူး
GitHub Docs: Caching dependencies to speed up workflows — CI/CD with GitHub Actions