Thuta Learning
CI/CD with GitHub Actions
IntermediateDevOps & Toolsbeginner

Dependency များကို Cache လုပ်ခြင်း

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Dependency များကို Cache လုပ်ခြင်း concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး pipeline ထဲမှာ job တွေ ဘယ်လိုစီးဆင်းပြီး ဘာက gate လုပ်သလဲ ခြေရာခံနိုင်ရန်
  • Workflow file ကို ဖတ်ပြီး ဘယ် job တွေ ဘယ်အစဉ်လိုက် run မလဲ ကြိုတင်ပြောနိုင်ရန်

နားလည်ထားရမယ့် အချက်

GitHub Actions job တစ်ခု စတိုင်းတွင် runner သည် အဲဒီ job အတွက်သာ အသစ်တည်ဆောက်ခံရပြီး job ပြီးဆုံးသည်နှင့် ဖျက်သိမ်းခံရသည်။ run တစ်ခုနှင့်တစ်ခုကြားတွင် ဘာမှ မကျန်ခဲ့ပါ — node_modules မရှိ၊ ~/.npm မရှိ၊ pip wheel cache မရှိ၊ Gradle cache မရှိ။ ဒီ clean slate က ရည်ရွယ်ချက်ရှိရှိ ဒီဇိုင်းလုပ်ထားခြင်းဖြစ်ပြီး build သည် မနေ့က ကျန်ခဲ့သော state ပေါ်တွင် တိတ်တဆိတ် မှီခိုမနေအောင် ကာကွယ်ပေးသည်။ သို့သော် ကုန်ကျစရိတ်ကတော့ run တိုင်းတွင် dependency tree တစ်ခုလုံးကို registry မှ ပြန်ဒေါင်းလုဒ်ရခြင်းဖြစ်ပြီး၊ project အများစုတွင် ဒီ download သည် pipeline time ရဲ့ အကြီးဆုံးအပိုင်း ဖြစ်နေတတ်သည်။

actions/cache@v4 သည် repository အတွင်း scope ချထားသော key-value store တစ်ခုကို ပေးသည်။ Install မလုပ်ခင် directory ကို restore လုပ်ပြီး၊ job အောင်မြင်စွာပြီးဆုံးချိန်တွင် ပြန်သိမ်းသည်။ အဓိက field နှစ်ခုသာ ရှိသည် — key သည် entry ရဲ့ တိတိကျကျ identity ဖြစ်ပြီး၊ restore-keys သည် exact key မတွေ့သည့်အခါ အစဉ်လိုက် အစားထိုးရှာမည့် prefix စာရင်းဖြစ်သည်။

Lookup လုပ်ပုံမှာ ဤသို့ဖြစ်သည်။ ပထမဦးစွာ key အတိအကျ တူညီသော entry ကို ရှာသည်။ တွေ့လျှင် (exact hit) directory ကို restore လုပ်ပြီး job အဆုံးတွင် save လုပ်ခြင်းကို ကျော်သွားသည် — အကြောင်းက cache entry များသည် immutable ဖြစ်ပြီး အဲဒီ key ရှိပြီးသား ဖြစ်နေသောကြောင့်။ မတွေ့လျှင် restore-keys ကို အစဉ်လိုက် လျှောက်ကြည့်ပြီး အဲဒီ prefix နှင့် စတင်သော အသစ်ဆုံး entry ကို ယူသည်။ ဒါက partial hit ဖြစ်သည် — နည်းနည်းဟောင်းနေသော directory တစ်ခု ရရှိပြီး package manager က ကွာဟချက်ကိုသာ ဖြည့်စွက်ကာ၊ job အဆုံးတွင် exact key အောက်၌ entry အသစ်တစ်ခု ရေးသိမ်းသည်။

အရေးအကြီးဆုံးက key ထဲတွင် lockfile ရဲ့ hash — hashFiles('**/package-lock.json') — မဖြစ်မနေ ပါဝင်ရမည်ဖြစ်သည်။ ဒါဟာ performance tuning မဟုတ်ဘဲ correctness ကိစ္စဖြစ်သည်။ node-cache ကဲ့သို့ ဘယ်တော့မှ မပြောင်းလဲသော key သည် အမြဲ hit ဖြစ်နေမည်၊ ထို့ကြောင့် dependency အသစ်တစ်ခု ထည့်လိုက်သည့်တိုင် ဘယ်တော့မှ install မဖြစ်တော့ဘဲ၊ CI သည် မည်သည့် machine ပေါ်တွင်မှ မတည်ရှိသော dependency tree တစ်ခုနှင့် စမ်းသပ်ကာ အစိမ်းရောင် ပြနေမည်။ ခြေရာမကျသော cache သည် cache လုံးဝမရှိသည်ထက် ပိုဆိုးသည် — cache မရှိလျှင် နှေးရုံသာဖြစ်ပြီး၊ stale cache ကတော့ သင့်ကို လိမ်ညာနေခြင်း ဖြစ်သည်။

text
CACHE LOOKUP AND SAVE FLOW
--------------------------
  restore step: key = <os>-npm-<hash of package-lock.json>
        |
        v
  +--------------------------------+
  | exact key exists in the cache? |
  +--------------------------------+
     | yes                   | no
     v                       v
  EXACT HIT              +----------------------------+
  directory restored     | walk restore-keys prefixes |
  cache-hit = true       |   "<os>-npm-"              |
  save at end SKIPPED    +----------------------------+
     |                      | match          | none
     |                      v                v
     |                 PARTIAL HIT          MISS
     |                 older entry          empty directory
     |                 cache-hit = false    cache-hit = false
     |                      |                |
     |                      +-------+--------+
     |                              v
     |                    install runs and reconciles
     |                              v
     |                    end of job: SAVE a new entry
     |                    under the exact key
     v
  nothing saved (that key is already stored, entries are immutable)

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

Install step က CI ရဲ့ အနှေးဆုံးအပိုင်းဖြစ်နေသော Next.js repo တစ်ခုကို စဉ်းစားကြည့်ပါ။ npm ci မလုပ်ခင်တွင် actions/cache@v4 ကို path: ~/.npm ဖြင့် ထည့်ပါ — node_modules မဟုတ်ဘဲ npm ရဲ့ download cache ကို cache လုပ်ခြင်းဖြစ်သည်။ ~/.npm ကို cache လုပ်တာက ပိုလုံခြုံသည်၊ အကြောင်းက npm ci သည် package-lock.json ကို ဆက်ဖတ်ပြီး node_modules ကို deterministic ဖြစ်စွာ ပြန်တည်ဆောက်နေဆဲဖြစ်ကာ network ကိုသာ ကျော်လိုက်ခြင်းဖြစ်သည်။ node_modules ကို တိုက်ရိုက် cache လုပ်လျှင် OS သို့မဟုတ် Node version တစ်မျိုးအတွက် build လုပ်ထားသော native module များ ပြန်ဝင်လာနိုင်ပြီး၊ cache ပြဿနာနှင့် လုံးဝမတူသော error များဖြင့် ကျဆုံးတတ်သည်။

Key မှာ ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} ဖြစ်သည်။ runner.os ပါရခြင်းက macOS runner နှင့် Linux runner တို့ entry တစ်ခုတည်းကို ဘယ်တော့မှ မမျှဝေရန်ဖြစ်သည်။ Hash သည် lockfile ကို တစ်ယောက်ယောက် ပြင်လိုက်သည်နှင့် ချက်ချင်း ပြောင်းသွားပြီး၊ အဲဒီအချိန်ကပင် cache ထဲက download set သည် မှားသွားချိန် ဖြစ်သည်။

restore-keys မှာ prefix တစ်ခုတည်း ${{ runner.os }}-npm- ဖြစ်သည်။ Dependency တစ်ခု bump လုပ်ပြီးနောက် ပထမဆုံး run တွင် exact key က miss ဖြစ်ပြီး prefix က မနေ့က entry ကို ဆွဲယူပေးကာ npm သည် အမှန်တကယ် ပြောင်းလဲသွားသော package များကိုသာ ဒေါင်းလုဒ်လုပ်သည်။ ထို့နောက် key အသစ်အောက်တွင် entry အသစ် သိမ်းသည်။

အဖွဲ့တော်တော်များများ မှားတတ်သည့်အချက် — cache သည် branch အလိုက် scope ချထားပြီး default branch သို့သာ fallback ရှိသည်။ Feature branch တစ်ခုသည် main ရဲ့ cache ကို ဖတ်နိုင်သော်လည်း feature branch နှစ်ခုအချင်းချင်း တစ်ယောက်ရဲ့ cache ကို မဖတ်နိုင်ပါ။

အတူတူ စမ်းရေးကြည့်မယ်

yaml
name: CI with dependency cache

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Restore the npm download cache
        id: npm-cache
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      - name: Install dependencies
        run: npm ci

      - name: Report whether the exact key was hit
        run: echo "cache-hit=${{ steps.npm-cache.outputs.cache-hit }}"

      - name: Run tests
        run: npm test
You should see
Workflow သည် test job တစ်ခုတည်းကို run သည်။ Lockfile တစ်ခုအတွက် ပထမဆုံး run တွင် exact key နှင့် prefix နှစ်ခုစလုံး miss ဖြစ်ပြီး npm ci က အားလုံးကို ဒေါင်းလုဒ်လုပ်ကာ၊ job အဆုံးတွင် exact key အောက်၌ entry အသစ်တစ်ခု သိမ်းသည်။ Lockfile မပြောင်းဘဲ နောက်တစ်ခါ run လျှင် exact key hit ဖြစ်ကာ npm ci မလုပ်ခင် ~/.npm ကို restore လုပ်ပြီး install သည် registry ကို မထိတော့ဘဲ ပြီးဆုံးသည်၊ အဲဒီ key ရှိပြီးသားဖြစ်၍ save မလုပ်တော့ပါ — steps.npm-cache.outputs.cache-hit သည် true ဖြစ်သည်။ တစ်ယောက်ယောက်က package-lock.json ကို ပြောင်းလိုက်လျှင် exact key မရှိတော့သဖြင့် restore-keys က prefix နှင့်ကိုက်သော အသစ်ဆုံး entry သို့ ပြန်ဆင်းသွားပြီး npm ci သည် ကွာဟချက်ကိုသာ ဒေါင်းလုဒ်လုပ်ကာ cache-hit သည် false ဖြစ်ပြီး entry အသစ် ရေးသိမ်းသည်။ Job အောင်/ရှုံး ရလဒ်သည် cache ပေါ်တွင် ဘယ်တော့မှ မမူတည်ပါ — ဒေါင်းလုဒ်ပမာဏသာ ပြောင်းလဲသည်။

၅ မိနစ် စမ်းကြည့်

Cache key ကို hashFiles မပါဘဲ ${{ runner.os }}-npm ကဲ့သို့ constant တစ်ခုသို့ ပြောင်းပြီး၊ package.json နှင့် package-lock.json ထဲသို့ dependency အသစ်တစ်ခု ထည့်သော commit တစ်ခု push လုပ်ပါ။ npm ci ဘယ်လိုပြုမူသလဲ ကြည့်ပါ — ပြီးရင် hashFiles key ကို ပြန်ထည့်ပြီး constant key သည် ဘာကြောင့် performance ရွေးချယ်မှုမဟုတ်ဘဲ correctness bug ဖြစ်ကြောင်း တစ်ကြောင်းတည်းနှင့် ရှင်းပြပါ။ ထို့နောက် သင့် build tool ရဲ့ cache directory အတွက် cache step ဒုတိယတစ်ခု ထပ်ထည့်ပြီး သီးခြား key ပေးပါ။

သတိလေးတစ်ချက်

Lockfile hash မပါသော cache key (ဥပမာ ${{ runner.os }}-npm) — run တိုင်း hit ဖြစ်နေသဖြင့် dependency အသစ်ကို ဘယ်တော့မှ install မလုပ်တော့ဘဲ CI သည် တခြားဘယ်နေရာမှာမှ မရှိသော dependency tree နှင့် အောင်မြင်နေသည်။

Package manager ရဲ့ download directory အစား node_modules ကို cache လုပ်ခြင်း — OS သို့မဟုတ် Node version တစ်မျိုးအတွက် compile လုပ်ထားသော native module များ ပြန်ဝင်လာပြီး cache ပြဿနာနှင့် လုံးဝမတူသော error ဖြင့် ကျဆုံးသည်။

GitHub Docs - Caching dependencies to speed up workflowsCI/CD with GitHub Actions

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • Lockfile hash မပါသော cache key (ဥပမာ ${{ runner.os }}-npm) — run တိုင်း hit ဖြစ်နေသဖြင့် dependency အသစ်ကို ဘယ်တော့မှ install မလုပ်တော့ဘဲ CI သည် တခြားဘယ်နေရာမှာမှ မရှိသော dependency tree နှင့် အောင်မြင်နေသည်။
  • Package manager ရဲ့ download directory အစား node_modules ကို cache လုပ်ခြင်း — OS သို့မဟုတ် Node version တစ်မျိုးအတွက် compile လုပ်ထားသော native module များ ပြန်ဝင်လာပြီး cache ပြဿနာနှင့် လုံးဝမတူသော error ဖြင့် ကျဆုံးသည်။
  • Workflow ကို production branch ပေါ် တိုက်ရိုက်မစမ်းဘဲ branch (သို့) test repository တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

Cache key ကို hashFiles မပါဘဲ ${{ runner.os }}-npm ကဲ့သို့ constant တစ်ခုသို့ ပြောင်းပြီး၊ package.json နှင့် package-lock.json ထဲသို့ dependency အသစ်တစ်ခု ထည့်သော commit တစ်ခု push လုပ်ပါ။ npm ci ဘယ်လိုပြုမူသလဲ ကြည့်ပါ — ပြီးရင် hashFiles key ကို ပြန်ထည့်ပြီး constant key သည် ဘာကြောင့် performance ရွေးချယ်မှုမဟုတ်ဘဲ correctness bug ဖြစ်ကြောင်း တစ်ကြောင်းတည်းနှင့် ရှင်းပြပါ။ ထို့နောက် သင့် build tool ရဲ့ cache directory အတွက် cache step ဒုတိယတစ်ခု ထပ်ထည့်ပြီး သီးခြား key ပေးပါ။

You'll know it worked when: Workflow သည် test job တစ်ခုတည်းကို run သည်။ Lockfile တစ်ခုအတွက် ပထမဆုံး run တွင် exact key နှင့် prefix နှစ်ခုစလုံး miss ဖြစ်ပြီး npm ci က အားလုံးကို ဒေါင်းလုဒ်လုပ်ကာ၊ job အဆုံးတွင် exact key အောက်၌ entry အသစ်တစ်ခု သိမ်းသည်။ Lockfile မပြောင်းဘဲ နောက်တစ်ခါ run လျှင် exact key hit ဖြစ်ကာ npm ci မလုပ်ခင် ~/.npm ကို restore လုပ်ပြီး install သည် registry ကို မထိတော့ဘဲ ပြီးဆုံးသည်၊ အဲဒီ key ရှိပြီးသားဖြစ်၍ save မလုပ်တော့ပါ — steps.npm-cache.outputs.cache-hit သည် true ဖြစ်သည်။ တစ်ယောက်ယောက်က package-lock.json ကို ပြောင်းလိုက်လျှင် exact key မရှိတော့သဖြင့် restore-keys က prefix နှင့်ကိုက်သော အသစ်ဆုံး entry သို့ ပြန်ဆင်းသွားပြီး npm ci သည် ကွာဟချက်ကိုသာ ဒေါင်းလုဒ်လုပ်ကာ cache-hit သည် false ဖြစ်ပြီး entry အသစ် ရေးသိမ်းသည်။ Job အောင်/ရှုံး ရလဒ်သည် cache ပေါ်တွင် ဘယ်တော့မှ မမူတည်ပါ — ဒေါင်းလုဒ်ပမာဏသာ ပြောင်းလဲသည်။

Dependency များကို Cache လုပ်ခြင်း | Thuta Learning