Download Game! Currently 65 players and visitors. Last logged in:OrdosZhouyuKlarenceQumni

Blitzer's Blog >> 71926

Back to blogs index
Posted: 01 Sep 2026 02:47 [ permalink ]
Olet asiantuntija jC$rjestelmC$arkkitehti. Sinun pitC$C$ pC$ivittC$C$ olemassa
olevaa koodipohjaa (lockfree-ram-db) lisC$tC$ksesi datan pysyvyyden
(Persistence) ominaisuuksia: Zero-Copy Snapshots ja Fast Recovery. Tavoitteena
on, ettC$ taustaprosessi (esim. OmegaServerissC$ app.js) dumppaa koko
SharedArrayBufferin (tai sen kC$ytetyn osan) levylle binC$C$ritiedostona
sC$C$nnC6llisesti (esim. 10s), ilman lukkoja. Ja kC$ynnistyksessC$ ladataan
snapshot.bin takaisin SharedArrayBufferiin ennen workerien kC$ynnistystC$.
Ota huomioon DESIGN.md:n arkkitehtuuri, erityisesti ZERO_COPY_SNAPSHOTS
(ramdb-mem-0005) ja APPEND_ONLY_LOG (ramdb-store-0002). MyC6s mahdollisesti
FULL_STATE_SNAPSHOTS (ramdb-dist-0002) mainitaan. PC$ivitC$ tarvittavat
tiedostot (todennC$kC6isesti app.js ja core.js) ja kerro integraatio.
Nykyinen koodi: core.js kC$yttC$C$ SharedArrayBufferia, sisC$ltC$C$
hash-taulun, append-only login, SPSC ring bufferit viestivC$ylinC$. app.js luo
SAB:n, luo core:n, luo in/out bus:t, spawnaa workerit. Huomaa: core
konstruktori ottaa bufferSize ja luo oman SAB:n. Mutta app.js luo ison SAB:n
(core + bus), mutta sitten core luo oman erillisen SAB:n! TC$mC$ on bugi
nykyisessC$ koodissa: app.js varaus this.sab ei ole sama kuin core:n sisC$inen
this.sab. TC$mC$ tC$ytyy korjata, jotta snapshot toimisi koko datan osalta.
EhkC$ core:n pitC$isi hyvC$ksyC$ valmis SAB ja offsetit, tai app.js:n pitC$isi
antaa core:lle SAB ja core kC$yttC$C$ sitC$.
LisC$ksi Zero-Copy Snapshot tarkoittaa koko SharedArrayBufferin kC$ytetyn osan
tallentamista levylle binC$C$rinC$. Koska SharedArrayBuffer on jo muistissa,
voimme ottaa siitC$ Uint8Array-nC$kymC$n ja kirjoittaa sen suoraan tiedostoon
(fs.writeFile tai stream). TC$mC$ on zero-copy siinC$ mielessC$, ettC$ ei
tarvitse serialisoida tai kopioida dataa erikseen, vain lukea muistista ja
kirjoittaa levylle. EhkC$ "zero-copy" tarkoittaa, ettC$ ei tarvitse muuntaa
JSONiksi tms., vaan dumpataan raaka SAB. Mutta on huomioitava, ettC$ SAB voi
muuttua samanaikaisesti, koska lock-free. Siksi snapshot voi olla
epC$johdonmukainen, mutta se on hyvC$ksyttC$vC$C$ tietyin rajoituksin.
Voisimme ottaa snapshotin esim. kC$yttC$mC$llC$ atomisia operaatioita lukea
koko alue. Koska SAB on jaettu, toinen sC$ie voi kirjoittaa samaan aikaan,
mutta me vain luemme perC$kkC$in. TC$mC$ voi johtaa osittain pC$ivittyneeseen
tilaan, mutta se on OK, koska tarkoitus on saada nopea "point-in-time" kopio
mahdollisimman lC$hellC$. Vaihtoehtoisesti voidaan kC$yttC$C$ copy-on-write
tai lukita, mutta se rikkoisi lock-free periaatteen. Suunnittelussa sanotaan
"Worker-sC$ie voi vain ottaa koko SharedArrayBufferin tilan sellaisenaan ja
puskuttaa sen suoraan ZFS-pooliin tai verkkosocketiin binC$C$ridumppina". Eli
se on tarkoitettu toimimaan ilman lukkoja, hyvC$ksyen pienen epC$johdonmukaisuu
den.
Fast Recovery: Kun app.js kC$ynnistyy, se tarkistaa onko snapshot.bin
olemassa. Jos on, lataa sen SharedArrayBufferiin. TC$mC$ tarkoittaa, ettC$
app.js:n tC$ytyy luoda SAB ja ladata snapshot siihen ennen kuin core
alustetaan. Mutta core:n konstruktori tC$llC$ hetkellC$ luo oman SAB:n.
PitC$C$ muuttaa niin, ettC$ core voi ottaa vastaan olemassa olevan SAB:n, tai
app.js luo SAB:n ja antaa sen core:lle. MyC6s core:n tC$ytyy pystyC$
alustamaan itsensC$ olemassa olevasta SAB:stC$, mukaan lukien header-tiedot
(uidCounter, logOffset, flags), hash table, log data. TC$mC$ vaatii, ettC$
core:n konstruktori osaa lukea olemassa olevan SAB:n tilan. Vaihtoehtoisesti
snapshot sisC$ltC$C$ koko SAB:n, joten voimme ladata sen suoraan ja sitten
core vain kC$yttC$C$ sitC$. Mutta core:n konstruktori nollaa headerit ja
hash-taulun, mikC$ pyyhkisi ladatun datan. Joten tC$ytyy muuttaa core:n
konstruktoria siten, ettC$ se voi ottaa parametrin loadFromExisting tms., joka
jC$ttC$C$ alustamatta headerit ja hash-taulun, tai lukee ne olemassa olevasta
SAB:stC$.
MyC6s huomioitava, ettC$ SAB:ssa on core:n lisC$ksi myC6s bus:t (in/out).
Snapshotin tulisi sisC$ltC$C$ vain core:n data, ei bus:eja. Koska bus:t ovat
vain viestintC$C$ varten, ne voidaan jC$ttC$C$ pois snapshotista. Mutta
nykyisessC$ arkkitehtuurissa core:lla on oma SAB, ja bus:t ovat toisessa
SAB:ssa (app.js:n luomassa). Itse asiassa app.js luo ison SAB:n ja antaa
siitC$ osan bus:eille, mutta core luo oman SAB:n. TC$mC$ on ristiriita.
PitC$isi yhtenC$istC$C$: joko core kC$yttC$C$ app.js:n SAB:ia, tai app.js ei
varaa bus:eja samaan SAB:iin. Yksinkertaisin korjaus: core luo oman SAB:n
datalle, ja app.js luo erillisen SAB:n bus:eille. Mutta silloin snapshotin
pitC$C$ tallentaa core:n SAB, ei buseja. TC$mC$ on OK.
NykyisessC$ app.js:ssC$ this.sab on iso SAB, mutta sitC$ ei kC$ytetC$ core:n
datalle, koska core luo oman. TC$mC$ on bugi, mutta ei vC$lttC$mC$ttC$
haittaa, jos core:n SAB on erillinen. Jatkossa snapshotin pitC$C$ tallentaa
core:n SAB, ei koko app.js:n SAB. Joten meidC$n pitC$C$ pC$C$stC$ kC$siksi
core:n SAB:iin. core.js:ssC$ this.sab on se. Voimme lisC$tC$ getterin
getSharedArrayBuffer().
Mutta jos haluamme "zero-copy" koko SAB:sta, voimme tallentaa core:n SAB:n
sellaisenaan. TC$mC$ onnistuu fs.writeFileSync(snapshotPath, Buffer.from(core.s
ab)). Mutta Buffer.from(SharedArrayBuffer) ei vC$lttC$mC$ttC$ ole suoraan
mahdollista, koska SAB ei ole ArrayBuffer. Voimme tehdC$ new Uint8Array(core.sa
b) ja sitten Buffer.from(uint8Array). TC$mC$ kopioi datan, mutta se on
vC$istC$mC$tC6ntC$ kun kirjoitetaan levylle, koska levylle kirjoitus vaatii
kopion. Zero-copy tarkoittaa tC$ssC$ yhteydessC$, ettC$ ei tehdC$
ylimC$C$rC$istC$ serialisointia/deserialisointia, vaan dumpataan raaka muisti.
Vaihtoehto: kC$ytetC$C$n fs.write suoraan Buffer.from(sab) - mutta se kopioi
myC6s. Ei voi vC$lttC$C$ kopiota levylle, mutta se on OK.
Nyt suunnitellaan muutokset:
core.js:
LisC$tC$C$n konstruktoriin mahdollisuus ottaa olemassa oleva SAB ja alustaa
siitC$. EhkC$ lisC$tC$C$n parametri existingSab (valinnainen). Jos annetaan,
niin ei luoda uutta SAB:ta, vaan kC$ytetC$C$n annettua ja luetaan headerit,
hash table, log jne. Mutta on hankalaa tietC$C$ tarkat koot. Parempi: antaa
core:lle valmis SAB ja tiedot offseteista? Nykyinen core kC$yttC$C$ kiinteitC$
offseteja: header 0-15, hash table alkaa 1024, log alkaa LOG_START. NC$mC$
ovat sisC$isiC$, joten jos annamme SAB:n, joka on luotu samalla tavalla, se
toimii. Joten core:n konstruktoriin voidaan lisC$tC$ sharedArrayBuffer
-parametri. Jos se annetaan, kC$ytetC$C$n sitC$; muuten luodaan uusi. Sitten
alustuksessa: jos SAB on uusi, alustetaan headerit, hash table, logOffset=0.
Jos SAB on olemassa, ei nollata, vaan luetaan olemassa olevat arvot. TC$mC$
onnistuu: tarkistamme, onko uidCounter jo asetettu (esim. >0), mutta
uidCounter voi olla 0 jos ei ole vielC$ insertoitu. EhkC$ parempi: annetaan
eksplisiittinen lippu loadFromSnapshot konstruktorille. Jos true, ei nollata
header/hash/logOffset. Jos false, alustetaan.
LisC$tC$C$n metodi getSharedArrayBuffer() palauttamaan this.sab.
LisC$tC$C$n metodi getLogStart() ja getLogEnd() tai getUsedSize() jotta
snapshot voi tallentaa vain kC$ytetyn osan logista? Suunnittelussa sanotaan
"koko SharedArrayBufferin (tai sen kC$ytetyn osan)". Nykyinen core:n SAB
sisC$ltC$C$ headerin, hash-taulun ja login. Hash table on kiinteC$ koko, log
on loppu. KC$ytetty osa on header + hash table + logOffset. Voimme tallentaa
koko SAB:n (se on 64MB), mikC$ on hieman turhaa, mutta yksinkertaista.
Vaihtoehtoisesti tallentaa vain header + hash table + logOffset verran, mutta
sitten recoveryn yhteydessC$ pitC$C$ tietC$C$ koko SAB:n koko. EhkC$
tallennetaan koko SAB, koska se on yksinkertaisempaa ja "zero-copy" koko SAB.
Levytila ei ole ongelma. Mutta jos haluamme optimoida, voidaan tallentaa myC6s
metadata: SAB koko, logOffset jne., mutta se monimutkaistaa. PidC$mme
yksinkertaisena: tallennetaan koko core:n SAB.
app.js:
Muutetaan siten, ettC$ core luodaan joko uudella SAB:lla tai ladatusta
snapshotista.
Ennen core:n luontia tarkistetaan snapshot.bin olemassaolo. Jos on, luetaan se
Bufferiksi ja muunnetaan SharedArrayBufferiksi. Sitten core luodaan tC$llC$
SAB:lla ja loadFromSnapshot=true. Jos ei ole, core luodaan uudella SAB:lla
(kuten ennenkin).
Huom: nykyisessC$ app.js:ssC$ on this.sab varattu bus:eille, mutta core luo
oman. Muutetaan niin, ettC$ busit kC$yttC$vC$t omaa SAB:ia (erillinen), ja
core kC$yttC$C$ omaa. TC$mC$ on selkeC$mpi. Tai voidaan yhdistC$C$, mutta
pitC$C$ mC$C$ritellC$ offsetit. Yksinkertaisempi: erilliset SAB:t. app.js luo
this.busSab bus:eille ja this.coreSab core:lle. Mutta coreSab luodaan joko
uutena tai ladattuna.
Snapshot-toiminto: lisC$tC$C$n startSnapshotTimer(intervalMs) joka
kC$ynnistC$C$ setIntervalin, joka kutsuu takeSnapshot(). takeSnapshot() lukee
this.core.getSharedArrayBuffer(), muuntaa Bufferiksi ja kirjoittaa
snapshot.bin. TC$mC$ voidaan tehdC$ asynkronisesti fs.promises.writeFile jotta
ei blokata pC$C$sC$iettC$. Mutta koska kirjoitus on suhteellisen nopea ja
taustalla, setInterval voi olla ok. Parempi kC$yttC$C$ setTimeout
rekursiivisesti tai setInterval ja fs.writeFile callbackilla.
Varmistetaan, ettC$ snapshot ei mene pC$C$llekkC$in: kC$ytetC$C$n lippua
snapshotInProgress.
KC$ynnistetC$C$n snapshot-ajastin vasta kun serveri on valmis.
Shutdown: ehkC$ otetaan viimeinen snapshot ennen sammutusta? Ei
vC$lttC$mC$tC6ntC$, mutta voidaan lisC$tC$.
Muut tiedostot: ei tarvitse muuttaa ring-buffer.js tai worker.js.
Integraatio-ohjeet: selitetC$C$n, miten snapshot otetaan kC$yttC6C6n, miten
recovery toimii.
LisC$ksi on huomioitava, ettC$ snapshot.bin sisC$ltC$C$ koko SAB:n, mukaan
lukien mahdollisesti keskenerC$iset operaatiot. Koska lock-free, snapshot voi
olla hieman epC$johdonmukainen. TC$mC$ on hyvC$ksytty suunnittelussa. Recovery
lataa sen ja jatkaa siitC$.
Nyt kirjoitetaan pC$ivitetyt tiedostot