Deno KV: built-in database

Kas yra Deno KV ir kodėl tai svarbu

Kai Deno komanda 2023 metų pavasarį pristatė Deno KV, daugelis kūrėjų pagalvojo: „Dar viena duomenų bazė? Rimtai?” Bet čia ne tiesiog dar vienas NoSQL sprendimas – tai fundamentalus požiūrio pokytis į tai, kaip mes galvojame apie duomenų saugojimą moderniose aplikacijose.

Deno KV yra įmontuota key-value tipo duomenų bazė, kuri veikia tiesiogiai Deno runtime aplinkoje. Nereikia jokių išorinių priklausomybių, nereikia atskirų serverių ar konteinerių. Tiesiog importuoji ir naudoji. Tai skamba paprasta, bet už šio paprastumo slypi labai protingai suprojektuota sistema, kuri gali tvarkyti rimtus darbo krūvius.

Pats įdomiausias dalykas – Deno KV yra sukurta ant FoundationDB, kuri yra viena patikimiausių paskirstytų duomenų bazių pasaulyje. Apple ją naudoja savo iCloud infrastruktūroje, o tai jau sako ką nors apie jos patikimumą. Lokalioje aplinkoje Deno KV naudoja SQLite, o kai deplojinat į Deno Deploy, automatiškai perjungia į paskirstytą FoundationDB variantą.

Kaip pradėti naudoti Deno KV

Pradėti naudoti Deno KV yra juokingai paprasta. Nereikia jokių instaliacijos procesų ar konfigūracijos failų. Štai minimalus pavyzdys:

„`javascript
const kv = await Deno.openKv();

// Įrašome duomenis
await kv.set([„users”, „john”], {
name: „John Doe”,
email: „[email protected]
});

// Skaitome duomenis
const result = await kv.get([„users”, „john”]);
console.log(result.value);
„`

Matote tą `[„users”, „john”]` dalį? Tai vadinamas raktu (key), ir jis veikia kaip hierarchinis kelias. Galite galvoti apie tai kaip apie failų sistemos struktūrą – galite turėti `[„users”, „john”, „settings”]` arba `[„users”, „john”, „posts”, „123”]`. Ši hierarchija leidžia labai elegantiškai organizuoti duomenis ir vėliau juos efektyviai ieškoti.

Vienas dalykas, kurį turite žinoti – Deno KV yra asinchroninis. Visi operacijos grąžina Promise objektus, todėl visada naudokite `await` arba `.then()`. Tai gali atrodyti kaip papildomas sudėtingumas, bet iš tikrųjų tai užtikrina, kad jūsų aplikacija neužsiblokuos laukdama duomenų bazės operacijų.

Transakcijos ir atomines operacijos

Čia prasideda tikroji magija. Deno KV palaiko ACID transakcijas, o tai reiškia, kad galite atlikti kelias operacijas, kurios arba visos pavyks, arba visos nepavyks. Nėra tarpinių būsenų, kurios galėtų sugadinti jūsų duomenis.

Pavyzdžiui, įsivaizduokite, kad kuriate paprastą bankinę sistemą (taip, žinau, bankai naudoja sudėtingesnius sprendimus, bet principas tas pats):

„`javascript
const kv = await Deno.openKv();

async function transferMoney(fromUser, toUser, amount) {
const fromAccount = await kv.get([„accounts”, fromUser]);
const toAccount = await kv.get([„accounts”, toUser]);

if (fromAccount.value.balance < amount) { throw new Error("Nepakanka lėšų"); } const atomic = kv.atomic() .check(fromAccount) .check(toAccount) .set(["accounts", fromUser], { ...fromAccount.value, balance: fromAccount.value.balance - amount }) .set(["accounts", toUser], { ...toAccount.value, balance: toAccount.value.balance + amount }); const result = await atomic.commit(); if (!result.ok) { throw new Error("Transakcija nepavyko"); } } ``` Tas `.check()` metodas yra labai svarbus – jis užtikrina, kad duomenys nebuvo pakeisti nuo tada, kai juos perskaitėte. Jei kažkas kitas tuo pačiu metu bandė pakeisti tuos pačius duomenis, transakcija nepavyks ir turėsite bandyti iš naujo. Tai vadinama optimistic concurrency control, ir tai yra vienas geriausių būdų tvarkyti konkurenciją paskirstytose sistemose.

Indeksavimas ir paieška

Key-value duomenų bazės turi vieną didelį trūkumą – jos puikiai veikia, kai žinote tikslų raktą, bet kaip ieškoti duomenų pagal kitus kriterijus? Deno KV sprendžia tai leisdama naudoti `list()` metodą su prefixais.

Tarkime, turite sistemą, kur saugote įrašus su laiko žymomis:

„`javascript
// Įrašome kelis įrašus
await kv.set([„posts”, „2024-01-15”, „post1”], { title: „Pirmas” });
await kv.set([„posts”, „2024-01-16”, „post2”], { title: „Antras” });
await kv.set([„posts”, „2024-01-17”, „post3”], { title: „Trečias” });

// Gauname visus įrašus iš sausio
const entries = kv.list({ prefix: [„posts”, „2024-01”] });

for await (const entry of entries) {
console.log(entry.key, entry.value);
}
„`

Čia yra vienas svarbus dalykas – `list()` grąžina async iteratorių, todėl naudojate `for await…of` ciklą. Tai reiškia, kad duomenys gali būti gaunami dalimis, o ne visi iš karto, kas yra labai efektyvu dirbant su dideliais duomenų kiekiais.

Bet kas, jei norite ieškoti pagal kitus laukus, ne tik raktą? Čia turite būti kūrybingi. Deno KV neturi automatinio indeksavimo kaip SQL duomenų bazės, todėl turite patys kurti papildomus indeksus. Pavyzdžiui:

„`javascript
async function addUser(userId, email, name) {
const atomic = kv.atomic()
.set([„users”, userId], { email, name })
.set([„users_by_email”, email], userId);

await atomic.commit();
}

async function findUserByEmail(email) {
const indexEntry = await kv.get([„users_by_email”, email]);
if (!indexEntry.value) return null;

const user = await kv.get([„users”, indexEntry.value]);
return user.value;
}
„`

Taip, tai reiškia papildomo darbo, bet tai suteikia jums pilną kontrolę. Galite sukurti tik tuos indeksus, kurių tikrai reikia, ir optimizuoti juos būtent jūsų naudojimo atvejui.

Darbas su dideliais duomenų kiekiais

Viena iš dažniausių klaidų, kurią matau žmonėms dirbant su Deno KV, yra bandymas įkelti visus duomenis į atmintį vienu metu. Tai veikia su keliais šimtais įrašų, bet kai turite tūkstančius ar milijonus, jūsų aplikacija tiesiog užsimuš.

Deno KV turi įmontuotą puslapiavimo palaikymą per `cursor` mechanizmą:

„`javascript
async function getAllUsersInBatches() {
let cursor = undefined;
const batchSize = 100;

do {
const result = await kv.list(
{ prefix: [„users”] },
{ limit: batchSize, cursor }
);

for (const entry of result) {
// Apdorojame kiekvieną įrašą
processUser(entry.value);
}

cursor = result.cursor;
} while (cursor);
}
„`

Šis kodas apdoroja visus vartotojus po 100 vienu metu. Cursor automatiškai saugo poziciją, kur sustojote, todėl galite tęsti nuo tos vietos. Tai ypač naudinga, kai darote batch processing arba migracijas.

Dar vienas patarimas – jei dirbate su dideliais objektais, pagalvokite apie jų skaidymą. Deno KV turi 64KB limitą vienam įrašui (bent jau lokalioje versijoje), todėl jei turite didesnius duomenis, turėsite juos suskaidyti:

„`javascript
async function saveLargeDocument(docId, content) {
const chunks = splitIntoChunks(content, 50000); // 50KB chunks

const atomic = kv.atomic()
.set([„docs”, docId, „meta”], {
chunkCount: chunks.length,
totalSize: content.length
});

chunks.forEach((chunk, index) => {
atomic.set([„docs”, docId, „chunks”, index], chunk);
});

await atomic.commit();
}
„`

Watch funkcionalumas ir real-time updates

Vienas iš coolest Deno KV funkcijų yra galimybė stebėti raktų pokyčius real-time. Tai leidžia kurti reaktyvias aplikacijas be jokių papildomų įrankių kaip Redis Pub/Sub ar WebSocket serverių.

„`javascript
const kv = await Deno.openKv();

// Stebime konkretų raktą
const stream = kv.watch([[„users”, „john”]]);

for await (const entries of stream) {
console.log(„Duomenys pasikeitė:”, entries[0].value);
}
„`

Tai veikia net paskirstytoje aplinkoje! Jei turite kelias Deno Deploy instances, ir viena iš jų pakeičia duomenis, visos kitos instances gaus notification. Tai atidarė duris visiškai naujiems architektūriniams šablonams.

Pavyzdžiui, galite sukurti paprastą chat sistemą:

„`javascript
// Serverio pusė
async function sendMessage(roomId, message) {
const timestamp = Date.now();
await kv.set([„messages”, roomId, timestamp], message);
}

// Kliento pusė (per Deno Deploy)
async function watchMessages(roomId) {
const stream = kv.watch([[„messages”, roomId]]);

for await (const entries of stream) {
// Siunčiame naują žinutę klientui per WebSocket
broadcastToClients(entries[0].value);
}
}
„`

Bet būkite atsargūs – `watch()` sukuria long-running connection, todėl jei turite daug tokių, galite išnaudoti resursus. Naudokite tai protingai ir visada turėkite cleanup logiką, kai watch’as nebereikalingas.

Performance optimizacijos ir best practices

Po kelių mėnesių darbo su Deno KV produkcinėje aplinkoje, išmokau kelias pamokas, kuriomis noriu pasidalinti.

Pirma, batch operacijos yra jūsų draugas. Vietoj to, kad darytumėte 100 atskirų `set()` operacijų, naudokite atomic transactions:

„`javascript
// Blogai
for (const user of users) {
await kv.set([„users”, user.id], user);
}

// Gerai
const atomic = kv.atomic();
for (const user of users) {
atomic.set([„users”, user.id], user);
}
await atomic.commit();
„`

Antra, pagalvokite apie savo raktų struktūrą iš anksto. Keisti ją vėliau yra skausminga, nes reikės migruoti visus duomenis. Gera praktika – įtraukti versijos numerį į raktų struktūrą:

„`javascript
// Vietoj
await kv.set([„users”, userId], data);

// Geriau
await kv.set([„v1”, „users”, userId], data);
„`

Taip, jei ateityje reikės keisti struktūrą, galėsite turėti `v2` šalia `v1` ir migruoti palaipsniui.

Trečia, naudokite `getMany()` kai reikia gauti kelis įrašus vienu metu:

„`javascript
const results = await kv.getMany([
[„users”, „user1”],
[„users”, „user2”],
[„users”, „user3”]
]);
„`

Tai daug efektyviau nei trys atskiri `get()` kvietimai, ypač kai dirbate su Deno Deploy, kur kiekvienas network round-trip prideda latency.

Ketvirta, būkite atsargūs su `list()` operacijomis be limitų. Visada nustatykite protingą `limit` parametrą, net jei manote, kad rezultatų bus nedaug. Duomenys auga greičiau nei tikitės.

Kada naudoti ir kada ne

Deno KV nėra silver bullet. Yra scenarijai, kur ji puikiai tinka, ir scenarijai, kur geriau rinktis ką nors kita.

Deno KV puikiai tinka:
– Serverless aplikacijoms Deno Deploy platformoje
– Prototypams ir MVP projektams
– Real-time aplikacijoms su watch funkcionalumu
– Session management ir caching
– Paprastoms CRUD aplikacijoms su nedideliu duomenų kiekiu

Deno KV ne pati geriausia, kai:
– Reikia sudėtingų SQL užklausų su JOIN’ais
– Dirbate su labai dideliais duomenų kiekiais (terabaitai)
– Reikia full-text paieškos (geriau Elasticsearch)
– Jau turite esamą infrastruktūrą su PostgreSQL ar MongoDB
– Komanda neturi patirties su NoSQL

Vienas dalykas, kurį pastebėjau – Deno KV yra puikus pasirinkimas, kai kuriate aplikaciją nuo nulio ir norite greitai pradėti. Vėliau, jei reikės, visada galite migruoti į specializuotesnį sprendimą, bet daugeliu atvejų Deno KV bus visiškai pakankamas.

Ką reikia žinoti apie pricing ir limitus

Lokalioje aplinkoje Deno KV yra visiškai nemokamas ir neturi jokių limitų (išskyrus tuos, kuriuos nustato jūsų disko talpa). Bet kai deplojinat į Deno Deploy, situacija šiek tiek kitokia.

Deno Deploy free tier’as duoda jums:
– 1GB storage
– 1 milijoną read operacijų per mėnesį
– 100,000 write operacijų per mėnesį

Tai skamba kaip nedaug, bet praktikoje to pakanka nedidelėms aplikacijoms. Vienas read operacija – tai vienas `get()` arba vienas įrašas iš `list()` rezultato. Write operacija – tai `set()` arba `delete()`.

Jei viršijate šiuos limitus, kainodara tampa:
– $0.30 už papildomą GB storage per mėnesį
– $0.10 už milijoną papildomų read operacijų
– $1.00 už milijoną papildomų write operacijų

Palyginus su AWS DynamoDB ar Google Firestore, tai gana konkurencingos kainos. Bet visada stebėkite savo naudojimą, nes kainos gali greitai išaugti, jei nesate atsargūs.

Vienas trikšas – naudokite caching strategijas. Jei turite duomenis, kurie retai keičiasi, cache’inkite juos aplikacijos atmintyje arba naudokite Deno Deploy edge cache. Tai gali dramatiškai sumažinti read operacijų skaičių.

Realūs naudojimo atvejai ir patirtis

Pats naudoju Deno KV keliuose projektuose, ir noriu pasidalinti realiais pavyzdžiais, kaip tai veikia praktikoje.

Vienas projektas – analytics dashboard’as, kuris renka ir rodo svetainės statistiką. Čia Deno KV puikiai tinka, nes:
– Duomenys organizuoti pagal datą (lengva naudoti hierarchinius raktus)
– Reikia greitų write operacijų (kiekvienas page view – tai write)
– Skaitymai dažniausiai pagal konkretų laiko intervalą (efektyvu su prefix paieška)

Raktų struktūra atrodo taip:
„`javascript
[„analytics”, siteId, „pageviews”, year, month, day, hour, visitorId]
[„analytics”, siteId, „totals”, year, month, day]
„`

Kitas projektas – task management sistema. Čia buvo šiek tiek sudėtingiau, nes reikėjo kelių indeksų:
– Tasks pagal projektą
– Tasks pagal vartotoją
– Tasks pagal status
– Tasks pagal deadline

Sprendimas – kurti kelis indeksus transakcijose:

„`javascript
async function createTask(task) {
const taskId = generateId();
const atomic = kv.atomic()
.set([„tasks”, taskId], task)
.set([„tasks_by_project”, task.projectId, taskId], taskId)
.set([„tasks_by_user”, task.userId, taskId], taskId)
.set([„tasks_by_status”, task.status, taskId], taskId)
.set([„tasks_by_deadline”, task.deadline, taskId], taskId);

await atomic.commit();
}
„`

Taip, tai reiškia 5 write operacijas vietoj vienos, bet tai vis tiek greičiau ir pigiau nei išorinis database serveris.

Trečias projektas – real-time notification sistema. Čia `watch()` funkcionalumas buvo game changer. Vietoj to, kad turėčiau atskirą Redis serverį ar WebSocket infrastruktūrą, tiesiog naudoju Deno KV:

„`javascript
// Kai vartotojas prisijungia
async function subscribeToNotifications(userId, sendToClient) {
const stream = kv.watch([[„notifications”, userId]]);

for await (const [entry] of stream) {
if (entry.value) {
sendToClient(entry.value);
// Išvalome notification po to, kai jį išsiuntėme
await kv.delete([„notifications”, userId]);
}
}
}

// Kai reikia išsiųsti notification
async function sendNotification(userId, message) {
await kv.set([„notifications”, userId], message);
}
„`

Visa sistema veikia puikiai, latency yra apie 100-200ms, kas yra visiškai priimtina real-time aplikacijai.

Migracija ir backup strategijos

Vienas dalykas, apie kurį niekas nemėgsta galvoti, kol neatsitinka bėda – kaip backup’inti ir migruoti duomenis. Deno KV dar neturi oficialaus backup/restore funkcionalumo, todėl turite patys pasirūpinti.

Paprasčiausias būdas – periodiškai eksportuoti visus duomenis:

„`javascript
async function backupToFile(filename) {
const entries = kv.list({ prefix: [] }); // Gauna viską
const data = [];

for await (const entry of entries) {
data.push({
key: entry.key,
value: entry.value,
versionstamp: entry.versionstamp
});
}

await Deno.writeTextFile(
filename,
JSON.stringify(data, null, 2)
);
}

async function restoreFromFile(filename) {
const content = await Deno.readTextFile(filename);
const data = JSON.parse(content);

// Restore’iname dalimis, kad neviršytume atomic transaction limito
for (let i = 0; i < data.length; i += 100) { const batch = data.slice(i, i + 100); const atomic = kv.atomic(); for (const entry of batch) { atomic.set(entry.key, entry.value); } await atomic.commit(); } } ``` Produkcinėje aplinkoje rekomenduoju šiuos backup'us daryti automatiškai kas naktį ir saugoti kelis paskutinius backup'us. Galite naudoti Deno Deploy cron jobs arba išorinį scheduler'į. Jei migruojate iš kitos duomenų bazės į Deno KV, procesas panašus – ištraukiate duomenis iš senosios DB, transformuojate juos į tinkamą raktų struktūrą, ir įkeliate į Deno KV batch'ais. Tik būtinai testuokite migraciją su kopija duomenų prieš darydami produkcinėje aplinkoje.

Ateities perspektyvos ir bendruomenės įnašas

Deno KV dar yra gana jaunas projektas – oficialiai išleistas tik 2023 metais. Bet jau dabar matau, kaip greitai jis auga ir tobulėja. Deno komanda aktyviai klauso bendruomenės feedback’o ir reguliariai prideda naujas funkcijas.

Keli dalykai, kurių asmeniškai laukiu ateityje:
– Geresnis full-text search palaikymas
– Built-in backup/restore funkcionalumas
– Geresni monitoring ir debugging įrankiai
– Galimybė naudoti Deno KV su self-hosted FoundationDB

Bendruomenė jau pradeda kurti įvairius wrapper’ius ir bibliotekus virš Deno KV. Pavyzdžiui, yra projektų, kurie prideda ORM-like funkcionalumą, automatinį schema validation, ir net GraphQL integracijas.

Vienas dalykas, kuris man labai patinka – Deno ekosistema yra labai moderni ir developer-friendly. Nėra legacy baggage, nėra sudėtingų konfigūracijų. Tiesiog rašai kodą ir jis veikia. Deno KV puikiai įsilieja į šią filosofiją.

Jei dar neišbandėte Deno KV, labai rekomenduoju skirti valandą ar dvi ir sukurti kokį nors mažą projektą. Geriausias būdas išmokti – tai praktika. Pradėkite nuo ko nors paprasto, pavyzdžiui, todo list aplikacijos ar paprastos blog sistemos. Pamatysite, kaip greitai galite eiti nuo idėjos iki veikiančios aplikacijos.

Ir nepamirškite – technologijos keičiasi, bet fundamentalūs principai lieka tie patys. Deno KV gali būti nauja, bet key-value duomenų bazių koncepcija egzistuoja dešimtmečius. Jei suprasite, kaip veikia Deno KV, lengvai persiorientuosite ir į kitus panašius sprendimus.

Daugiau

Kaip naudojant viešą Wi-Fi išlikti saugiam jungiantis prie paskyrų ir atliekant mokėjimus