Azimjon Abdurasulov

Bazel 10: Cache — action key, disk cache, remote cache, Build without the Bytes va cache-miss detektivligi

📦 Bazel iOS — Mukammal Qo‘llanma & Amaliyot

Bazel 10: Cache — action key, disk cache, remote cache, Build without the Bytes va cache-miss detektivligi

Holat: 2026-yil oktabr. Bu dars Bazel cache'ining butun mexanizmini ochadi: action key qanday hisoblanadi, nima cache'lanadi (Action Cache + CAS), cache qatlamlari, --disk_cache va --remote_cache, Build without the Bytes, va eng muhimi — cache nega "tegmay" qolganini execution log orqali topish. Kod misollari Bazel 9.3.0 (Bazelisk 1.29.0), rules_apple 5.3.0, rules_swift 4.2.1 va bazel-remote 2.6.3 bilan, Xcode 27.1'da, Apple M4 Pro (12 yadro, 24 GB) da tekshirilgan.

Bu darsdan keyin siz: bir marta kompilyatsiya qilingan natijani butun jamoa va CI bilan ulashib, build vaqtini daqiqalardan soniyalarga tushira olasiz — va cache ishlamay qolganda sababini bir daqiqada topa olasiz.

  • Action key — cache'ning yuragi — qaysi "ingredientlar"dan hisoblanishini aniq bilib olasiz (buyruq qatori, kirish fayllar digest'i, environment, platform).
  • --disk_cache bilan bitta Mac ichida, --remote_cache bilan butun jamoa bilan natijalarni ulashasiz — ikkalasini ham o'z qo'lingiz bilan o'rnatasiz.
  • Haqiqiy bazel-remote serverini ko'tarib, "ikkinchi dasturchi" cache'dan to'liq hit olishini o'z ko'zingiz bilan ko'rasiz.
  • Build without the Bytes (--remote_download_toplevel/minimal) nima uchun oraliq fayllarni yuklab olmasligini tushunasiz.
  • Cache-miss detektivi bo'lasiz: ikki build'ning execution log'ini solishtirib, aybdorni (absolute path, Xcode versiyasi, environment, stamping) topasiz.

Mundarija

  1. Nega cache kerak — og'riqdan boshlaymiz
  2. Action key — cache'ning yuragi
  3. Nima cache'lanadi: Action Cache va CAS
  4. Cache qatlamlari: in-memory → local → disk → remote → RBE
  5. Disk cache — eng oson tezlik (amaliy + o'lchov)
  6. Remote cache — natijani jamoa bilan ulashish
  7. Build without the Bytes
  8. Rollar: dev faqat o'qiydi, CI yozadi
  9. Remote cache provayderlari
  10. Cache-miss detektivligi: execution log bilan
  11. iOS'ga xos cache qotillari
  12. Real tajriba: jamoalar nima deydi
  13. Amaliy mashqlar
  14. O'zingizni tekshiring
  15. Xulosa
  16. Manbalar

1. Nega cache kerak — og'riqdan boshlaymiz {#nega}

9-darsda biz Bazel loyihasini Xcode bilan bog'ladik. Endi jamoaga qaraymiz. Tasavvur qiling: siz ertalab git pull qildingiz, 40 ta modul o'zgargan, va Xcode "Building..." deb 15 daqiqa aylanadi. Holbuki o'sha 40 modulni kecha hamkasbingiz allaqachon kompilyatsiya qilgan edi. Nega siz uni qaytadan quryapsiz?

Mana shu — build tizimlarining eng katta isrofi: har bir dasturchi va har bir CI runner bir xil ishni qayta-qayta bajaradi. Xcode'da standart holatda jamoa o'rtasida ulashiladigan build cache yo'q — har kimning DerivedData'si o'ziniki.

Bazel bunga boshqacha qaraydi. Bazel uchun build — bu e'lon qilingan kirishlardan chiqishlarga sof funksiya. Agar kirishlar (manba fayllar, flaglar, kompilyator) bir xil bo'lsa, chiqish ham bir xil bo'ladi — kim, qachon, qaysi mashinada qurganidan qat'i nazar. Bu esa bitta katta imkoniyatni ochadi: natijani bir marta hisoblab, keyin cache'dan qayta ishlatish.

Keling, buni raqamlarda ko'raylik. Shu darsda biz kichik 3 modulli iOS ilova ("Kutubxona") quramiz va o'lchaymiz:

HolatVaqtNima bo'ldi
Birinchi build (cache bo'sh)58.3 shamma narsa noldan kompilyatsiya qilindi
bazel clean + disk cache'dan1.5 s188 ta action disk cache'dan olindi
Bitta faylni o'zgartirib qayta build2.8 sfaqat ta'sirlangan modul qayta qurildi
"Ikkinchi dasturchi" remote cache'dan6.3 s188 ta action tarmoq cache'idan olindi

📊 Real raqamlar: Yuqoridagi barcha raqamlar shu darsda, Apple M4 Pro (12 yadro, 24 GB), Xcode 27.1, Bazel 9.3.0 da o'lchangan. "58.3 s → 1.5 s" — bu 38 barobar tezlik, lekin bu sehr emas: Bazel shunchaki ikkinchi marta ishni bajarmadi, tayyor natijani oldi. Qanday qilib "tayyor natija"ni topishni biladi? Javob — action key.

🚀 Motivatsiya: Cache — Bazel'ning eng katta "super-kuchi". Monzo (Buyuk Britaniya neobanki) o'z iOS loyihasida (deyarli 200 modul, 45+ iOS muhandis) PR'larning o'rtacha CI vaqtini 52 daqiqadan 15 daqiqaga (−71%) tushirdi — bunga asosiy sabab, main branch'da har 30 daqiqada isitiladigan (warm qilinadigan) umumiy build cache (manba: Tuist case study, 2026-05-26, §12'da batafsil). Bazel'da bu mexanizmni siz ham, hisob/obuna shart bo'lmagan holda, bugun o'z mashinangizda o'rnata olasiz.

Darsning qolgan qismida biz: (1) action key qanday hisoblanishini mexanizm darajasida tushunamiz, (2) disk va remote cache'ni o'z qo'limiz bilan o'rnatamiz, (3) cache buzilganda — bu eng ko'p vaqt yeydigan muammo — sababini topishni o'rganamiz.


2. Action key — cache'ning yuragi {#action-key}

Birinchi tushunish kerak bo'lgan narsa: Bazel target'larni emas, action'larni cache'laydi. Bitta swift_library bir nechta action'ga bo'linadi: Swift modulni kompilyatsiya qilish, statik kutubxonani linklash va hokazo. Har bir action — bu kirishlari, buyruq qatori va chiqish fayllari aniq e'lon qilingan bitta buyruq (masalan, bitta swiftc chaqiruvi).

Har bir action uchun Bazel bitta action key hisoblaydi — bu action'ning "barmoq izi", SHA-256 hash. Rasmiy hujjat (glossary) buni shunday ta'riflaydi:

"Action key — action'ning cache kaliti. Action metama'lumotlari asosida hisoblanadi: bajariladigan buyruq, kompilyator flaglari, kutubxona joylashuvi yoki tizim header'lari — action'ga qarab. Bu Bazel'ga har bir action'ni deterministik ravishda cache'lash yoki bekor qilish imkonini beradi." — bazel.build glossary

Action key qanday hisoblanadi: buyruq qatori, kirish fayllar digest'i, environment, platform va chiqish nomlari SHA-256 orqali bitta kalitga aylanadi; kalit Action Cache'da topilsa — hit, topilmasa — miss.

Yuqoridagi rasmda action key'ning "retsepti" ko'rsatilgan: beshta ingredientning istalgan biri o'zgarsa — butunlay yangi kalit chiqadi, demak cache miss bo'ladi. Keling, buni o'z ko'zimiz bilan ko'raylik. bazel aquery buyrug'i har bir action'ning to'liq retseptini chop etadi.

Avval loyihamizni tanishtiramiz. Uchta modul:

kutubxona/
├── MODULE.bazel
├── .bazelrc
├── Core/            # sof Swift: Book modeli + validator
│   ├── BUILD.bazel
│   └── Sources/{Book.swift, BookValidator.swift}
├── Catalog/         # Core'ga bog'liq: aktor-asosli katalog
│   ├── BUILD.bazel
│   └── Sources/{Catalog.swift, SampleData.swift}
└── App/             # SwiftUI ilova (Catalog + Core)
    ├── BUILD.bazel
    ├── Info.plist
    └── Sources/{KutubxonaApp.swift, BookListView.swift}

Core modulining Swift kompilyatsiya action'ini so'rab ko'ramiz:

bazel aquery 'mnemonic("SwiftCompile", //Core:Core)'
action 'Compiling Swift module //Core:Core'
  Mnemonic: SwiftCompile
  Target: //Core:Core
  Configuration: darwin_arm64-fastbuild
  Execution platform: @@platforms//host:host
  ActionKey: 88ac68a9e759424541047bd31edc673b66f33bf0804a604a897e10eb15bb6bff
  Inputs: [Core/Sources/Book.swift, Core/Sources/BookValidator.swift,
           .../tools/worker/worker, .../crosstool/wrapped_clang, ...]
  Outputs: [.../Core.swiftmodule, .../Book.swift.o, .../BookValidator.swift.o, ...]
  Environment: [APPLE_SDK_PLATFORM=MacOSX, APPLE_SDK_VERSION_OVERRIDE=27.0,
                PATH=/bin:/usr/bin:/usr/local/bin,
                XCODE_VERSION_OVERRIDE=27.1.0.27A9275]
  Command Line: (exec .../worker swiftc -target arm64-apple-macos27.0 -sdk ...
    -DDEBUG -Onone -enable-testing ... )

Shu bitta chiqishda action key'ning hamma ingredientlari ko'rinadi:

  • **ActionKey: 88ac68a9...** — action'ning barmoq izi.
  • **Inputs** — kirish fayllar. E'tibor bering: Bazel fayl nomini emas, uning kontentining SHA-256 digest'ini hisobga oladi. Book.swift ichida bitta harf o'zgarsa, digest o'zgaradi, action key o'zgaradi.
  • **Environment — muhit o'zgaruvchilari. Mana bu yerda iOS uchun eng muhim narsa yashiringan:** XCODE_VERSION_OVERRIDE=27.1.0.27A9275. Ya'ni Xcode versiyasi to'g'ridan-to'g'ri action key'ning bir qismi. Boshqa Xcode'da qurgan hamkasbingiz butunlay boshqa kalit oladi — va cache hit olmaydi. (Buni §11'da batafsil ko'ramiz.)
  • **Command Line** — kompilyator chaqiruvi. -Onone (fastbuild) yoki -O (opt) — har qanday flag farqi kalitni o'zgartiradi.
  • **Configuration va Execution platform** — qaysi rejim va qaysi platforma uchun.

Buni isbotlash oson. Faqat kompilyatsiya rejimini fastbuilddan optga o'zgartiramiz — manba fayllar o'zgarmaydi, lekin kompilyator flaglari o'zgaradi:

bazel aquery 'mnemonic("SwiftCompile", //Core:Core)' | grep ActionKey
# ActionKey: 88ac68a9e759424541047bd31edc673b66f33bf0804a604a897e10eb15bb6bff

bazel aquery 'mnemonic("SwiftCompile", //Core:Core)' -c opt | grep ActionKey
# ActionKey: 417fe0921758cbf4e495315c27e10d7f85524d9e461523e1475449f065360704

Manba kod bir xil, lekin kalit butunlay boshqa — 88ac68a9... → 417fe092.... Bu nega muhim? Chunki debug build va release build bir-birining cache'ini "buzmaydi" — ularning kalitlari alohida. Siz -c opt bilan qurgan narsa -c dbg bilan qurgan narsaga aralashmaydi.

Va aksincha — agar hech narsa o'zgarmasa, kalit ham o'zgarmaydi. Yuqoridagi birinchi buyruqni qancha marta takrorlasangiz ham 88ac68a9... chiqadi. Mana shu determinizm — cache ishlashining asosi.

🧠 Esda tuting: Action key = action retseptining SHA-256 hash'i. Retseptga kiradi: buyruq qatori, har bir kirish faylning kontent digest'i, environment o'zgaruvchilari, execution platform va chiqish fayl nomlari. Bittasi o'zgarsa — yangi kalit, yangi kompilyatsiya. Hammasi bir xil bo'lsa — cache hit, kim qurganidan qat'i nazar.


3. Nima cache'lanadi: Action Cache va CAS {#ac-cas}

Action key'ni tushundik. Endi savol: Bazel bu kalit yordamida nimani saqlaydi? Rasmiy hujjat (remote/caching.mdx) cache ikki xil ma'lumotni saqlashini aytadi:

  • Action Cache (AC) — action hash'laridan (ya'ni action key'lardan) action natijasi metama'lumotiga xarita. "Bu kalitli action'ni men bajarganman, natijasi — mana bu fayllar (nomlari + digest'lari), exit code 0."
  • Content-Addressable Store (CAS) — chiqish fayllarining o'zi, kontenti bo'yicha manzillangan. Fayl o'z SHA-256 digest'i bo'yicha saqlanadi.

Oddiy qilib aytganda: AC "retsept → qanday natija chiqqan"ini aytadi, CAS esa natijaning haqiqiy baytlarini saqlaydi. Ikki bosqich ataylab ajratilgan, chunki bir xil fayl (masalan, bir xil .swiftmodule) ko'p action natijasida paydo bo'lishi mumkin — CAS uni bir marta saqlaydi.

Mana Tuist muhandislari o'z Bazel cache hujjatlarida keltirgan AC yozuvining namunasi (soddalashtrilgan):

{
  "actionDigest": { "hash": "e3d9…7a02", "sizeBytes": "148" },
  "actionResult": {
    "outputFiles": [
      {
        "path": "bin/Networking.swiftmodule",
        "digest": { "hash": "5c0e…91d4", "sizeBytes": "182344" }
      },
      {
        "path": "bin/Client.o",
        "digest": { "hash": "9f2c…b41a", "sizeBytes": "48213" }
      }
    ],
    "exitCode": 0
  }
}

_(Manba: tuist.dev/blog/2026/09/09/bazel)_

Cache hit qanday kechishini qadamma-qadam ko'rib chiqaylik. Bazel bitta action'ni bajarish oldidan:

  1. Action key'ni hisoblaydi (§2'dagi retseptdan).
  2. Bu kalitni Action Cacheda qidiradi.
  3. Topilsa (hit): AC yozuvidan chiqish fayllarining digest'larini oladi, keyin ularning baytlarini CASdan tortadi. Action umuman bajarilmaydi — na swiftc ishga tushadi, na protsessor band bo'ladi.
  4. Topilmasa (miss): action lokal (yoki remote) bajariladi, chiqish fayllari CASga, kalit→natija xaritasi esa ACga yoziladi. Keyingi safar bu action hit beradi.

💡 Pro tip: Action'ning stdout/stderr'i ham cache'da saqlanadi. Shuning uchun cache hit bo'lgan test ham xuddi haqiqiy ishga tushgandek log chiqaradi — bu sizni chalg'itmasin. "Cache ishladimi?" degan savolga to'g'ri javob — stdout emas, build oxiridagi INFO: qatori (188 remote cache hit kabi). Buni §10'da detektiv ishida ishlatamiz.

Bu ikkala omborni (AC + CAS) Bazel turli joylarda saqlashi mumkin: xotirada, lokal diskda, umumiy disk papkada yoki tarmoqdagi serverda. Mana shu "joylar" — cache qatlamlari, ularni endi ko'rib chiqamiz.


4. Cache qatlamlari: in-memory → local → disk → remote → RBE {#qatlamlar}

Bazel har bir action uchun cache'ni qat'iy tartibda, eng tez va tor qatlamdan eng keng va sekin qatlamgacha qidiradi. Bitta qatlamda hit bo'lsa — pastga tushmaydi.

Cache qatlamlari: 1-in-memory graph (Skyframe), 2-local action cache, 3-disk cache, 4-remote cache, 5-remote execution; yuqoridan pastga qidiruv tartibi, har qatlam yonida scope va tezlik.

№QatlamQayerdaScope (kim ulashadi)Hisob/server kerakmi
1In-memory graph (Skyframe)ishlab turgan Bazel server, RAMshu processyo'q
2Local action cacheoutput base, diskdashu workspaceyo'q
3Disk cache (--disk_cache)~/.cache/bazel-diskshu Mac, barcha checkoutyo'q
4Remote cache (--remote_cache)grpc/http serverbutun jamoa + CIserver kerak
5Remote execution (RBE)remote ishchi mashinalarjamoa + bajarishmacOS ishchi kerak
  • 1 — In-memory (Skyframe). Bazel server xotirasida butun action graph turadi. Bir sessiya ichida ikkinchi marta bazel build qilsangiz va hech narsa o'zgarmagan bo'lsa — javob deyarli bir zumda keladi. Bu server o'chsa yo'qoladi (bazel shutdown yoki --max_idle_secs).
  • 2 — Local action cache. Output base ichidagi diskda yotadi, server qayta ishga tushsa ham saqlanadi. Bu Bazel'ning incremental build'ining asosi.
  • 3 — Disk cache. --disk_cache=<papka> bilan yoqiladi. Bitta Mac'dagi barcha workspace va checkout'lar u bilan bo'lishadi. Branch almashtirganda eng foydali — eski branch'ning natijalari papkada turaveradi.
  • 4 — Remote cache. --remote_cache=<url> bilan tarmoqdagi serverga ulanadi. Endi natijalar butun jamoa va CI o'rtasida bo'lishiladi. Bu darsning asosiy mavzusi.
  • 5 — Remote execution (RBE). Faqat cache emas — kompilyatsiyaning o'zi ham uzoqdagi ishchi mashinalarda bajariladi. iOS uchun bu murakkabroq (macOS ishchilar kerak); buni 18-darsda ko'ramiz.

🧠 Esda tuting: 1–3 qatlamlar lokal, hech qanday server yoki hisob talab qilmaydi — ularni hoziroq ishlatishingiz mumkin. 4–5 qatlamlar server talab qiladi. Disk cache (3) va remote cache (4) bir vaqtda ishlashi mumkin: disk L1, remote L2 bo'ladi.


5. Disk cache — eng oson tezlik (amaliy + o'lchov) {#disk-cache}

Eng arzon g'alaba — disk cache. Bitta flag, hisob kerak emas, va u darhol branch almashtirish va parallel checkout'larni tezlashtiradi. Darsning .bazelrc faylida biz uni butun loyiha uchun yoqib qo'yamiz:

# .bazelrc
common --disk_cache=~/.cache/bazel-disk

💡 Pro tip: ~ belgisi --disk_cache da $HOMEga kengayadi (buni biz tekshirdik). Shuning uchun bu qatorni loyihaning .bazelrc fayliga yozib, git'ga commit qilsangiz — har bir dasturchida o'z uyida to'g'ri papkaga tushadi. common prefiksi uni build, test, run — hammasiga qo'llaydi.

Endi o'lchaylik. Toza ("cold") o'lchov uchun yangi output base va bo'sh disk cache papkasidan foydalanamiz (repozitoriy yuklamalari oldindan cache'langan edi, shuning uchun bu vaqt tarmoqqa bog'liq emas):

# (1) COLD: yangi output base + bo'sh disk cache
bazel --output_base=/tmp/ob_disk build //App:Kutubxona --disk_cache=/tmp/dc_fresh
INFO: Elapsed time: 58.266s, Critical Path: 23.66s
INFO: 353 processes: 165 internal, 184 darwin-sandbox, 1 local, 3 worker.
INFO: Build completed successfully, 353 total actions

58 soniya. Vaqtning katta qismi — rules_swift'ning Swift kompilyatori uchun "persistent worker" vositasini (abseil kutubxonasidan C++ kod) noldan qurish; bu mashinaga/cache'ga bir marta to'lanadigan narx. E'tibor bering: 184 darwin-sandbox — ya'ni 184 ta action lokal sandbox'da bajarildi (cache hit bo'lmadi, chunki cache bo'sh edi). Lekin bajarilgani sari ular disk cache'ga yozildi.

Endi bazel clean qilamiz (lokal chiqishlarni o'chiradi, lekin disk cache papkasiga tegmaydi) va qayta quramiz:

# (2) WARM: bazel clean, keyin disk cache'dan o'qib qayta build
bazel --output_base=/tmp/ob_disk clean
bazel --output_base=/tmp/ob_disk build //App:Kutubxona --disk_cache=/tmp/dc_fresh
INFO: Elapsed time: 1.502s, Critical Path: 0.52s
INFO: 353 processes: 188 disk cache hit, 165 internal.
INFO: Build completed successfully, 353 total actions

58.3 s → 1.5 s. 188 disk cache hit — ya'ni 188 ta action'ning natijasi diskdagi cache papkasidan to'g'ridan-to'g'ri olindi, hech narsa qayta kompilyatsiya qilinmadi. Qolgan 165 internal — bu symlink yaratish kabi mayda ichki ishlar, ular cache'lanmaydi.

Oxirgi o'lchov — kundalik hayot: bitta faylni o'zgartirib qayta quramiz:

# (3) INCREMENTAL: App/Sources/BookListView.swift ga bitta qator qo'shamiz
echo "// kichik o'zgarish" >> App/Sources/BookListView.swift
bazel --output_base=/tmp/ob_disk build //App:Kutubxona --disk_cache=/tmp/dc_fresh
INFO: Elapsed time: 2.756s, Critical Path: 2.65s
INFO: 2 processes: 4 action cache hit, 1 internal, 1 worker.
INFO: Build completed successfully, 2 total actions

Faqat 2 ta action bajarildi: o'zgargan AppLib modulini qayta kompilyatsiya qilish va ilovani qayta bundle qilish. Core va Catalog — tegmagan — ular cache'dan keldi (4 action cache hit). Mana shu Bazel'ning incremental build'i: u faylni emas, uning action graph'dagi o'rnini biladi va faqat ta'sirlangan action'larni qayta ishlaydi.

Bu o'lchovlar diagrammada:

HolatVaqtCache hit'larIzoh
Cold (bo'sh cache)58.3 s0hamma narsa noldan qurildi
Warm (disk cache'dan)1.5 s188 disk cache hitbazel cleandan keyin
Incremental (1 fayl)2.8 s4 action cache hitfaqat AppLib qayta qurildi

Disk cache o'lchami bor-yo'g'i 30 MB bo'ldi. Arzon narx, katta foyda.

⚠️ Diqqat: Disk cache faqat bitta Mac ichida ishlaydi. Hamkasbingiz boshqa kompyuterda o'sha ishni qaytadan quradi. Jamoaviy foyda uchun — keyingi qadam: remote cache. Shuningdek, disk cache cheksiz o'smaydi: Bazel 7.4+ da --experimental_disk_cache_gc_max_size va --experimental_disk_cache_gc_max_age bilan avtomatik tozalashni yoqish mumkin (idle vaqtda ishlaydi).


6. Remote cache — natijani jamoa bilan ulashish {#remote-cache}

Disk cache bitta Mac'da qolib ketdi. Endi uni butun jamoa va CI bilan bo'lishamiz. Buning uchun tarmoqda bitta cache serveri turishi kerak. Rasmiy hujjatga ko'ra, server ikki turdagi ma'lumotni saqlaydi (biz buni §3'da ko'rdik): Action Cache va CAS.

Eng sodda protokol — HTTP/1.1. Bazel action natijasining metama'lumotini /ac/<hash> yo'lida, chiqish fayllarini /cas/<hash> yo'lida GET/PUT qiladi. Ya'ni oddiy GET/PUT'ni qo'llab-quvvatlaydigan har qanday server (nginx WebDAV, S3, Google Cloud Storage) remote cache bo'la oladi. Lekin biz maxsus, yengil va Bazel uchun yaratilgan bazel-remote serverini ishlatamiz — u Homebrew orqali bir buyruqda o'rnatiladi:

brew install bazel-remote     # bizda: bazel-remote 2.6.3

Endi haqiqiy "ikki dasturchi" ssenariysini o'tkazamiz. Serverni ko'taramiz va uni ikki alohida papkadagi klonga ulaymiz.

1-qadam — serverni ishga tushiramiz (HTTP :8080, gRPC o'chirilgan, maksimum 5 GiB):

bazel-remote --dir /tmp/remote_cache_data --max_size 5 \
  --http_address localhost:8080 --grpc_address none &
# probe: curl http://localhost:8080/status  →  HTTP 200

2-qadam — "Dasturchi 1" cache'ni to'ldiradi. Disk cache'ni ataylab o'chiramiz (--disk_cache=), shunda hamma narsa remote serverga ketadi va biz faqat remote hit'larni ko'ramiz (lokal hit'lar "maskirovka" qilmaydi):

bazel --output_base=/tmp/ob_dev1 build //App:Kutubxona \
  --disk_cache= --remote_cache=http://localhost:8080 --remote_upload_local_results
INFO: Elapsed time: 38.574s, Critical Path: 18.45s
INFO: 353 processes: 165 internal, 184 darwin-sandbox, 1 local, 3 worker.
INFO: Build completed successfully, 353 total actions

38.6 soniya — Dasturchi 1 hamma narsani kompilyatsiya qildi (184 darwin-sandbox) va natijalarni serverga yukladi. Serverni tekshiramiz:

remote_cache_data hajmi:  6.1 MB
fayllar soni:  870

6.1 MB, 870 ta fayl — bu AC va CAS yozuvlari.

3-qadam — "Dasturchi 2" keladi. Bu boshqa papkadagi klon (dev2_clone/), yangi output base, disk cache yana o'chirilgan. Ya'ni uning lokalida hech narsa yo'q — faqat remote serverga ulanadi:

bazel --output_base=/tmp/ob_dev2 build //App:Kutubxona \
  --disk_cache= --remote_cache=http://localhost:8080
INFO: Elapsed time: 6.268s, Critical Path: 0.53s
INFO: 353 processes: 188 remote cache hit, 165 internal.
INFO: Build completed successfully, 353 total actions

Mana shu daqiqa. Dasturchi 2 — boshqa papkada, toza mashinaga o'xshash holatda — 6.3 soniyada bitta ham swiftc ishga tushirmasdan ilovani yig'di. 188 remote cache hit — hamma action'lar Dasturchi 1 serverga yuklagan natijalardan olindi, hatto 38 soniyalik worker kompilyatsiyasi ham. Dasturchi 2 o'sha 38 soniyani butunlay o'tkazib yubordi.

Jamoaviy remote cache: CI va Dasturchi 1 cache'ni to'ldiradi (yozish, 38.6 s), Dasturchi 2 va 3 esa o'qiydi (6.3 s, 188 hit); markazda AC va CAS.

Bu — jamoa miqyosidagi super-kuch. Agar bu cache serveri butun jamoa va CI uchun ochiq bo'lsa, faqat bitta mashina (odatda CI) har bir commit uchun "to'liq" build qiladi; qolgan hamma — dasturchilar ham, boshqa CI job'lar ham — tayyor natijani soniyalarda oladi.

📊 Real raqamlar: Dasturchi 1: 38.6 s (kompilyatsiya + yuklash). Dasturchi 2: 6.3 s, 188 remote cache hit. Ikkalasi bir xil kod, bir xil Xcode 27.1, bir xil Bazel 9.3.0 — faqat papka va output base boshqacha. Remote cache cross-directory ishladi, chunki rules_swift action'lari absolute path'lardan qochadi (quyida §11'da ko'ramiz).

🧠 Esda tuting: --remote_cache ham http(s)://, ham grpc(s):// protokollarini qabul qiladi. Katta jamoalarda gRPC tezroq. --remote_timeout standart 60 soniya; sekin tarmoqda oshirish mumkin. Autentifikatsiya: https://user:parol@host yoki --remote_header=x-api-key=....


7. Build without the Bytes {#bwb}

Diqqat bilan qarasangiz, oldingi §6 misolda bitta g'alati narsa bor edi. Dasturchi 2 remote cache'dan 188 ta natija "oldi", lekin atigi 6.1 MB cache'dan hammasini yuklab olsa, nega bu kerak? Aslida u hammasini yuklab olmadi.

Bu — Build without the Bytes (BwB) deb ataladigan xususiyat. G'oyasi oddiy: agar sizga faqat yakuniy natija (masalan, .ipa) kerak bo'lsa, nega oraliq .o va .swiftmodule fayllarni mahalliy diskka yuklab olish kerak? Ular remote CAS'da tursa yetadi — faqat yakuniy fayllarni tortib olamiz.

Buni Dasturchi 2'ning lokal papkasini tekshirib isbotladik:

Dasturchi 2 lokalida yuklab olingan oraliq .o fayllar:  0
Dasturchi 2 lokalida yakuniy .ipa:  bor

Oraliq Book.swift.o fayllardan hech biri lokalga tushmadi — ular remote CAS'da qoldi. Faqat yakuniy .ipa yuklab olindi. Natijada tarmoqdan kamroq bayt o'tadi, build tezroq bo'ladi.

Bu xatti-harakatni --remote_download_outputs flagi boshqaradi, uning uchta qiymati bor:

QiymatNima yuklab olinadiFlag
allhamma oraliq fayllar ham(eski standart)
toplevelfaqat top-level target'ning chiqishi--remote_download_toplevel
minimaldeyarli hech narsa--remote_download_minimal

🧠 Esda tuting: Bazel 9.3.0 da standart qiymat — **toplevel**. Buni biz shu mashinada tekshirdik:

text $ bazel help build | grep remote_download_outputs --remote_download_outputs (all, minimal or toplevel; default: "toplevel")

Ya'ni Build without the Bytes Bazel 9'da standart yoqilgan — siz uchun maxsus hech narsa qilish shart emas. Shuning uchun Dasturchi 2 faqat .ipani oldi.

--remote_download_minimal esa CI uchun juda mos: CI odatda oraliq fayllarni umuman ko'rmaydi, u faqat testlar o'tganini yoki artefakt yaratilganini tekshiradi. Minimal rejim bilan tarmoq trafigi va disk bandligi yanada kamayadi.

⚠️ Diqqat: BwB bir cheklovga ega: agar keyin sizga lokal fayl kerak bo'lib qolsa (masalan, LLDB'da debug qilish yoki coverage hisoboti), Bazel uni CAS'dan qayta tortishi kerak bo'ladi. Rasmiy hujjat coverage bilan --remote_download_minimalni birga ishlatib bo'lmasligini aytadi — coverage uchun oraliq fayllar kerak. Shuning uchun BwB'ni CI'da yoqib, lokal debug sessiyalarda --remote_download_all bilan vaqtincha o'chirish mumkin.


8. Rollar: dev faqat o'qiydi, CI yozadi {#rollar}

§6'da Dasturchi 1 ham o'qidi, ham yozdi. Lekin haqiqiy jamoada bu xavfli. Agar har bir dasturchi cache'ga yoza olsa, kimdir noto'g'ri sozlangan mashinada (boshqa Xcode, o'zgargan muhit) noto'g'ri natija yuklab, cache'ni "zaharlashi" (cache poisoning) mumkin. Keyin butun jamoa shu buzuq natijani oladi.

Shuning uchun standart qoida: dasturchilar faqat o'qiydi, faqat ishonchli CI yozadi. Buni ikki flag boshqaradi:

# Dasturchi mashinasida: o'qish — ha, yozish — yo'q
build --remote_cache=grpcs://cache.jamoa.uz
build --remote_upload_local_results=false

# CI'da (--config=ci): o'qish ham, yozish ham
build:ci --remote_cache=grpcs://cache.jamoa.uz
build:ci --remote_upload_local_results=true

--remote_upload_local_results standart holatda true (buni shu mashinada tekshirdik). Dasturchi mashinalarida uni false qilib qo'yish — eng muhim xavfsizlik sozlamasi.

BuildBuddy jamoasining 2024-yilgi iOS migratsiya qo'llanmasida ko'rsatilgan amaliy namuna — .bazelrc'da rollarni --config orqali ajratish:

# .bazelrc (BuildBuddy qo'llanmasidan moslashtirilgan)
common --remote_cache=grpcs://remote.buildbuddy.io
common --noremote_upload_local_results          # standart: faqat o'qish
common:upload --remote_upload_local_results      # "upload" konfiguratsiyasi yozadi
common:ci --config=upload                        # CI yozish huquqiga ega

_(Manba: buildbuddy.io/blog/how-to-migrate-an-ios-app-to-bazel, 2024-03-13; bu qo'llanma eski Bazel 7 ga yozilgan, lekin rol-ajratish namunasi bugun ham amal qiladi.)_

💡 Pro tip: Agar bitta target'ni umuman cache'lamaslik kerak bo'lsa (masalan, har safar yangi natija berishi kerak bo'lgan kod generatsiyasi), uni tags = ["no-remote-cache"] bilan belgilang. Butun build uchun esa --noremote_accept_cached cache'dan o'qishni o'chiradi (standart true).


9. Remote cache provayderlari {#provayderlar}

O'z cache serveringizni qayerda ushlab turasiz? Variantlar (narx faqat manba bo'lsa keltiriladi):

  • bazel-remote — biz shu darsda ishlatgan ochiq kodli server. O'z infratuzilmangizda ishlaydi, disk'da saqlaydi, axlat yig'ish (garbage collection) bilan hajmni cheklaydi. Rasmiy Bazel hujjatiga ko'ra, "2018-yildan beri bir nechta kompaniyada muvaffaqiyatli ishlatilmoqda". HTTP ham, gRPC ham qo'llab-quvvatlaydi. Eng arzon boshlash nuqtasi.
  • BuildBuddy — grpcs://remote.buildbuddy.io. Remote cache + remote execution + Build Event Protocol dashboard. macOS/ARM uchun executor bor (iOS remote execution uchun muhim). Bepul (anonim) daraja mavjud, lekin Mac RBE bepul darajada emas.
  • EngFlow, NativeLink — korporativ darajadagi remote cache/execution platformalari.
  • Tuist'ning Bazel cache'i — 2026-yil sentyabrdan beri Tuist serveri Bazel'ga ham Remote Execution API (REAPI) orqali remote cache beradi. O'rnatish ikki buyruq:
tuist auth login    # bir marta, barcha repozitoriylar uchun
tuist bazel setup   # har repozitoriyda bir marta

tuist bazel setup loyiha ildizida .bazelrc.tuist faylini yaratadi (eng yaqin cache regioniga ulangan, per-workspace credential helper bilan) va .bazelrc'ga try-import %workspace%/.bazelrc.tuist qatorini qo'shadi. Keyin cache faolligi Tuist dashboard'idagi "Bazel Cache" sahifasida ko'rinadi: action-cache hit rate, yuklash/yuklab olish, latency. _(Manba: Tuist rasmiy hujjati, guides/features/cache/bazel-cache.md; biz tuist bazel buyrug'i o'rnatilgan Tuist 4.211.1'da setup, test, credential-helper subkomandalariga ega ekanini tekshirdik.)_

🏢 Real-world: rules_apple o'z hujjatida iOS uchun remote cache'ga muhim maslahat beradi: bundling, linking va signing action'larini remote'ga bermang, lokalda qoldiring. Sababi — katta bundle va linklangan binary'larni serverga yuklab, keyin qaytarib olish, ularni lokal qurishdan sekinroq; qolaversa, linklangan binary'lar lokal path'larga ega. Buni bitta .bazelrc qatori hal qiladi (rules_apple hujjatidan so'zma-so'z):

text common --modify_execution_info=^(BundleApp|BundleTreeApp|DsymDwarf|DsymLipo|GenerateAppleSymbolsFile|ObjcBinarySymbolStrip|CppArchive|CppLink|ObjcLink|ProcessAndSign|SignBinary|SwiftArchive|SwiftStdlibCopy)$=+no-remote,^(BundleResources|ImportedDynamicFrameworkProcessor)$=+no-remote-exec

_(Manba: rules_apple common_info.md, skeptik tekshiruvda so'zma-so'z tasdiqlangan.)_

Qaysi birini tanlash? Kichik jamoa va o'z serveringiz bo'lsa — bazel-remote yetarli. Dashboard va CI insights kerak bo'lsa — BuildBuddy yoki Tuist. iOS remote execution kerak bo'lsa — macOS ishchi pool kerak bo'ladi, bu alohida xarajat (§12 va 18-dars).


10. Cache-miss detektivligi: execution log bilan {#detektiv}

Mana darsning eng muhim amaliy ko'nikmasi. Remote cache o'rnatdingiz, lekin hit rate past — hamma qayta-qayta quryapti. Nega? Odatda sabab bitta: ikki build "bir xil" ko'rinadi, lekin action key'lar har xil, chunki biror narsa nohermetik (muhitga bog'liq) bo'lib, build'dan build'ga o'zgaryapti.

Rasmiy hujjat (remote/cache-remote.mdx) aniq usul beradi: ikki build'ning execution log'ini oling va solishtiring. Execution log har bir action'ning kirishlari (fayllar, buyruq argumentlari, environment) va chiqishlarini yozadi — shuning uchun undan action nega qayta bajarilganini topish mumkin.

Keling, bu detektiv ishini o'tkazaylik. Avval muammoni ataylab yaratamiz. Oddiy genrule yozamiz, u APP_FLAVOR degan muhit o'zgaruvchisini o'qiydi:

# BUILD.bazel
genrule(
    name = "build_info",
    outs = ["build_info.txt"],
    # APP_FLAVOR --action_env orqali keladi; shu action'ning muhitiga kiradi
    cmd = "echo \"flavor=$${APP_FLAVOR:-none}\" > $@",
)

⚠️ Diqqat — haqiqiy xato: Birinchi urinishimizda genrule'ni ${APP_FLAVOR:-none} deb yozdik va Bazel darhol to'xtatdi:

text ERROR: in cmd attribute of genrule rule //:build_info: '${APP_FLAVOR:-none}' syntax is not supported; use '$(APP_FLAVOR:-none)' instead for "Make" variables, or escape the '$' as '$$' if you intended this for the shell

Sabab: genrule cmd ichida Bazel $(...)'ni o'zining "Make" o'zgaruvchisi deb biladi. Shell $ belgisini $$ qilib yozish kerak. Yuqoridagi to'g'ri variantda $${APP_FLAVOR...} — shell uchun, $@ esa (yagona $) — genrule'ning chiqish fayli uchun Make o'zgaruvchisi.

Endi ikki marta quramiz, har safar --action_env bilan boshqa qiymat beramiz va execution log'ni JSON formatida yozamiz:

# Build A
bazel build //:build_info --action_env=APP_FLAVOR=free \
  --execution_log_json_file=exec_free.json

# Build B (clean'dan keyin, boshqa qiymat)
bazel clean
bazel build //:build_info --action_env=APP_FLAVOR=pro \
  --execution_log_json_file=exec_pro.json

Ikki log tayyor. Endi ularni solishtiramiz. JSON formatini o'qish oson — har bir action bitta obyekt. Ikki faylning farqini ko'raylik:

--- exec_free.json
+++ exec_pro.json
       "name": "APP_FLAVOR",
-      "value": "free"
+      "value": "pro"
-        "hash": "db8b3216a6b4f9fbb4175fc49355c75406b640c31c55558dda9dae1de94e561f",
+        "hash": "32edae48959b4d8fc26e674d17e4909feaea30c42e5f05a722fefe518299ddff",
         "hashFunctionName": "SHA-256"

Aybdor topildi. Ikki build'ning environmentVariables bo'limida APP_FLAVOR farq qilyapti (free vs pro), va shu sababli chiqish faylining digest'i ham har xil (db8b3216... vs 32edae48...). Agar ikki dasturchi (yoki CI) APP_FLAVORni har xil qo'ygan bo'lsa — ularning build'lari hech qachon bir-birining cache'iga tegmaydi.

Execution log'da bitta action to'liq qanday ko'rinishini ham ko'rib chiqaylik (exec_free.json'dan, qisqartirilgan):

{
  "commandArgs": [
    "/bin/bash",
    "-c",
    "source external/bazel_tools/tools/genrule/genrule-setup.sh; echo \"flavor=${APP_FLAVOR:-none}\" > bazel-out/.../build_info.txt"
  ],
  "environmentVariables": [
    { "name": "APP_FLAVOR", "value": "free" },
    { "name": "PATH", "value": "/bin:/usr/bin:/usr/local/bin" }
  ],
  "mnemonic": "Genrule",
  "targetLabel": "//:build_info",
  "actualOutputs": [
    {
      "path": "bazel-out/.../build_info.txt",
      "digest": {
        "hash": "db8b3216...",
        "sizeBytes": "12",
        "hashFunctionName": "SHA-256"
      }
    }
  ]
}

Ko'rib turganingizdek, action'ning hamma kirishlari shu yerda: buyruq, environment, chiqish digest'i. Ikki log'ni diff qilib, farq qilgan maydonni topasiz — bu sizning cache miss'ingizning aybdori.

💡 Pro tip: Uchta execution log formati bor: --execution_log_compact_file (eng kichik, tavsiya etiladi), --execution_log_binary_file va --execution_log_json_file (inson o'qiydigan). O'rganish uchun JSON eng qulay — hech qanday qo'shimcha vosita kerak emas, to'g'ridan-to'g'ri diff yoki jq bilan ochasiz. Katta loyihada compact format ishlatib, uni Bazel'ning //src/tools/execlog:parser vositasi bilan matnga aylantiring — u ikkinchi log'ning action'larini birinchisiga moslab tartiblaydi, shunda diff oson bo'ladi.

🧠 Esda tuting — detektivlik tartibi: (1) bazel clean qiling (lokal hit'lar maskirovka qilmasin). (2) Build'ni ikki marta (ikki mashinada yoki ikki holatda) --execution_log_json_file bilan ishga tushiring. (3) INFO: qatorida nechta hit borligini ko'ring. (4) Agar hit kam bo'lsa — ikki log'ni diff qiling. (5) Farq qilgan maydon (environment, buyruq, kirish digest'i) — aybdor. (6) Uni hermetik qiling.


11. iOS'ga xos cache qotillari {#ios-qotillar}

Yuqoridagi detektiv usuli universal. Lekin iOS'da cache hit'ni o'ldiradigan bir nechta tipik "qotil" bor — ularni oldindan bilsangiz, ko'p vaqt tejaysiz.

1. Turli Xcode versiyalari — №1 qotil. §2'da biz aquery chiqishida Swift action'ning muhitida XCODE_VERSION_OVERRIDE=27.1.0.27A9275 borligini ko'rdik. Bu to'g'ridan-to'g'ri action key'ga kiradi. Demak, Xcode 27.1'da qurgan natija Xcode 27.0'dagi bilan hech qachon mos kelmaydi. Jamoada remote cache hit rate yuqori bo'lishi uchun hamma bir xil Xcode versiyasida bo'lishi shart.

Buni majburlash uchun rules_apple tools/bazel wrapper namunasini beradi — u Xcode o'zgarganda avtomatik ravishda xcode.bazelrc yaratadi va kerak bo'lsa serverni qayta ishga tushiradi:

#!/bin/bash
# tools/bazel (rules_apple hujjatidan)
bazel_real="$BAZEL_REAL"
bazelrc_lines=()
if [[ $OSTYPE == darwin* ]]; then
  xcode_path=$(xcode-select -p)
  xcode_version=$(xcodebuild -version | tail -1 | cut -d " " -f3)
  bazelrc_lines+=("startup --host_jvm_args=-Xdock:name=$xcode_path")
  bazelrc_lines+=("build --xcode_version=$xcode_version")
  bazelrc_lines+=("build --repo_env=XCODE_VERSION=$xcode_version")
  bazelrc_lines+=("build --repo_env=DEVELOPER_DIR=$xcode_path")
fi
printf '%s\n' "${bazelrc_lines[@]}" > xcode.bazelrc
exec "$bazel_real" "$@"

_(Manba: rules_apple common_info.md, skeptik tekshiruvda tasdiqlangan.)_ So'ngra .bazelrc oxiriga import %workspace%/xcode.bazelrc qo'shiladi. Bu wrapper barcha dasturchi va CI'ni bir xil Xcode'ga "qulflaydi" — remote cache hit rate shu tufayli yuqori qoladi.

2. Absolute path'lar. Agar action buyrug'ida yoki chiqishida mashinaga xos mutlaq yo'l bo'lsa (/Users/aziz/...), boshqa mashinada u mos kelmaydi. Yaxshi xabar: rules_swift buni oldindan hal qilgan. §2'dagi aquery chiqishida biz ko'rdik:

-file-prefix-map '__BAZEL_XCODE_DEVELOPER_DIR__=/PLACEHOLDER_DEVELOPER_DIR'

Ya'ni rules_swift Xcode yo'lini /PLACEHOLDER_DEVELOPER_DIR degan barqaror joy-egasiga almashtiradi, shunda kompilyatsiya natijasi mashinaning haqiqiy yo'liga bog'lanib qolmaydi. Aynan shuning uchun §6'da ikki alohida papkadagi klon cross-directory cache hit oldi. Lekin agar siz o'zingiz genrule yoki custom rule yozib, unda absolute path ishlatsangiz — bu hermetiklikni buzasiz.

3. Environment leakage. Yaxshi xabar: Bazel 9'da --incompatible_strict_action_env **standart true** (buni shu mashinada tekshirdik). Shu sababli §2'dagi Swift action muhitida PATH=/bin:/usr/bin:/usr/local/bin — qisqartirilgan, barqaror PATH edi, sizning shaxsiy $PATHingiz emas. Bu avtomatik ravishda ko'p "environment leakage" muammosini bartaraf etadi. Lekin --action_env=FOO=$FOO deb o'zingiz muhitni "ochib" qo'ysangiz (§10'dagi APP_FLAVOR kabi) — yana nohermetiklik qaytadi.

4. Timestamp va stamping. Agar action build vaqtida date yoki tasodifiy qiymat yaratsa, har safar boshqa natija chiqadi. Versiya ma'lumotini kiritish uchun --stamp ishlatiladi, lekin uni ehtiyotkorlik bilan — Bazel "volatile" (tez-tez o'zgaradigan) ma'lumotni alohida faylda saqlaydi, shunda u action key'ni buzmaydi. Shunga qaramay, --stamp'ni faqat release build'larda yoqing, kundalik build'da emas.

5. Mixed Swift/Objective-C modullar va sandboxing. BuildBuddy qo'llanmasi ogohlantiradi: aralash tillik modullar Bazel'da yomon cache'lanadi; har bir modul o'z papkasida bo'lishi kerak (glob() papka chegarasini kesib o'tmaydi). Rappi jamoasi esa CocoaPods pod'lari o'rtasida ishlatilgan "private header"lar tufayli sandboxing'ni o'chirishga majbur bo'lgan — bu hermetiklikni zaiflashtiradi (Rappi engineering blogi).

🧠 Esda tuting — iOS cache hit checklist: (1) hamma bir xil Xcode; (2) absolute path yo'q (yoki -file-prefix-map ishlatilgan); (3) --incompatible_strict_action_env yoqilgan (Bazel 9'da standart); (4) --stamp faqat release'da; (5) har modul o'z papkasida, aralash tillik modullardan qoching. Shu beshtasi bajarilsa — remote cache hit rate yuqori bo'ladi.


12. Real tajriba: jamoalar nima deydi {#real-tajriba}

Cache haqida gapirganda, bir narsani ochiq aytish kerak: neytral, uch tomonlama (Bazel vs Tuist vs Xcode) benchmark mavjud emas. E'lon qilingan deyarli barcha raqamlar bitta vositani o'sha kompaniyaning avvalgi sozlamasi bilan solishtiradi va ko'pchiligi vendor (Tuist, BuildBuddy) tomonidan chop etilgan. Shuning uchun quyidagi hikoyalarni "yo'nalish" sifatida oling, mutlaq haqiqat sifatida emas — har biriga manba va kontekst qo'shilgan.

🏢 Real-world — Monzo (Buyuk Britaniya neobanki). Tuist chop etgan, Monzo o'zi yozgan case study (2026-05-26): loyiha ~50 moduldan deyarli 200 modulga o'sdi, 45+ iOS muhandis, 1.9M qator Swift, 20k unit + 8k snapshot test. CI PR'da 70 daqiqadan oshib ketgan edi. Umumiy build cache'ni main branch'da har 30 daqiqada warm qilish bilan: P50 PR vaqti 52 → 15 daqiqa (−71%). Lekin Monzo ikki muhim ogohlantirish beradi: (1) cache hit rate ~80% atrofida, 100% emas; (2) lokal build vaqtlari cache bilan ham deyarli o'zgarmay qoldi — foyda asosan CI'da; (3) ular hech qachon production build uchun cache ishlatmaydi. (Bu Tuist module cache'i, Bazel emas — lekin g'oya bir xil: AC + CAS. Manba: tuist.dev case study.)

🏢 Real-world — Toss (~1000 modul). Tuist case study'si (2026-10-01) muhim saboq beradi: cache foyda berishidan oldin, dependency graph'ni "cache'lanadigan" qilish kerak. Toss buning uchun: ishlatilmagan import'larni avtomatik o'chirdi; uchinchi tomon kutubxonalarini alohida repozitoriyga ko'chirdi; umumiy modullardan "conditional-compilation" flaglarini olib tashladi (bitta cache konfiguratsiyasi ichki va production build'ga xizmat qilsin); va cache'lanadigan modullar ulushi tushsa — ogohlantiradigan PR tekshiruvi qo'shdi. Ya'ni cache — bu "yoqib qo'ydim va ishladi" emas, balki arxitekturaviy intizom. (Enterprise sozlama; manba: tuist.dev case study.)

🏢 Real-world — Spotify (Bazel). InfoQ (2023-oktabr) xabariga ko'ra: Spotify iOS'da PR'ning p75 (75-persentil) "merge oldidan kutish" vaqti 2022-yil mayda ~80 daqiqa edi, remote cache bilan ~20 daqiqaga tushdi ("to'rt barobar"). Eng og'ir CI konfiguratsiyasi 45+ daqiqadan (Xcode) 10 daqiqadan kamga (Bazel) tushdi. Bu — Bazel remote cache'ning katta miqyosdagi real foydasi.

🏢 Real-world — Trendyol (self-hosted cache). Trendyol (Turkiya, 170+ dasturchi) o'z case study'sida (2024-12-16, Tuist chop etgan) self-hosted cache'dan keyin bir oylik PoC natijasini keltiradi: CI ~30 daqiqadan ~10 daqiqaga (−65%) tushgan; UI-testlar uchun toza muhit tayyorlash ≥15 daqiqadan ~30 soniyaga qisqargan. Bu ham Tuist module cache'i (Bazel emas), mijozning o'zi yozgan va vendor chop etgan — lekin self-hosted jamoaviy cache'ning amaliy foydasini ko'rsatadi.

⚠️ Diqqat — cache har doim ham yutuq emas. intrada loyihasi (ochiq, mustaqil o'lchov, 2026-09-30, 18 yadroli Mac, Xcode 26) Xcode'ning kompilyatsiya cache'ini sinab ko'rdi: bitta Swift qatorini o'zgartirib, 10 martadan build qildi. Mediana cache o'chiqda 5.5 s, cache yoqiqda 6.5 s bo'ldi — ya'ni cache kundalik incremental build'ni ~1 soniyaga sekinlashtirdi. Sabab: kichik o'zgarishda cache qidirishning o'zi hisob-kitobdan ortiq vaqt oldi. Saboq: cache remote/katta miqyos uchun foydali, lekin kichik loyihada yoki arzon incremental build'da u ortiqcha yuk bo'lishi mumkin. (Manba: github.com/jonyardley/intrada/issues/1886.)

📊 Real raqamlar — vendor da'volari (ehtiyotkorlik bilan). BuildBuddy'ning iOS migratsiya qo'llanmasi (2024-03-13, eski) remote cache uchun zstd siqishni yoqsa "uzatiladigan baytlar ~70% kamayadi va ba'zi loyihalarda build ~45% tezlashadi", rules_swift global module cache esa "10 barobargacha tez kompilyatsiya" beradi deydi. Lekin bu raqamlar qaysi loyihada, kim o'lchagani ko'rsatilmagan — shuning uchun ularni "da'vo" sifatida qabul qiling. (Bazel 9.3.0'da --remote_cache_compression standart false; yoqish uchun ataylab qo'yish kerak.)

Umumiy xulosa: remote cache katta jamoa va CI uchun eng kuchli yutuq beradi. Kichik loyiha yoki yakka dasturchi uchun disk cache (bepul, hisob kerak emas) ko'pincha yetarli. Va har doim: remote cache lokaldan sekinroq (tarmoq latency'si), chunki build minglab cache so'rovi qiladi — shuning uchun serverni jamoangizga jismonan yaqin joyda ushlang.

🚀 Motivatsiya: Siz endi Spotify, Monzo kabi kompaniyalar ishlatgan aynan o'sha mexanizmni — content-addressable action cache'ni — tushunasiz va o'z mashinangizda o'rnata olasiz. Farqi faqat miqyosda: ular minglab modul va yuzlab muhandis uchun, siz esa bugun kichik loyihada. Lekin tamoyil bir xil, va u siz bilan har qanday miqyosga o'sadi.


Amaliy mashqlar {#mashqlar}

  1. Disk cache'ni o'lchang. Shu darsdagi "Kutubxona" loyihasini (yoki o'z loyihangizni) oling. --disk_cache= (bo'sh) bilan yangi output base'da cold build qiling, vaqtini yozing. Keyin --disk_cache=/tmp/dc bilan qayta quring, bazel clean qiling va yana quring. Uch vaqtni jadvalga yozing. INFO: qatoridagi hit sonini solishtiring.
  2. Haqiqiy remote cache ko'taring. brew install bazel-remote. Serverni --http_address localhost:8080 bilan ishga tushiring. Loyihani --remote_cache=http://localhost:8080 --disk_cache= bilan quring (Dasturchi 1). Keyin loyihani boshqa papkaga nusxalang, yangi output base bilan yana quring — INFO: qatorida remote cache hit ko'rinishini tasdiqlang.
  3. ⭐ Cache-miss detektivligi. --action_env o'qiydigan genrule yozing (§10'dagi build_info kabi). Ikki marta, ikki xil qiymat bilan, --execution_log_json_file orqali quring. Ikki JSON log'ni diff yoki jq bilan solishtiring va qaysi maydon o'zgarganini (environment va chiqish digest'i) toping. Natijani bir jumlada tushuntiring.
  4. Action key'ni "sindiring". bazel aquery 'mnemonic("SwiftCompile", //Core:Core)' | grep ActionKey bilan kalitni oling. Keyin -c opt qo'shib, yana oling — kalit o'zgardimi? Nega? So'ng Core/BUILD.bazeldagi swift_libraryga defines = ["MY_FLAG"] qo'shib, aquery'ni qayta ishga tushiring: kalit yana o'zgaradi (bizda 88ac68a9… → 2ba41d70…, chunki buyruq qatorida -DMY_FLAG paydo bo'ladi). Keyin defines'ni olib tashlang — kalit yana eski holatiga qaytadimi?
  5. ⭐ Build without the Bytes. Remote cache'dan quring, lekin --remote_download_minimal bilan. Build tugagach, output base ichida oraliq .o fayllar bor-yo'qligini find bilan tekshiring. Keyin --remote_download_all bilan qayta quring va farqni ko'ring.
  6. ⭐⭐ Xcode'ni qulflang. rules_apple'ning tools/bazel wrapper'ini (§11) loyihangizga qo'shing. Uni ishga tushirib, xcode.bazelrc yaratilganini va ichida --xcode_version borligini tekshiring. Bu nega remote cache hit rate'ni yuqori ushlashini bir xatboshida yozing.

O'zingizni tekshiring {#checklist}

  • [ ] Action key qaysi "ingredientlar"dan hisoblanishini (buyruq qatori, kirish digest'i, environment, platform, chiqish nomlari) sanab bera olaman.
  • [ ] Action Cache (AC) va Content-Addressable Store (CAS) farqini tushuntira olaman.
  • [ ] bazel aquery bilan bitta action'ning ActionKey'ini topa olaman.
  • [ ] --disk_cache va --remote_cache'ni .bazelrc'da to'g'ri yoqa olaman.
  • [ ] bazel-remote serverini ko'tarib, ikki workspace o'rtasida remote cache hit ola olaman.
  • [ ] Dev faqat o'qiydi (--remote_upload_local_results=false), CI yozadi — rollarini sozlay olaman.
  • [ ] Cache miss sababini ikki execution log'ni diff qilib topa olaman.
  • [ ] Bazel 9'da --remote_download_outputs standart qiymati toplevel ekanini va nega turli Xcode versiyasi cache hit'ni o'ldirishini bilaman.

Xulosa {#xulosa}

  • Bazel target'larni emas, action'larni cache'laydi. Har action o'z "retsepti"dan bitta action key (SHA-256) oladi: buyruq qatori, kirish fayllar kontent digest'i, environment, execution platform, chiqish nomlari. Bittasi o'zgarsa — yangi kalit, yangi kompilyatsiya.
  • Cache ikki qismdan iborat: Action Cache (kalit → qanday natija) va CAS (natijaning haqiqiy baytlari).
  • Qatlamlar tartibi: in-memory → local action cache → disk cache → remote cache → remote execution. 1–3 bepul va lokal, 4–5 server talab qiladi.
  • Disk cache (--disk_cache=~/.cache/bazel-disk) — eng arzon g'alaba: bizda cold 58.3 s → warm 1.5 s (188 disk cache hit).
  • Remote cache (--remote_cache) natijani butun jamoa bilan ulashadi: "Dasturchi 2" boshqa papkada 6.3 soniyada 188 remote cache hit oldi — 38 soniyalik kompilyatsiyani butunlay o'tkazib yubordi.
  • Build without the Bytes Bazel 9'da standart (toplevel): oraliq fayllar lokalga tushmaydi, faqat yakuniy natija.
  • Xavfsizlik: dev faqat o'qiydi, CI yozadi (--remote_upload_local_results=false) — cache poisoning'ning oldini oladi.
  • Cache-miss detektivligi: ikki build'ning execution log'ini (--execution_log_json_file) diff qilib, action key'ni o'zgartirgan nohermetik maydonni toping.
  • iOS'da №1 qotil — turli Xcode versiyalari (Xcode versiyasi to'g'ridan-to'g'ri action muhitida). Yechim: tools/bazel wrapper bilan Xcode'ni qulflash.
  • Keyingi dars: Bazel 11: CI/CD — GitHub Actions, remote cache, faqat o'zgarganini build/test qilish va release

Manbalar {#manbalar}

  1. Bazel — Remote Caching — AC va CAS, HTTP protokoli, server variantlari (nginx, bazel-remote, GCS).
  2. Bazel — Debugging Remote Cache Hits for Remote Execution — execution log'ni solishtirish usuli.
  3. Bazel — Debugging Remote Cache Hits for Local Execution — cache hit rate'ni tekshirish.
  4. Bazel Glossary — action key, action cache — rasmiy ta'riflar.
  5. rules_apple — common_info.md — --modify_execution_info, tools/bazel Xcode-qulflash wrapper.
  6. bazel-remote (GitHub) — ochiq kodli remote cache serveri.
  7. BuildBuddy — How to migrate an iOS app to Bazel — .bazelrc rol-ajratish namunasi (2024-03-13, eski).
  8. Tuist — Bazel cache (blog, 2026-09-09) — REAPI remote cache, AC yozuvi namunasi, tuist bazel setup.
  9. Monzo case study (Tuist) — ~200 modul, P50 PR 52→15 min, ~80% hit rate, lokal build o'zgarmadi.
  10. Toss case study (Tuist) — ~1000 modul, graph'ni cache'lanadigan qilish intizomi.
  11. Trendyol case study (Tuist, 2024-12-16) — self-hosted cache, CI ~30→~10 min.
  12. Spotify iOS builds (InfoQ, 2023-oktabr) — p75 merge-kutish ~80→~20 min, Bazel remote cache.
  13. Rappi iOS Bazel migratsiyasi (engineering blog) — private header'lar tufayli sandboxing o'chirilgani.
  14. intrada — Xcode compilation cache o'lchovi — cache incremental build'ni sekinlashtirgan counter-evidence.

Markdown manba

iOS Lessons