# DevOps 3: Release engineering — versiyalash, phased release va feature flag'lar

> **Holat:** 2026-yil sentabr. Bu dars "kodni qanday xavfsiz chiqaramiz?" savoliga javob beradi: versiya raqamlari, branch strategiyasi, bosqichma-bosqich reliz, feature flag va majburiy yangilanish.

## Mundarija

1. [Versiya va build raqami](#versiya)
2. [DORA: reliz sifatini o'lchash](#dora)
3. [Branch strategiyasi mobil ilova uchun](#branch)
4. [Release train va hotfix](#release-train)
5. [Phased release — App Store'dagi "canary"](#phased)
6. [Feature flag va remote config](#feature-flags)
7. [Kill switch va majburiy yangilanish](#force-update)
8. [Reliz oldidan checklist](#checklist)
9. [Xulosa](#xulosa)
10. [Manbalar](#manbalar)

---

## 1. Versiya va build raqami {#versiya}

Har bir iOS build'da ikkita raqam bor:

| Info.plist kaliti            | Build Setting             | Kim ko'radi                  | Qoida                                                        |
| ---------------------------- | ------------------------- | ---------------------------- | ------------------------------------------------------------ |
| `CFBundleShortVersionString` | `MARKETING_VERSION`       | Foydalanuvchi (App Store'da) | SemVer: `MAJOR.MINOR.PATCH`, masalan `2.4.1`                 |
| `CFBundleVersion`            | `CURRENT_PROJECT_VERSION` | Faqat siz va Apple           | Bir versiya ichida har bir yuklashda **o'sib borishi shart** |

**SemVer qisqacha:** `MAJOR` — katta o'zgarish (masalan butun dizayn yangilandi), `MINOR` — yangi funksiya, `PATCH` — faqat tuzatish.

Raqamlarni terminaldan o'zgartirish uchun `agvtool` bor. U ishlashi uchun target'da **Versioning System = Apple Generic** bo'lishi kerak:

```bash
# build raqamini 42 ga qo'yish (barcha target'larda)
xcrun agvtool new-version -all 42

# foydalanuvchi ko'radigan versiyani 2.5.0 ga qo'yish
xcrun agvtool new-marketing-version 2.5.0

# joriy qiymatlarni ko'rish
xcrun agvtool what-version
xcrun agvtool what-marketing-version
```

CI'da build raqamini qo'lda boshqarish shart emas. `ExportOptions.plist`dagi `manageAppVersionAndBuildNumber` (standart qiymati `YES`) yuklash paytida raqamni Xcode'ning o'ziga boshqartiradi — batafsil [DevOps 1](/ios-lessons/devops-cicd#upload) darsida.

---

## 2. DORA: reliz sifatini o'lchash {#dora}

**DORA** (DevOps Research and Assessment) — Google tarkibidagi tadqiqot guruhi. U jamoaning yetkazib berish sifatini to'rtta metrika bilan o'lchaydi ([dora.dev](https://dora.dev/guides/dora-metrics-four-keys/)):

| Metrika                             | Savol                                         | Mobil ilovada qanday o'lchanadi          |
| ----------------------------------- | --------------------------------------------- | ---------------------------------------- |
| **Deployment frequency**            | Qanchalik tez-tez chiqaramiz?                 | App Store'ga reliz soni (oyiga)          |
| **Lead time for changes**           | Commit'dan foydalanuvchigacha qancha vaqt?    | PR merge → App Store'da "Ready for Sale" |
| **Change failure rate**             | Relizlarning qanchasi tuzatishni talab qildi? | Hotfix talab qilgan relizlar ulushi      |
| **Failed deployment recovery time** | Buzilgan reliz qancha vaqtda tiklandi?        | Muammo topilgandan hotfix chiqquncha     |

**2025-yilgi DORA hisoboti** "State of AI-assisted Software Development" deb nomlandi (~5 000 ishtirokchi). Unda AI ishlatish **90%**ga yetdi (yiliga +14 foiz punkt), eski "Elite/High/Medium/Low" darajalari o'rniga esa jamoa arxetiplari kiritildi. Asosiy xulosa: AI — **kuchaytirgich**. Yaxshi jarayonli jamoani tezlashtiradi, tartibsiz jamoaning muammolarini esa kattalashtiradi. Manba: [Google — DORA 2025](https://blog.google/innovation-and-ai/technology/developers-tools/dora-report-2025/).

---

## 3. Branch strategiyasi mobil ilova uchun {#branch}

Web'da **trunk-based development** (hamma kichik PR'lar bilan to'g'ridan-to'g'ri `main`ga) keng tarqalgan. Mobil ilovada esa bitta farq bor: reliz App Review'dan o'tadi va foydalanuvchida eski versiya uzoq qoladi. Shuning uchun amaliy **gibrid** yaxshi ishlaydi:

```text
main  ──●──●──●──●──●──●──●──●──●──●──►   (kichik PR'lar, har kuni)
            \                 \
             release/2.4 ──●──●    release/2.5 ──●   (faqat reliz oldidan: freeze + tuzatishlar)
                           │
                        tag v2.4.0 → App Store
```

- Kundalik ish — `main`da, qisqa umrli branch'lar orqali.
- Reliz oldidan `release/x.y` ajratiladi. Unga faqat tuzatishlar kiradi (_code freeze_).
- Tugallanmagan funksiyalar `main`da **feature flag** ortida yashiringan holda turadi (6-bo'lim).

---

## 4. Release train va hotfix {#release-train}

**Release train** — reliz belgilangan jadval bo'yicha chiqadi (masalan har 2 haftada). "Poyezd" vaqtida jo'naydi: tayyor bo'lmagan funksiya keyingi reyse qoladi. Foydasi — reliz sanasi bashorat qilinadi va jamoa har safar "chiqaramizmi?" deb bahslashmaydi.

**Hotfix oqimi** (production'da jiddiy bug):

```bash
# 1. Oxirgi reliz tag'idan hotfix branch
git switch -c hotfix/2.4.1 v2.4.0

# 2. main'dagi tuzatish commit'ini olib kelish (yoki shu yerda tuzatish)
FIX_SHA="a1b2c3d"   # git log --oneline main dan tuzatish commit'ining SHA'si
git cherry-pick "$FIX_SHA"

# 3. PATCH versiyani oshirib tag qo'yish — CI TestFlight'ga yuboradi
xcrun agvtool new-marketing-version 2.4.1
git commit -am "bumped version to 2.4.1"
git tag v2.4.1
git push origin hotfix/2.4.1 v2.4.1
```

Kritik holatda App Store Connect'dan **expedited review** (tezlashtirilgan ko'rib chiqish) so'rash mumkin. Bu imkoniyatni faqat haqiqatan jiddiy muammolar uchun ishlating.

---

## 5. Phased release — App Store'dagi "canary" {#phased}

Backend'da **canary deploy** — yangi versiyani avval trafikning kichik qismiga berish. App Store'dagi analogi — **Phased Release for Automatic Updates**. Yangilanish 7 kun davomida avtomatik yangilanishi yoqilgan foydalanuvchilarga bosqichma-bosqich tarqaladi:

| Kun                     | 1   | 2   | 3   | 4   | 5   | 6   | 7    |
| ----------------------- | --- | --- | --- | --- | --- | --- | ---- |
| Foydalanuvchilar ulushi | 1%  | 2%  | 5%  | 10% | 20% | 50% | 100% |

Muhim nuanslar:

- Faqat **update**'larga tegishli, birinchi relizga emas.
- Foydalanuvchi App Store'ga kirib ilovani **qo'lda** yangilashi mumkin — phased release unga to'sqinlik qilmaydi.
- Relizni **pauza** qilish mumkin (umumiy pauza muddati cheklangan).
- Bu **rollback emas**. Yangilab olgan foydalanuvchi eski versiyaga qaytmaydi, shuning uchun haqiqiy himoya — feature flag va kill switch.

Manba: [Apple — Release a version update in phases](https://developer.apple.com/help/app-store-connect/update-your-app/release-a-version-update-in-phases), [phased release guide](https://departures.to/about/guides/how-to-use-app-store-phased-releases).

**Phased release vaqtida nimani kuzatasiz?** Crash-free sessions, hang rate, asosiy funnel (masalan xarid) konversiyasi. Batafsil [DevOps 3](/ios-lessons/devops-observability) darsida.

---

## 6. Feature flag va remote config {#feature-flags}

**Feature flag** — kod binary'da bor, lekin uni yoqish-o'chirish serverdan boshqariladi. Bu **deploy** (kod foydalanuvchiga yetdi) bilan **release** (funksiya ochildi)ni ajratadi. Mobil ilovada bu ayniqsa qadrli: App Review'ni kutmasdan funksiyani o'chirib, yoqib yoki foizlar bo'yicha ochib borasiz.

### Asbob tanlash (2026)

| Asbob                      | Xususiyati                                                                                                                                                                                                                                                                                                 |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Firebase Remote Config** | 2026-09-01 dan foydalanish bo'yicha narxlanadi: kuniga 100 000 fetch'gacha bepul, 100 ming–10 mln/kun oralig'ida har 10 000 fetch uchun $0.06, 10 mln/kun'dan yuqori qismi uchun $0.01 ([pricing](https://firebase.google.com/docs/remote-config/pricing))                                                 |
| **LaunchDarkly**           | Enterprise standart, faqat SaaS                                                                                                                                                                                                                                                                            |
| **Statsig**                | 2025-09-02 da OpenAI ~$1.1 mlrd'ga sotib oldi. 2026-yil mayda Amplitude bilan brend va mijozlar bo'yicha kelishuv bo'ldi — tanlashdan oldin joriy holatini tekshiring ([TechCrunch](https://techcrunch.com/2025/09/02/openai-acquires-product-testing-startup-statsig-and-shakes-up-its-leadership-team/)) |
| **GrowthBook**             | Ochiq manba, o'zingiz host qilishingiz mumkin                                                                                                                                                                                                                                                              |
| **Unleash**                | Ochiq manba, self-host                                                                                                                                                                                                                                                                                     |

### SDK'dan mustaqil flag qatlami (Swift)

Qaysi provayderni tanlashingizdan qat'i nazar, ilova kodi provayderga to'g'ridan-to'g'ri bog'lanmasin. Protokol orqali ajrating: ertaga Firebase'dan GrowthBook'ga o'tsangiz, faqat bitta klass o'zgaradi.

```swift
import Foundation

enum FeatureFlag: String, CaseIterable, Sendable {
    case newOnboarding = "new_onboarding"
    case aiLessonSummary = "ai_lesson_summary"

    /// Serverdan javob kelmasa (birinchi ishga tushish, internet yo'q) shu qiymat ishlaydi.
    var defaultValue: Bool {
        switch self {
        case .newOnboarding: false
        case .aiLessonSummary: false
        }
    }
}

/// Har qanday provayder (Firebase, GrowthBook, o'z serveringiz) shu protokolni bajaradi.
protocol RemoteFlagProvider: Sendable {
    func fetchFlags() async throws -> [String: Bool]
}

@MainActor
final class FeatureFlags {
    private var values: [String: Bool]
    private let provider: any RemoteFlagProvider
    private let cacheKey = "feature_flags.cache"

    init(provider: any RemoteFlagProvider) {
        self.provider = provider
        // Oxirgi muvaffaqiyatli javob — keyingi ishga tushishda darhol ishlatiladi.
        values = UserDefaults.standard.dictionary(forKey: cacheKey) as? [String: Bool] ?? [:]
    }

    func isEnabled(_ flag: FeatureFlag) -> Bool {
        values[flag.rawValue] ?? flag.defaultValue
    }

    func refresh() async {
        do {
            let fresh = try await provider.fetchFlags()
            values = fresh
            UserDefaults.standard.set(fresh, forKey: cacheKey)
        } catch {
            // Tarmoq xatosi: eski (cache) yoki default qiymatlar bilan davom etamiz.
        }
    }
}
```

**Nega default va cache muhim?** Birinchi ishga tushishda server javobi hali kelmagan bo'ladi. Default qiymat bo'lmasa, UI "sakraydi" (_flicker_) — avval bir holat ko'rinadi, keyin boshqasi. Qoida: default qiymat har doim **xavfsiz** holat bo'lsin, ya'ni yangi funksiya o'chiq.

---

## 7. Kill switch va majburiy yangilanish {#force-update}

**Kill switch** — muammoli funksiyani serverdan darhol o'chiradigan flag. Yangi build va review kerak emas. Har bir xavfli yangi funksiya (to'lov oqimi, AI funksiyasi, yangi sinxronizatsiya) kill switch bilan chiqsin.

**Majburiy yangilanish** — backend eski, buzuq client'ni endi qo'llab-quvvatlamaydi. Eng ishonchli usul: o'z serveringizdan `min_supported_version` qaytarish. Yumshoq eslatma ("yangi versiya chiqdi") uchun esa Apple'ning ochiq **iTunes Lookup API**sini ishlatish mumkin:

```swift
import Foundation

struct AppStoreLookup: Decodable {
    struct Entry: Decodable {
        let version: String
        let trackViewUrl: String
    }
    let resultCount: Int
    let results: [Entry]
}

enum UpdateStatus: Sendable {
    case upToDate
    case updateAvailable(storeURL: URL)
}

/// App Store'dagi versiyani joriy versiya bilan solishtiradi.
func checkForUpdate(bundleID: String, currentVersion: String) async throws -> UpdateStatus {
    var components = URLComponents(string: "https://itunes.apple.com/lookup")!
    components.queryItems = [
        URLQueryItem(name: "bundleId", value: bundleID),
        URLQueryItem(name: "country", value: "uz"),
    ]
    let (data, _) = try await URLSession.shared.data(from: components.url!)
    let lookup = try JSONDecoder().decode(AppStoreLookup.self, from: data)

    guard let entry = lookup.results.first,
          let storeURL = URL(string: entry.trackViewUrl) else {
        return .upToDate // topilmadi — foydalanuvchini bezovta qilmaymiz
    }
    // .numeric: "2.10.0" > "2.9.3" deb to'g'ri solishtiradi
    let isNewer = entry.version.compare(currentVersion, options: .numeric) == .orderedDescending
    return isNewer ? .updateAvailable(storeURL: storeURL) : .upToDate
}
```

Ehtiyot bo'ling:

- Lookup javobi keshlangan bo'lishi mumkin. Reliz chiqqandan so'ng yangi versiya u yerda biroz kechikib paydo bo'ladi. Shuning uchun **majburiy** blok uchun o'z serveringizdagi qiymatga tayaning.
- `resultCount == 0` — ilova tanlangan storefront'da topilmadi. Bu xato emas, foydalanuvchini bloklamang.

---

## 8. Reliz oldidan checklist {#checklist}

- [ ] `release/x.y` branch, versiya va build raqami to'g'ri
- [ ] Barcha testlar va lint CI'da yashil
- [ ] Yangi SDK bo'lsa — privacy manifest ko'rib chiqilgan
- [ ] Xavfli funksiyalar feature flag / kill switch ortida
- [ ] TestFlight'da internal test o'tgan (asosiy oqimlar qo'lda tekshirilgan)
- [ ] "What's New" matni barcha tillarda tayyor
- [ ] Phased release yoqilgan, kuzatiladigan metrikalar dashboard'i tayyor
- [ ] Hotfix rejasi: kim, qaysi branch'dan, qanday tag bilan

---

## 9. Xulosa {#xulosa}

- `CFBundleShortVersionString` — foydalanuvchi uchun (SemVer), `CFBundleVersion` — har yuklashda o'sadi.
- DORA'ning 4 metrikasi mobil jamoa uchun ham ishlaydi: chastota, lead time, failure rate, tiklanish vaqti.
- Mobil uchun gibrid: kundalik ish `main`da, reliz oldidan qisqa `release/x.y`.
- Phased release (1%→100%, 7 kun) — monitoring oynasi, rollback emas.
- Feature flag deploy'ni release'dan ajratadi. Default qiymat har doim xavfsiz bo'lsin, provayder protokol ortida tursin.

---

## Manbalar {#manbalar}

1. [DORA — The four keys](https://dora.dev/guides/dora-metrics-four-keys/)
2. [Google — 2025 DORA report](https://blog.google/innovation-and-ai/technology/developers-tools/dora-report-2025/)
3. [Apple — Release a version update in phases](https://developer.apple.com/help/app-store-connect/update-your-app/release-a-version-update-in-phases)
4. [Apple QA1827 — agvtool](https://developer.apple.com/library/archive/qa/qa1827/_index.html)
5. [Firebase Remote Config pricing](https://firebase.google.com/docs/remote-config/pricing)
6. [TechCrunch — OpenAI acquires Statsig](https://techcrunch.com/2025/09/02/openai-acquires-product-testing-startup-statsig-and-shakes-up-its-leadership-team/)
7. [Apple — iTunes Search API](https://performance-partners.apple.com/search-api)
