---
title: "Bitta fayl, hamma navbatda"
description: "One slow disk operation at Dropbox could make unrelated requests wait too. What should a server do while the disk is busy?"
canonical_url: "https://otabek.io/uz/blogs/one-file-everyone-waits"
md_url: "https://otabek.io/uz/blogs/one-file-everyone-waits.md"
language: "uz"
last_updated: "2026-03-26"
tags: ["Linux", "Systems", "Performance"]
---

# Bitta fayl, hamma navbatda

Dropbox'ning engineering blogida bir muammo haqida o'qib qoldim: **bitta sekin disk operatsiyasi boshqa so'rovlarni ham kuttirib qo'yishi mumkin ekan.** Bitta faylning ishi cho'zildi, navbatga esa boshqalar ham tushib qoldi.

![Otabek kafeda o'tirib, oq mushugi Mimi tomonga o'girilgancha kulimsirab tennis to'pi bilan o'ynayapti; orqada qahva tayyorlanyapti](/images/blogs/one-file-everyone-waits/cover-otabek-mimi-playing.png)

Ularning **2017-yildagi postida** Nginx katta fayllarni uzatish bilan birga tez javob talab qiladigan so'rovlarga ham xizmat qilgani yozilgan. Disk I/O'si event loop'ni to'xtatib qo'yganida kutishlar ko'paygan. Jamoa I/O ishlarini **thread pool**'ga, ya'ni alohida ishchi thread'larga topshirib, bu kutishlarni kamaytirgan. [Dropbox'ning server optimizatsiyasi haqidagi posti](https://dropbox.tech/infrastructure/optimizing-web-servers-for-high-throughput-and-low-latency)

Menga aynan shu joyi qiziq: **disk ishini tugatgunicha dastur nima qilyapti?** Boshqa so'rovlar bilan ishlayaptimi yoki yonida qarab turibdimi? Faylga tikilib tursam, tezroq yuklanib qoladigandek 😄

Keling, oddiy fayl o'qishdan boshlab ko'ramiz. Qayerda kutib qolayotganimiz shunda ko'rinadi.

### Faylni o'qib qo'ysak bo'lmaydimi?

**I/O** input/output degani: ma'lumotni o'qish yoki yozish. Masalan, dastur bilan fayl yoki socket orasida byte'larni ko'chirish.

Boshlanishiga shunday pseudocode yozishimiz mumkin:

```text
fayldan bir bo'lak o'qi
o'qish tugashini kut
shu bo'lakni jo'nat
takrorla
```

**Blocking read** ishlatsak, uni chaqirgan thread funksiya qaytgunicha keyingi ishga o'tolmaydi. Boshqa thread'lar ishlashi mumkin, butun kompyuter qotib qolmaydi. Lekin bu thread ko'p mijozlarga xizmat qilayotgan bo'lsa, bitta sekin o'qish boshqalarning so'rovlarini ham kechiktiradi.

Buni kafedagi navbatga o'xshataman. Bitta kassir bor. Bir odam sendvich buyurtma qildi, kassir esa oshxonaga kirib, uning pishishini tomosha qilib turibdi. Butun navbat endi o'sha sendvichning taqdiriga qiziqib qolgan.

**Mijoz:** “Men faqat suv olmoqchi edim.”

**Kassir:** “Sendvich 73% bo'ldi.”

### Ko'proq thread ochsak-chi?

**Thread pool**'ni ishchilar jamoasi deb olsak bo'ladi. Asosiy thread fayl bilan bog'liq ishni bir ishchiga topshiradi va keyingi mijozga o'tadi. Ishchi diskni kutishi mumkin, asosiy thread esa ishlashda davom etadi.

Bu yaxshi yechim. Dropbox'ning **2020-yildagi Nucleus sync engine haqidagi maqolasida** ham control thread filesystem I/O, network I/O va hashing ishlarini alohida thread yoki pool'larga topshirishi aytilgan. [Dropbox'da sync'ni test qilish](https://dropbox.tech/infrastructure/-testing-our-new-sync-engine)

Bu maqolalarda Dropbox io_uring ishlatgani aytilmagan, gap thread'lar haqida ketyapti. Menda esa boshqa savol tug'ildi: **shu ishni io_uring bilan qilsak nima bo'ladi?**

Unda boshqa yechim nega kerak? Thread'larni boshqarish va ishni ular orasida uzatishning ham xarajati bor. Juda ko'p kichik operatsiyalar bo'lsa, operatsion tizimga vazifa berish va javob olishni arzonlashtirishni xohlaymiz.

### io_uring

**io_uring Linux'da asynchronous I/O bilan ishlash uchun interfeys hisoblanadi.** Ishni topshirib, natijasini keyinroq olishimiz mumkin. Orada boshqa ishlarni qilaveramiz. U fayllar va socket'lar bilan bog'liq operatsiyalarni qo'llab-quvvatlaydi. [io_uring dokumentatsiyasi](https://man7.org/linux/man-pages/man7/io_uring.7.html)

Endi kassir bir nechta buyurtmani olib, oshxonaga topshiradi. Ovqat tayyor bo'lganda buyurtma raqamini chaqiradi. Tosterga tikilib turadigan odam kerak emas.

Operatsion tizimning **kernel** qismi disk va tarmoq bilan ishlashni boshqaradi. Bizning dasturimiz **user space**'da ishlaydi. **System call**, qisqacha syscall, dastur kernel'dan biror ishni bajarishni so'raydigan yo'llardan biri.

io_uring'da esa dastur va kernel xotiradagi ikkita umumiy navbatdan ham foydalanadi:

| Navbat | Oddiy ma'nosi | Kim yozadi? |
| --- | --- | --- |
| Submission Queue, **SQ** | Bajarilishi kerak bo'lgan ishlar | Bizning dastur |
| Completion Queue, **CQ** | Olib ketishimiz mumkin bo'lgan natijalar | Kernel |

Navbat kataklari aylana bo'ylab qayta ishlatiladi. Nomidagi **ring** shundan. Ular operatsiya tavsifi va natijasini tashiydi; butun videoni ring ichiga tiqayotganimiz yo'q. [Jens Axboe'ning tushuntirishi](https://www.kernel.dk/io_uring.pdf)

```text
Dastur → “A, B, C bo'laklarni o'qi” → Submission Queue
                                          ↓
                                        Kernel
                                          ↓
Dastur ← “B tugadi; A tugadi” ← Completion Queue
```

Bitta so'rov **SQE**, ya'ni submission queue entry deyiladi. Bitta natija esa **CQE**. Buyurtma qog'ozi va natija qog'oziga chiroyli nomlar qo'yib oldik.

### Tezlik qayerdan keladi?

Birinchidan, bir nechta mustaqil operatsiya bir paytda **in flight** bo'lishi mumkin: topshirilgan, ammo hali tugamagan. Bitta o'qish diskni kutayotganida boshqa foydali ish davom etadi.

Ikkinchidan, so'rovlarni **batch** qilib topshirish mumkin. Bir nechta entry tayyorlab, birga submit qilamiz. Bu dastur va kernel orasidagi qatnovlarni kamaytirishi mumkin. `io_uring_submit()` tayyor ishlarni topshiradi; undan qaytgan qiymat fayl operatsiyalari tugadi degani emas. [Submit dokumentatsiyasi](https://man7.org/linux/man-pages/man3/io_uring_submit.3.html)

SSD jismonan tezlashib qolmaydi. Bor qurilmadan samaraliroq foydalanishimiz mumkin bo'ladi.

“io_uring'da syscall umuman kerak emas” degan gapni ham uchratishingiz mumkin. Bu umumiy holatda to'g'ri emas. Odatdagi ishlashda syscall'lar bor. **SQPOLL** degan ixtiyoriy rejimda kernel thread yangi so'rovlarni kuzatadi: kamroq submit syscall evaziga CPU vaqti sarflanadi. O'sha thread'ni uyg'otish uchun ham syscall kerak bo'lishi mumkin. io_uring'ni tushunish yoki ishlatishni boshlash uchun bu rejim shart emas. [Setup flag'lari](https://man7.org/linux/man-pages/man2/io_uring_setup.2.html)

### Qaysi fayl tugadi o'zi?

Yuqoridagi chizmaga yana qarang: A'ni B'dan oldin so'radik, lekin B oldin tugadi. Mustaqil operatsiyalarning tugash tartibi boshqacha bo'lishi mumkin. Demak har bir natijani tanib olishimiz kerak.

Har bir so'rovga **`user_data`** orqali raqam beramiz. Natija bilan o'sha qiymat qaytadi. 43-buyurtma oldin tayyor bo'lsa ham, 42-raqam hali ham 42-so'rovga tegishli. [So'rovni aniqlash](https://man7.org/linux/man-pages/man3/io_uring_cqe_get_data.3.html)

C'da bunga yordam beradigan **liburing** kutubxonasi bor. U bilan read tayyorlash shunday ko'rinadi:

```c
io_uring_prep_read(sqe, fd, buffer, 4096, 0);
io_uring_sqe_set_data64(sqe, 42);
```

Bu ikki qatorni ishlatishdan oldin ring'ni tayyorlash, faylni `fd` sifatida ochish, bo'sh `sqe` olish va kamida 4,096 byte'lik buffer ajratish kerak. Shundan keyin **0-offset'dan 4,096 byte'gacha o'qishni** tayyorlab, unga 42-raqamni qo'yamiz. Hali submit qilishimiz ham kerak. [Read'ni tayyorlash](https://man7.org/linux/man-pages/man3/io_uring_prep_read.3.html)

Natija kelganda `cqe->res` ni tekshiramiz: musbat qiymat o'qilgan byte'lar soni, 0 shu read uchun fayl oxiriga yetganimiz, manfiy qiymat esa xatolik. So'ralgandan kamroq byte o'qilishi ham mumkin. 4,096 byte so'raganimiz hammasini oldik degani emas. [Read natijalari](https://man7.org/linux/man-pages/man3/io_uring_prep_read.3.html), [kamroq byte o'qilishi](https://man7.org/linux/man-pages/man2/read.2.html)

**Topshirildi degani tugadi emas. Tugadi degani muvaffaqiyatli bo'ldi emas.**

Boshqa foydali ish qolmasa, `io_uring_wait_cqe()` bilan natijani kutish mumkin. Natijani muvaffaqiyatli olganimiz read ham muvaffaqiyatli bo'lganini bildirmaydi; `cqe->res` ni alohida tekshiramiz. Ishlov bergach, `io_uring_cqe_seen()` bilan CQE'ni olib bo'lganimizni bildiramiz. [Kutish](https://man7.org/linux/man-pages/man3/io_uring_wait_cqe.3.html), [natijani olib bo'lish](https://man7.org/linux/man-pages/man3/io_uring_cqe_seen.3.html)

### Fayllarimizga qaytamiz

Xo'sh, **Dropbox'ga o'xshagan xizmatning Linux backend**'ida bundan qanday foydalanamiz? Bir nechta download uchun mustaqil bo'laklarni kichik batch'larda o'qishimiz mumkin. Read'lar tugagani sari natijalar bilan ishlaymiz va keyingi batch uchun joy bo'shatamiz.

Bitta fayl sekin kelayotganida boshqasi tayyor bo'lishi mumkin. Hammaga sekin faylni kuttirish o'rniga tayyor bo'lgani bilan ishlaymiz. Lekin har bir fayl byte'larini to'g'ri tartibda jo'natish va tarmoq protokoliga rioya qilish hali ham bizning vazifamiz. Navbat tezlashdi deb birovning ta'til videosini aralashtirib yubormaymiz-ku.

Upload'da ham shunday yo'l tutish mumkin: kelgan bo'laklarni yozamiz, boshqa transfer'lar davom etadi. Hashing yoki compression esa alohida CPU ishi. io_uring ularni avtomatik bajarib bermaydi.

Bu yerda bir tuzoq bor: **asynchronous degani cheksiz degani emas**. Deylik, bir paytda 32 ta read'ga ruxsat berdik, har birining buffer'i 256 KiB. Faqat read buffer'larining o'zi **8 MiB** bo'ladi. Shunday read'lardan 10,000 tasiga ruxsat bersak, taxminan **2.44 GiB** chiqadi.

Mijoz sekin yuklab olsa, tayyor bo'laklarni yig'averish faqat xotirani to'ldiradi. Chegara qo'yamiz: keyingi bosqich ulgurmayotganida yangi read'larni to'xtatamiz, buffer bo'shaganida davom etamiz. Bu **backpressure**: “joyimiz to'ldi, hozircha yana yubormay tur.”

**Men:** “Kutishni olib tashladim.”

**RAM:** “Mening ichimga ko'chirding.”

Tugamagan so'rovlar sonini ko'pincha **queue depth** deyishadi. Ko'pligi doim yaxshi emas. Yetarli concurrency qurilmani band tutishi mumkin; ortiqchasi xotira sarfini va har bir so'rovning kutishini oshiradi.

Database'larda ham shu muammo bor. **PostgreSQL 18** sequential scan, bitmap heap scan va vacuum kabi ishlarga asynchronous read qo'shgan. `io_method = io_uring` varianti uchun liburing bilan build qilingan bo'lishi kerak; default usul esa `worker`. Dokumentatsiyada keragidan yuqori I/O concurrency latency'ni oshirishi ham aytilgan. [PostgreSQL 18 release notes](https://www.postgresql.org/docs/18/release-18.html), [I/O sozlamalari](https://www.postgresql.org/docs/18/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-IO)

Demak gap faqat katta videolarda emas. Ko'p page o'qiydigan database ham shu savolga duch keladi: **disk ulgurib olgunicha qancha foydali ishni davom ettira olamiz?**

### Nimada ehtiyot bo'lish kerak?

Endi bir nechta read bir paytda ishlayapti. Lekin hali o'qish tugamasidan buffer'ni boshqa ishga berib yuborsak-chi? Yoki kelmagan byte'larni yozishni boshlasak?

**Buffer yashab turishi kerak.** Yuqoridagi read tugagunicha kernel buffer'dan foydalanadi. Operatsiya tugamasidan uni o'chirish yoki boshqa so'rov uchun qayta ishlatish mumkin emas. Rust'dagi **tokio-uring** buni ownership orqali hal qiladi: read buffer ownership'ini oladi va natija bilan birga qaytaradi. [tokio-uring'dagi ownership](https://docs.rs/tokio-uring/latest/tokio_uring/#submit-based-operations)

**Ba'zi ishlar baribir kutishi kerak.** Write uchun read'dan keladigan byte'lar kerak bo'lsa, ikkalasini mustaqil topshirish write o'sha byte'larni kutadi degani emas. Misolimizda avval read muvaffaqiyatli tugashini kutamiz, keyin write'ni topshiramiz. io_uring'da request'larni bir-biriga bog'lash imkoniyati ham bor, uni boshqa safar ko'rarmiz. [Operatsiyalar tartibi](https://man7.org/linux/man-pages/man7/io_uring.7.html)

**Write tugashi fayl diskda ishonchli saqlandi degani emas.** Buffered data hali xotirada bo'lishi mumkin. Crash'dan keyin ham ma'lumot saqlanishi uchun to'g'ri durability protokoli, jumladan `fsync` va zarur holatda papkani ham sync qilish kerak. Local diskdagi operatsiya tugashi boshqa serverdagi replica haqida ham hech narsa demaydi. [Faylni sinxronlashtirish](https://man7.org/linux/man-pages/man2/fsync.2.html)

| Holat | Avval nimani ko'rib chiqardim? |
| --- | --- |
| Kichik xizmat latency talabini allaqachon bajaryapti | Soddaroq yechimni qoldirish. |
| CPU vaqtini compression yoki hashing olayapti | Shu ishni optimizatsiya qilish; I/O topshirish usuli uni yo'qotmaydi. |
| Tarmoq kanali allaqachon to'lgan | Ko'proq disk so'rovi tarmoq tezligini oshirmaydi. |
| Desktop ilova Linux, macOS va Windows'da ishlashi kerak | Har bir platforma uchun backend'i bor umumiy abstraksiya ishlatish. |
| Host io_uring'ga ruxsat bermaydi | Qo'llab-quvvatlanadigan fallback ishlatish; Linux ring yaratishni cheklashi mumkin. |

Oxirgi cheklov haqiqiy: kernel siyosati io_uring yaratishni cheklashi yoki taqiqlashi mumkin. Qaysi operatsiyalar ishlashi ham kernel'ga bog'liq. Dastur aynan qayerda ishlashini tekshiring. [Kernel sozlamalari](https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#io-uring-disabled)

### Conclusion

io_uring I/O ishlarini batch qilib topshirish, bir nechta operatsiyani davom ettirish va natijalarni yig'ish imkonini beradi. Dropbox'ga o'xshagan xizmatda fayl uzatishlarni boshqaradigan thread'ni to'xtatmasdan ishlash uchun qo'l kelishi mumkin.

Endi hamma thread pool'ni almashtirish kerakmi? Albatta yo'q. Avval bir xil vazifani ikkala usulda bajarib ko'ring. Throughput, CPU, xotira va **p99 latency**'ni solishtiring. P99 so'rovlarning 99%'i qancha vaqtda tugaganini ko'rsatadi. Cache'da ma'lumot bor va yo'q holatlarni, sekin mijozlarni ham sinang. Mening noutbukimdagi bitta yaxshi benchmark sizning serveringizda ham shu natija chiqadi degani emas.

Kodni o'zgartirishdan oldin Dropbox postida meni qiziqtirgan savoldan boshlardim: **disk ishini tugatgunicha dasturimiz nima qilyapti?**

Hamma narsaning oldiga `async` yozadigan tanishingizga yuboring. Faqat avval sendvichini yeb olsin 😄


## Sitemap

See the full [Markdown sitemap](/uz/sitemap.md) for all pages.
