Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Nega bittalab sekin: ikki sabab
- 2.1 Tarmoq aylanishi
- 2.2 Har COMMIT — diskka yozish
- 3. Partiya: bitta so'rovda ko'p qator
- 3.1 Ko'p qatorli VALUES
- 3.2 Parametr chegarasi: 65 535
- 3.3 unnest: ustunlar massiv bo'lib
- 4. COPY FROM STDIN
- 4.1 Nima va qanday
- 4.2 Bo'lak hajmi: kanonning eng kutilmagan raqami
- 4.3 CSV'ni to'g'ri yozish
- 4.4 Bitta yomon qator — butun COPY bekor
- 5. Katta natijani o'qish
- 5.1 Muammo: hammasi xotirada
- 5.2 pg-cursor
- 5.3 pg-query-stream
- 5.4 Eksport: COPY TO STDOUT
- 6. Qaysi usulni tanlash
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): chegarani hurmat qiluvchi partiyalovchi
- 3-mashq (qiyin): CSV import vaqtinchalik jadvalga
- 9. Node qadami
- 10. Real ishda
- Xulosa
- Manbalar
Postgres'ga ommaviy yozish: COPY, batch INSERT, unnest va cursor
Qisqacha: Ko'p qatorni bittalab
INSERTbilan yozish — eng sekin yo'l: kanonda 145 qator/s, 100 000 qator uchun 11 daqiqa. Tezlashtirishning uch pog'onasi: bir nechta qatorni bitta so'rovda (VALUESpartiya yokiunnest($1::int[], …)— 45–135 ming qator/s) vaCOPY FROM STDIN(pg-copy-streams) — 1000 qatorlik bo'laklar bilan 409 ming qator/s. Tuzoqlar ham o'lchangan: COPY'ga har qatorni alohida bo'lak qilib bersangiz — 8.7 ming qator/s (eng sekinlardan); bitta so'rovda 65 535 dan ortiq parametr — chalg'ituvchi xato. Katta natijani o'qishda esapg-cursor/pg-query-streamxotirani 229 MB dan 52 MB ga tushiradi.
Bu darsda
- Bittalab
INSERTnega sekin ekanini ikki sabab bilan tushuntirasiz. - Ko'p qatorli
VALUESvaunnestbilan partiyalab yozasiz, parametr chegarasini hisobga olasiz. COPY FROM STDINnipipelinebilan ishlatasiz va bo'lak hajmi nega muhimligini bilasiz.- COPY xatosi butun importni bekor qilishini va PG 17+
ON_ERROR ignoreni bilasiz. - 1 mln qatorni xotirani to'ldirmay
pg-cursorvapg-query-streambilan o'qiysiz.
Oldin bilishingiz kerak: Parametrli so'rovlar va SQL injection, pipeline, finished va stream xatolari, Backpressure, INSERT va RETURNING.
1. Nega bu kerak?
Tasavvur qiling: vazifalar ilovasiga "eski ro'yxatni import qilish" tugmasi qo'shildi. Foydalanuvchi CSV fayl yuklaydi — boshqa ilovadan eksport, 5 000 qator. Yoki 22-qismdagi brauzer versiyasidan serverga ko'chish: minglab foydalanuvchining localStorage dagi vazifalari. Yoki tunda tahliliy jadvalga million qator.
Birinchi xayolga keladigan kod — sikl ichida INSERT:
for (const q of qatorlar) {
await client.query(
"INSERT INTO t46 (egasi_id, matn, bajarildi) VALUES ($1, $2, $3)",
q,
);
}Kanon o'lchovi (natijalar/tajriba/t46-copy.txt, PostgreSQL 18.6 Docker'da, Node 24.21.0, 3 marta mediana):
bittalab INSERT, autocommit 5000 qator: 34581 ms (145 qator/s)5 000 qatorga 35 soniya. 100 000 qatorga — 11 daqiqadan ortiq (kanon buni 100 000 da o'lchamadi ham, juda uzoq). Foydalanuvchi bu vaqtda brauzerni yopib yuboradi.
O'xshatish: 1000 ta xatni pochtaga yuborish. Har xat uchun pochtaga borib-kelish — bittalab INSERT. Hammasini bitta qopga solib bir marta borish — partiya. Pochta bilan "konveyer" kelishuvi — xatlarni lenta bo'ylab tinmay jo'natish — COPY.
2. Nega bittalab sekin: ikki sabab
2.1 Tarmoq aylanishi
Har await client.query(...) — so'rov ketadi, javob keladi, keyingisi ketadi. Kanonda bu aylanish ~1.3 ms edi (postgres.js va boshqa driverlar dagi ketma-ket o'lchov). 5 000 so'rov × aylanish — faqat kutishga bir necha soniya.
2.2 Har COMMIT — diskka yozish
Ikkinchi, kattaroq sabab. BEGIN siz har INSERT — o'z tranzaksiyasi (autocommit). ACID dagi D — durability: COMMIT qaytgan bo'lsa, ma'lumot elektr o'chsa ham saqlanadi. Buning uchun Postgres har COMMIT'da yozuvlar jurnalini (WAL — Write-Ahead Log) diskka majburan tushiradi (fsync). 5 000 COMMIT — 5 000 marta diskni kutish.
Hammasini bitta tranzaksiyaga solsak — COMMIT bitta:
bittalab INSERT, bitta tranzaksiyada 5000 qator: 6592 ms (759 qator/s)5 baravar tez — kodda faqat BEGIN va COMMIT qo'shildi (Tranzaksiya asoslari). Lekin tarmoq aylanishi qoldi: 5 000 so'rov hamon 5 000 marta borib-keladi. Keyingi qadam — so'rovlar sonini kamaytirish.
Tekshirib ko'ring: Bitta tranzaksiyadagi 5 000 INSERT ning 4 999-chisi xato bersa, nechta qator saqlanadi?
Javob
Hech qaysi (agar xatoni ushlab, SAVEPOINT bilan davom etmasangiz). Tranzaksiya "yo hammasi, yo hech biri". Import uchun bu ko'pincha to'g'ri xulq: yarim import qilingan fayl — eng chalkash holat. Qatorma-qator rad etish kerak bo'lsa — Tranzaksiya asoslari dagi SAVEPOINT naqshi yoki pastdagi ON_ERROR.
3. Partiya: bitta so'rovda ko'p qator
3.1 Ko'p qatorli VALUES
INSERT va RETURNING darsida ko'rgan edik: INSERT … VALUES (…), (…), (…) — bir so'rovda bir nechta qator. Node'da parametrlarni yig'amiz: har qator uchun uchta $N:
function valuesQur(bolak) {
const joylar = bolak.map((_, j) =>
`($${j * 3 + 1}, $${j * 3 + 2}, $${j * 3 + 3})`,
);
return {
text: "INSERT INTO t46 (egasi_id, matn, bajarildi) VALUES " +
joylar.join(", "),
values: bolak.flat(),
};
}
const s = valuesQur([
[1, "Bozordan meva olish #bozor", false],
[2, "IELTS writing task 2 mashqi #ingliz", true],
]);
console.log(s.text);
console.log(s.values.length); // 6Konsolda:
INSERT INTO t46 (egasi_id, matn, bajarildi) VALUES ($1, $2, $3), ($4, $5, $6)
6Qiymatlar baribir parametrda — injection xavfi yo'q (Parametrli so'rovlar). Kanon 100 000 qatorni turli partiya hajmlarida yozdi:
VALUES, partiya 100 100000 qator: 6992 ms (14302 qator/s)
VALUES, partiya 1000 100000 qator: 2205 ms (45360 qator/s)
VALUES, partiya 5000 100000 qator: 1464 ms (68286 qator/s)Partiya 100 ham bittalabdan 19 baravar tez (14 302 va 759). 1000 dan 5000 ga o'tish esa atigi 1.5 baravar — so'rovlar soni 100 tadan 20 taga tushdi, aylanish endi asosiy xarajat emas.
3.2 Parametr chegarasi: 65 535
Kanon kattaroq partiyani sinadi — 22 000 qator, ya'ni 66 000 parametr:
VALUES, 22 000 qator (66 000 parametr): XATO bind message has 464 parameter formats but 0 parametersXabar chalg'itadi: "bind xabarida 464 ta parametr formati bor, lekin 0 ta parametr". Biz 66 000 berdik — 464 qayerdan? Postgres protokolida parametrlar soni 16 bitli son bilan yoziladi: eng ko'pi 65 535. 66 000 sig'maydi va "o'ralib" qoladi:
console.log(66000 % 65536); // 464
console.log(Math.floor(65535 / 3)); // 21845Ikkinchi qator — 3 ustunli jadvalga bitta so'rovda eng ko'pi 21 845 qator. Qoida: partiya hajmi × ustunlar soni ≤ 65 535. Amalda 1 000–5 000 qator — yetarli tez va chegaradan uzoq.
3.3 unnest: ustunlar massiv bo'lib
Parametrlar sonidan butunlay qutulish yo'li bor: har ustun uchun bitta massiv parametr. Postgres'ning unnest funksiyasi bir nechta massivni qatorlarga "yoyadi":
await client.query(
`INSERT INTO t46 (egasi_id, matn, bajarildi)
SELECT * FROM unnest($1::int[], $2::text[], $3::bool[])`,
[
b.map((x) => x[0]), // hamma egasi_id lar
b.map((x) => x[1]), // hamma matnlar
b.map((x) => x[2]), // hamma bajarildi lar
],
);Kanon chiqishi:
unnest($1::int[], $2::text[], $3::bool[]), partiya 10000 100000 qator: 896 ms (111605 qator/s)
unnest($1::int[], $2::text[], $3::bool[]), partiya 100000 100000 qator: 741 ms (135041 qator/s)Har so'rovda 3 ta parametr, qator soni qancha bo'lsa ham. Butun 100 000 qator bitta so'rovda — 741 ms. VALUES ning eng yaxshisidan 2 baravar tez: SQL matni qisqa, Postgres 300 000 ta $N ni tahlil qilmaydi. Bu usulning yana bir afzalligi — oddiy INSERT … SELECT: ON CONFLICT (UPSERT va MERGE) va RETURNING bilan birga ishlaydi.
Tekshirib ko'ring: 12 ustunli jadvalga
VALUESbilan bitta so'rovda eng ko'pi nechta qator sig'adi?unnestbilan-chi?
Javob
VALUES: Math.floor(65535 / 12) = 5 461 qator. unnest: parametrlar soni 12 (har ustunga bitta massiv) — qator soni parametr chegarasiga bog'liq emas. Chegarani endi xotira va so'rov hajmi belgilaydi.
4. COPY FROM STDIN
4.1 Nima va qanday
COPY — Postgres'ning ommaviy yuklash buyrug'i. U SQL tahlili, rejalashtirish va har qator uchun alohida ishlov berishni chetlab o'tadi: ma'lumot CSV yoki matn oqimi sifatida kirib, to'g'ri jadvalga yoziladi. psql'dagi \copy (psql chuqur) ham aynan shuni ishlatadi.
FROM STDIN — "fayldan emas, ulanish orqali yuboraman". Node'da buni pg-copy-streams paketi (kanon: 7.0.0) Writable oqim qilib beradi. Unga oqimni pipeline bilan ulaymiz:
import { pipeline } from "node:stream/promises";
import { Readable } from "node:stream";
import copyStreams from "pg-copy-streams";
const csv = (q) =>
`${q[0]},"${q[1].replaceAll('"', '""')}",${q[2]}\n`;
const kirish = client.query(copyStreams.from(
"COPY t46 (egasi_id, matn, bajarildi) FROM STDIN WITH (FORMAT csv)",
));
await pipeline(
Readable.from(function* () {
for (let i = 0; i < qatorlar.length; i += 1000) {
yield qatorlar.slice(i, i + 1000).map(csv).join("");
}
}()),
kirish,
);Generator har safar 1000 qatorni bitta satrga yig'ib beradi. pipeline backpressure'ni o'zi boshqaradi (Backpressure): Postgres ulgurmasa, generator to'xtaydi — xotira to'lmaydi.
4.2 Bo'lak hajmi: kanonning eng kutilmagan raqami
Kanon COPY'ni ikki xil sinadi — har qator alohida bo'lak va 1000 qatorlik bo'laklar:
COPY FROM STDIN (CSV oqim) 100000 qator: 11477 ms (8713 qator/s)
COPY FROM STDIN (CSV, 1000 qatorlik bo'lak) 100000 qator: 244 ms (409061 qator/s)Bir xil buyruq, bir xil ma'lumot — 47 baravar farq. Har qatorni alohida yield qilganda oqimga 100 000 ta mayda bo'lak tushdi. Kanon o'lchadi, lekin sababini alohida tekshirmadi; bizning izohimiz: har bo'lak drayverda alohida yozish va tarmoq paketi bo'lishi mumkin, ularning har biri xarajat. 1000 qatorlik bo'lakda — 100 ta yozish.
Saboq: "COPY — eng tez" faqat bo'laklar katta bo'lsa rost. Noto'g'ri ishlatilgan COPY partiyali VALUES dan 8 baravar sekin chiqdi.
- bittalab, autocommit145 qator/s
- bittalab, 1 tranzaksiya759 qator/s
- COPY, har qator bo'lak8 713 qator/s
- VALUES, partiya 100045 360 qator/s
- VALUES, partiya 500068 286 qator/s
- unnest, 100 000135 041 qator/s
- COPY, 1000 qatorlik bo'lak409 061 qator/s
Manba: IlmHamroh kanon o'lchovi t46, 2026-10-24: Node 24.21.0, pg 8.23.1, pg-copy-streams 7.0.0, PostgreSQL 18.6 (Docker, WSL2), Windows 11, i5-12500H; 100 000 qator (bittalab — 5 000), 3 marta mediana
Grafikda chapdan o'ngga: har pog'ona tezlikni bir necha baravar oshiradi. Eng chap va eng o'ng orasida — 2 800 baravar. Bitta ustunga e'tibor bering: noto'g'ri bo'laklangan COPY pastda, partiyalardan ham sekin.
4.3 CSV'ni to'g'ri yozish
COPY CSV'ni o'zi tahlil qiladi — biz to'g'ri CSV berishimiz kerak. Matnda vergul, qo'shtirnoq yoki yangi qator bo'lsa, maydon qo'shtirnoqqa olinadi va ichidagi " ikkilanadi:
const csvMaydon = (s) => `"${String(s).replaceAll('"', '""')}"`;
const csvQator = (q) => q.map(csvMaydon).join(",") + "\n";
console.log(csvQator([1, 'Kitob: "O\'tkan kunlar"', true]));Konsolda:
"1","Kitob: ""O'tkan kunlar""","true"Ichki " lar "" bo'ldi. Bu — injection'ning CSV'dagi o'xshashi: to'g'ri escape qilinmagan qo'shtirnoq maydonni "sindiradi" va keyingi ustunlar siljiydi. Faqat SQL emas, ma'lumot buziladi.
4.4 Bitta yomon qator — butun COPY bekor
COPY yomon qator: 22P02 invalid input syntax for type integer: "x"; jadvalda 0 qator
COPY ON_ERROR ignore (PG 17+): jadvalda 2 qatorUch qatordan ikkinchisida egasi_id o'rnida x. Natija — 0 qator: COPY bitta buyruq, bitta tranzaksiya; bitta xato — hammasi bekor. PostgreSQL 17 dan ON_ERROR ignore opsiyasi bor: tur o'girishda xato bergan qatorlarni o'tkazib yuboradi (LOG_VERBOSITY verbose — qaysilari o'tkazilganini log'ga yozadi). Kanon: 2 qator yozildi.
Diqqat:
ON_ERROR ignorefaqat kirish formati xatolarini (matnni songa o'girib bo'lmadi) o'tkazadi. CHECK, NOT NULL, UNIQUE, FOREIGN KEY buzilishi baribir butun COPY'ni bekor qiladi. Shuning uchun tavsiya etiladigan naqsh: avval cheklovsiz vaqtinchalik jadvalga COPY, keyin SQL bilan tekshirib,INSERT … SELECTbilan asosiy jadvalga.
5. Katta natijani o'qish
5.1 Muammo: hammasi xotirada
client.query("SELECT * FROM katta") — pg hamma qatorlarni olib, massivga yig'adi va keyin qaytaradi. Kanon 1 mln qatorli jadvalni uch usulda o'qib, Node xotirasini (heap cho'qqisi) o'lchadi:
butun natija (client.query) 1000000 qator, 2011 ms, heap cho'qqisi +229 MB
pg-cursor, 1000 tadan 1000000 qator, 3863 ms, heap cho'qqisi +52 MB
pg-query-stream → Writable 1000000 qator, 4019 ms, heap cho'qqisi +54 MB| Usul | Vaqt | Xotira | Qachon |
|---|---|---|---|
client.query |
2.0 s | +229 MB | natija kichik (sahifa, hisobot) |
pg-cursor |
3.9 s | +52 MB | partiyalab qayta ishlash |
pg-query-stream |
4.0 s | +54 MB | oqim zanjiri (fayl, HTTP) |
Butun natija — eng tez, lekin xotira 4 baravar ko'p. 10 mln qatorda u 2 GB bo'ladi va jarayon yiqiladi (Node xotira limitlari). Cursor va stream — ikki baravar sekinroq, lekin xotira natija hajmiga bog'liq emas: har lahzada faqat 1000 qator.
5.2 pg-cursor
Kursor (cursor) — Postgres tomonida "qayergacha o'qidim" belgisi. Natija serverda turadi, biz uni bo'lak-bo'lak so'raymiz:
import Cursor from "pg-cursor";
const cur = client.query(new Cursor("SELECT * FROM t46_oqi"));
let n = 0;
for (;;) {
const qatorlar = await cur.read(1000); // keyingi 1000 ta
if (qatorlar.length === 0) break;
n += qatorlar.length; // shu yerda qayta ishlash
}
await cur.close();Har read(1000) — bitta tarmoq aylanishi. await tufayli keyingi bo'lak oldingisi qayta ishlangach so'raladi — backpressure tabiiy ravishda bor. Kursor ochiq turganda ulanish band: hovuzdan olingan bo'lsa, oxirida close() va release().
5.3 pg-query-stream
Xuddi shu kursor, lekin Readable oqim sifatida — pipeline zanjiriga to'g'ridan ulanadi:
import QueryStream from "pg-query-stream";
import { Writable } from "node:stream";
let n = 0;
await pipeline(
client.query(new QueryStream("SELECT * FROM t46_oqi", [], {
batchSize: 1000,
})),
new Writable({
objectMode: true,
write(_qator, _enc, cb) { n++; cb(); },
}),
);Haqiqiy ilovada Writable o'rnida — CSV'ga o'giruvchi Transform (O'z Readable/Transform stream'ingizni yozish) va HTTP javob yoki fayl. "Hamma vazifalarimni CSV qilib yuklab olish" tugmasi xotirani to'ldirmay ishlaydi.
5.4 Eksport: COPY TO STDOUT
O'qishning eng tez yo'li ham COPY — teskari yo'nalishda. Biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, pg-copy-streams 7.0.0):
import pg from "pg";
import { to as copyTo } from "pg-copy-streams";
import { pipeline } from "node:stream/promises";
const client = new pg.Client({
connectionString: process.env.DATABASE_URL,
});
await client.connect();
const sql = `COPY (SELECT id, matn, bajarildi FROM vazifalar
WHERE egasi_id = 1 ORDER BY id LIMIT 3)
TO STDOUT WITH (FORMAT csv, HEADER)`;
await pipeline(client.query(copyTo(sql)), process.stdout, {
end: false,
});
await client.end();id,matn,bajarildi
1,JavaScript kursining 26-qismini boshlash #o'qish,f
2,Postgres'ni Docker'da ko'tarish #o'qish #docker,t
8,Bozordan kartoshka va piyoz olish #bozor,tPostgres CSV'ni o'zi yasadi: sarlavha (HEADER), boolean — t/f. { end: false } — process.stdout ni yopmaslik (Backpressure darsidagi qoida). process.stdout o'rnida HTTP javob bo'lsa — eksport endpoint tayyor.
6. Qaysi usulni tanlash
flowchart TD
A[Necha qator?] -->|o'nlab| B[oddiy INSERT]
A -->|minglab| C[unnest yoki VALUES partiya]
A -->|yuz minglab+| D[COPY, katta bo'laklar]
C --> E{RETURNING yoki ON CONFLICT kerakmi?}
E -->|ha| F[unnest]
D --> G[vaqtinchalik jadval + INSERT SELECT]Diagramma — boshlang'ich yo'l-yo'riq. Har qarorni o'z ma'lumotingizda o'lchang: kanon raqamlari bitta mashinada, Docker ichidagi Postgres bilan olingan.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Import daqiqalab davom etadi | siklda autocommit INSERT |
partiya, unnest yoki COPY |
bind message has 464 parameter formats but 0 parameters |
65 535 dan ortiq parametr | partiyani kichraytiring yoki unnest |
| COPY kutilganidan sekin | har qator alohida bo'lak | 1000+ qatorni bitta bo'lakka yig'ing |
| Bitta yomon qator — 0 qator yozildi | COPY — bitta tranzaksiya | ON_ERROR ignore (PG 17+) yoki vaqtinchalik jadval |
| Ustunlar siljib ketdi | CSV'da " escape qilinmagan |
csvMaydon |
Katta SELECT da jarayon yiqiladi |
butun natija xotirada | pg-cursor / pg-query-stream |
8. Mashqlar
Mashqlar kurs/mashqlar/26/46-copy/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Bitta so'rovdagi eng ko'p parametr —
- 4 ustunli jadvalga
VALUESbilan eng ko'p qator — - Kanonda eng tez usul —
- PG 17+ da format xatoli qatorni o'tkazib yuborish —
ON_ERROR - Kursordan keyingi 500 qatorni olish —
await cur.(500)
Yechim
- 65535 — 16 bitli son; 66 000 → 464 (
66000 % 65536). - 16383 —
Math.floor(65535 / 4). - COPY — 1000 qatorlik bo'laklar bilan 409 ming qator/s.
- ignore — faqat kirish formati xatolari; cheklov buzilishi baribir bekor qiladi.
- read — bo'sh massiv qaytsa, natija tugadi.
2-mashq (o'rta): chegarani hurmat qiluvchi partiyalovchi
partiyalar(qatorlar, ustunlar, maksParam = 65535) generator funksiyasini yozing: qatorlar massivini shunday bo'laklarga bo'lsin-ki, har bo'lakdagi parametrlar soni (bo'lak uzunligi × ustunlar) maksParam dan oshmasin. Sinang: 10 qator, 3 ustun, maksParam = 9 — bo'lak uzunliklarini chiqaring. (function* — Generator funksiyalar.)
Yechim
function* partiyalar(qatorlar, ustunlar, maksParam = 65535) {
const hajm = Math.floor(maksParam / ustunlar);
if (hajm < 1) throw new RangeError("bitta qator ham sig'maydi");
for (let i = 0; i < qatorlar.length; i += hajm) {
yield qatorlar.slice(i, i + hajm);
}
}
const qatorlar = Array.from(
{ length: 10 },
(_, i) => [i, "matn", false],
);
const bolaklar = [...partiyalar(qatorlar, 3, 9)];
const uzunliklar = bolaklar.map((b) => b.length);
console.log(uzunliklar);
console.log(Math.floor(65535 / 3)); // 21845Konsolda:
[ 3, 3, 3, 1 ]
21845maksParam = 9, 3 ustun → 3 qatordan. Oxirgi bo'lak — qolgan 1 qator. Haqiqiy ishda sukut 65 535 ni emas, o'lchangan qulay hajmni (masalan 1 000) bering: chegara — himoya, maqsad emas.
3-mashq (qiyin): CSV import vaqtinchalik jadvalga
Uchta vazifani (biri ichida " va ', biri ichida vergul va yangi qator) COPY bilan vaqtinchalik jadvalga yozing: CREATE TEMP TABLE import_v (matn text NOT NULL, bajarildi boolean) (ulanish yopilganda o'zi o'chadi — asosiy bazaga tegmaydi). CSV'ni csvMaydon/csvQator bilan yasang, pipeline + pg-copy-streams ishlating, oxirida jadvalni o'qib chiqaring.
Yechim
import pg from "pg";
import { from as copyFrom } from "pg-copy-streams";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const csvMaydon = (s) => `"${String(s).replaceAll('"', '""')}"`;
const csvQator = (q) => q.map(csvMaydon).join(",") + "\n";
const vazifalar = [
["Bozordan meva olish #bozor", false],
['Kitob: "O\'tkan kunlar" ni o\'qish', true],
["Vergul, nuqta; va\nyangi qator", false],
];
const client = new pg.Client({
connectionString: process.env.DATABASE_URL,
});
await client.connect();
await client.query(
"CREATE TEMP TABLE import_v " +
"(matn text NOT NULL, bajarildi boolean)",
);
await pipeline(
Readable.from(vazifalar.map(csvQator)),
client.query(
copyFrom("COPY import_v FROM STDIN WITH (FORMAT csv)"),
),
);
const { rows } = await client.query("SELECT * FROM import_v");
console.log(rows);
await client.end();Biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, pg-copy-streams 7.0.0):
[
{ matn: 'Bozordan meva olish #bozor', bajarildi: false },
{ matn: `Kitob: "O'tkan kunlar" ni o'qish`, bajarildi: true },
{ matn: 'Vergul, nuqta; va\nyangi qator', bajarildi: false }
]Uchala qiyin matn ham butun keldi: " ikkilanib, vergul va yangi qator qo'shtirnoq ichida qoldi. "false" qo'shtirnoq ichida bo'lsa ham Postgres uni boolean ga o'girdi. Node matnda ham ", ham ' bo'lganini ko'rib, uni backtick bilan ko'rsatdi. Bu yerda bo'laklar kichik (3 qator) — katta importda 1000 qatorlik bo'laklarni unutmang.
9. Node qadami
vazifalar-api da bu darsning bilimi kerak bo'lgan joy bor: PUT /v1/vazifalar — butun ro'yxatni yangisi bilan almashtirish (25-qismda frontend sinxroni uchun qo'shilgan). Postgres omborida (PgOmbori.almashtirHammasi) birinchi variant (holat 44) har vazifani alohida so'rov bilan yozardi — darsning boshidagi "sikl ichida INSERT". Kanon o'lchovi (olchov/put-olchov.ts: 1000 vazifa — 500 tasi yangilanadi, 500 tasi yangi; 5 marta, mediana):
## holat 44
almashtirHammasi 1000 vazifa (500 yangilash + 500 yangi): 1000 qaytdi, so'rovlar 1010, mediana 2175 ms (1603, 2067, 2175, 2442, 2872)Bitta PUT — 1 010 so'rov va 2 soniyadan ortiq. Bu yerda tranzaksiya allaqachon bitta (COMMIT bitta), demak qolgan xarajat — tarmoq aylanishlari. Davosi — «unnest: ustunlar massiv bo'lib» bo'limidagi naqsh. Qatorma-qator sikl o'rniga ikkita to'plam so'rovi (holat 48, qisqartirildi):
// mavjud vazifalar — bitta UPDATE
await c.query(
`UPDATE vazifalar v SET matn = u.matn, bajarildi = u.bajarildi,
tartib = u.tartib, ochirilgan_vaqt = NULL
FROM unnest($2::bigint[], $3::text[], $4::boolean[], $5::int[])
AS u(id, matn, bajarildi, tartib)
WHERE v.id = u.id AND v.egasi_id = $1`,
[this.#egasi, ...ustunlar(bor)]);
// yangi vazifalar — bitta INSERT … SELECT
await c.query(
`INSERT INTO vazifalar (id, egasi_id, matn, bajarildi, tartib)
OVERRIDING SYSTEM VALUE
SELECT u.id, $1, u.matn, u.bajarildi, u.tartib
FROM unnest($2::bigint[], $3::text[], $4::boolean[], $5::int[])
AS u(id, matn, bajarildi, tartib)`,
[this.#egasi, ...ustunlar((i) => !bor(i))]);ustunlar(tanla) — tanlangan vazifalardan to'rtta massiv yasaydi (id'lar, matnlar, bajarildi lar, tartib raqamlari); bor(i) — shu id bazada egasiniki bo'lsa true. Parametrlar har doim 5 ta — vazifa 10 ta bo'lsa ham, 10 000 ta bo'lsa ham, 65 535 chegarasi bu yerda xavf emas. (Asl kodda bajarilgan_vaqt uchun CASE ham bor — qisqartirishda olib tashlandi. OVERRIDING SYSTEM VALUE — mijoz bergan id ni GENERATED ALWAYS ustunga yozishga ruxsat, Primary key tanlovi.)
Xuddi shu o'lchov holat 48 da:
## holat 48
almashtirHammasi 1000 vazifa (500 yangilash + 500 yangi): 1000 qaytdi, so'rovlar 12, mediana 147 ms (136, 139, 147, 153, 274)Nima bo'ldi? So'rovlar 1 010 dan 12 ga, vaqt 2 175 ms dan 147 ms ga — 15 baravar tez. Qolgan 12 so'rov vazifalar soniga bog'liq emas. Ular: BEGIN va COMMIT, egasi qulfi, id'larni ikki tekshirish (begonami, o'zimnikimi), yangi id'lar uchun sequence. Yana: yo'qlarini Savatga tushirish, ikkita unnest, sequence'ni surish, ro'yxat versiyasi va javob uchun ro'yxatni o'qish. COPY bu yerda kerak emas: 1 000 qator uchun unnest yetarli, ustiga UPDATE … FROM va RETURNING bilan ishlaydi.
Atomiklik (butun PUT yo bo'ladi, yo yo'q) va commit — Tranzaksiya asoslari darsining «Node qadami» da: holat 48 o'sha darsda yakunlanadi. Katta natijani o'qish (pg-cursor, stream) API'ga kirmadi: 25-qismdagi CSV eksport ro'yxatni allaqachon sahifalab (ombor.royxat) o'qiydi, alohida kursor kerak bo'lmadi.
10. Real ishda
- Import/eksport — CRM'lar, buxgalteriya, "ma'lumotlaringizni yuklab oling" tugmalari (masalan Yevropadagi GDPR'ning ma'lumotni ko'chirish huquqi) — COPY va stream.
- ETL va tahlil — kechasi millionlab qatorni ko'chirish;
COPY+ vaqtinchalik jadval +INSERT … SELECT … ON CONFLICTstandart naqsh. - Migratsiya — eski tizimdan ko'chish; partiya hajmi va tranzaksiya chegaralari migratsiya rejasining asosiy qismi.
- Intervyuda: "Million qatorni qanday tez yozasiz?", "COPY nega INSERT dan tez?", "Katta SELECT natijasini xotirani to'ldirmay qanday qayta ishlaysiz?"
Xulosa
- Bittalab
INSERTsekin: har so'rov — tarmoq aylanishi, har autocommit — diskkafsync(kanon: 145 qator/s; bitta tranzaksiyada 759). - Partiya:
VALUES(≤ 65 535 parametr — aks holda chalg'ituvchi "464" xatosi) yokiunnest(har ustunga bitta massiv, 135 ming qator/s). COPY FROM STDIN+pipeline— eng tez (409 ming qator/s), lekin faqat katta bo'laklar bilan; har qator bo'lak — 8.7 ming.- COPY — bitta tranzaksiya: yomon qator hammasini bekor qiladi; PG 17+
ON_ERROR ignorefaqat format xatolari uchun. - Katta natija:
pg-cursor/pg-query-stream— xotira 229 MB o'rniga ~52 MB, vaqt 2 baravar ko'p; eksport —COPY TO STDOUT.
Keyingi dars: LISTEN/NOTIFY — Postgres'ning o'zi "vazifa o'zgardi" xabarini yuboradi: broker'siz oddiy realtime va uning 8 000 baytlik chegarasi.
Manbalar
- PostgreSQL 18 hujjati — COPY (
FORMAT csv,ON_ERROR,LOG_VERBOSITY): https://www.postgresql.org/docs/18/sql-copy.html - PostgreSQL 18 hujjati — Populating a Database (ommaviy yuklash maslahatlari): https://www.postgresql.org/docs/18/populate.html
- PostgreSQL 18 hujjati — Message Formats (
Bind: parametrlar soni Int16): https://www.postgresql.org/docs/18/protocol-message-formats.html - pg-copy-streams: https://github.com/brianc/node-pg-copy-streams · pg-cursor: https://node-postgres.com/apis/cursor · pg-query-stream: https://github.com/brianc/node-postgres/tree/master/packages/pg-query-stream
- Kanon:
tajriba/t46-copy.mjs,natijalar/tajriba/t46-copy.txt(PostgreSQL 18.6, Node 24.21.0, pg 8.23.1, 2026-10-24).COPY TO STDOUTva 3-mashq — biz ishga tushirdik (PostgreSQL 17.6).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!