DevOps 3: Release engineering — versiyalash, phased release va feature flag'lar
🛠️ DevOps Engineering
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
- Versiya va build raqami
- DORA: reliz sifatini o'lchash
- Branch strategiyasi mobil ilova uchun
- Release train va hotfix
- Phased release — App Store'dagi "canary"
- Feature flag va remote config
- Kill switch va majburiy yangilanish
- Reliz oldidan checklist
- Xulosa
- 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:
# 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.plistdagi manageAppVersionAndBuildNumber (standart qiymati YES) yuklash paytida raqamni Xcode'ning o'ziga boshqartiradi — batafsil DevOps 1 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):
| 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.
3. Branch strategiyasi mobil ilova uchun {#branch}
Web'da trunk-based development (hamma kichik PR'lar bilan to'g'ridan-to'g'ri mainga) 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:
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 —
mainda, qisqa umrli branch'lar orqali. - Reliz oldidan
release/x.yajratiladi. Unga faqat tuzatishlar kiradi (code freeze). - Tugallanmagan funksiyalar
mainda 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):
# 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, phased release guide.
Phased release vaqtida nimani kuzatasiz? Crash-free sessions, hang rate, asosiy funnel (masalan xarid) konversiyasi. Batafsil DevOps 3 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) |
| 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) |
| 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.
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 APIsini ishlatish mumkin:
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.ybranch, 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
mainda, reliz oldidan qisqarelease/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.