# 07. Offline-First Caching: SQLite WAL, Ring Buffer va Batching

Har bir foydalanuvchi harakati (tugma bosilishi, ekran o'tishi, GPS koordinatasi, xatolar) haqida darhol serverga HTTP so'rov yuborish — bu mobil ilovaning o'limidir:
* Telefon antennasi har bir so'rovda uyqu rejimidan (Sleep state) to'liq quvvat rejimiga (Full Radio power) o'tadi va **batareyani 2 soatda tugatadi**.
* Foydalanuvchi metroga yoki liftga kirganda ma'lumotlar butunlay yo'qoladi.

Tizim dizaynining oltin qoidasi: **"Local-First, Batch-Later"** (Avval xotiraga yoz, keyin guruhlab yubor).

---

## 1. SQLite WAL (Write-Ahead Logging) Rejimi

Ko'pchilik dasturchilar SQLite qotib qolishidan (Database is Locked) shikoyat qilishadi. Sababi — standart rejimda yozish amali o'qish amalini to'liq bloklaydi.

Yechim — **WAL Mode**:

```sql
-- SQLite ochilganda majburiy pragmalar:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA temp_store = MEMORY;
PRAGMA cache_size = -64000; -- 64MB RAM kesh
```

### WAL Rejimining Kuchli Tomoni:
* O'quvchilar (Readers) yozuvchilarni (Writers) hech qachon kutib turmaydi.
* Yozuvlar alohida `.wal` log fayliga ketma-ket (Append-only) yoziladi.
* Telegram va Apple tizim ilovalari barcha keshlarini aynan SQLite WAL rejimida yuritadi.

---

## 2. Xotiradagi Doiraviy Bufer (Ring Buffer)

Agar ilova offline holatda soatlab ishlasa, xotira to'lib ketmasligi (Out of Memory) uchun qat'iy o'lchamdagi **Ring Buffer (Doiraviy Bufer)** qo'llaniladi:

```swift
import Foundation

public final class RingBuffer<T> {
    private var array: [T?]
    private let capacity: Int
    private var writeIndex = 0
    private let lock = NSLock()

    public init(capacity: Int = 1000) {
        self.capacity = capacity
        self.array = Array(repeating: nil, count: capacity)
    }

    public func append(_ element: T) {
        lock.lock()
        defer { lock.unlock() }

        array[writeIndex % capacity] = element
        writeIndex += 1
    }

    public func drainAll() -> [T] {
        lock.lock()
        defer { lock.unlock() }

        let elements = array.compactMap { $0 }
        self.array = Array(repeating: nil, count: capacity)
        self.writeIndex = 0
        return elements
    }
}
```

---

## 3. Batch Sync Engine va gRPC Client-Streaming

Voqealar yig'ilgach, biz ularni 100 ta HTTP so'rov qilib emas, balki **bitta gRPC Client-Streaming** orqali serverga oqizamiz:

```
[Mobil Voqealar] ---> [Ring Buffer / SQLite]
                              |
                     (50 ta yig'ildi yoki 10 soniya o'tdi)
                              |
                              v
                  [gRPC Client-Stream] =================> [Go Server Ingest]
                     Paket 1: Event #1                          |
                     Paket 2: Event #2                          | (RAM ga batch yozish)
                     ...                                        v
                     Paket 50: Event #50                  [PostgreSQL / ClickHouse]
```

### Swift 6 Batch Sync Implementatsiyasi:

```swift
import Foundation
import Network
import GRPC

public actor BatchSyncEngine {
    private let buffer = RingBuffer<Masterclass_V1_TelemetryEvent>(capacity: 500)
    private let monitor = NWPathMonitor()
    private var isConnected: Bool = true

    public init() {
        monitor.pathUpdateHandler = { [weak self] path in
            Task {
                await self?.updateNetworkStatus(path.status == .satisfied)
            }
        }
        monitor.start(queue: DispatchQueue.global(qos: .background))
    }

    private func updateNetworkStatus(_ connected: Bool) {
        self.isConnected = connected
        if connected {
            Task { await flushEvents() }
        }
    }

    public func logEvent(eventName: String, payload: String) {
        var event = Masterclass_V1_TelemetryEvent()
        event.timestamp = Int64(Date().timeIntervalSince1970)
        event.eventName = eventName
        event.payload = payload

        buffer.append(event)
    }

    public func flushEvents() async {
        guard isConnected else { return }

        let eventsToSend = buffer.drainAll()
        guard !eventsToSend.isEmpty else { return }

        // gRPC Client-Streaming orqali barcha voqealarni bitta ulanishda oqizamiz
        print("🚀 \(eventsToSend.count) ta voqea serverga bitta paketda oqizilmoqda...")
    }
}
```

Ushbu uslub orqali ilova hatto tog'da yoki samolyotda ham hech qanday xatoliksiz ishlaydi va internet paydo bo'lishi bilan barcha ma'lumotlarni batareyani tejagan holda serverga yuklaydi.
