Lukottoman (lock-free) Hash Table -indeksin rakentaminen SharedArrayBufferin
(SAB) pC$C$lle vaatii atomisia operaatioita ja huolellista muistinasettelua.
Koska kyseessC$ on jaettu muisti, emme voi kC$yttC$C$ perinteisiC$ JS-olioita
tai linkitettyjC$ listoja (chaining) tC6rmC$ysten hallintaan.
Paras ja nopein lC$hestymistapa tC$hC$n on Avoin osoitteenmuodostus
lineaarisella kokeilulla (Open Addressing with Linear Probing) yhdistettynC$
CAS (Compare-And-Swap) -operaatioihin.
TC$ssC$ on arkkitehtuuri, jolla se rakennetaan.
1. Indeksin muistirakenne
Hash Table luodaan yhden suuren Int32Array-nC$kymC$n pC$C$lle. Jokainen taulun
"slotti" (C$mpC$ri) vie 8 tavua (kaksi 32-bittistC$ kokonaislukua).
Slotit rakennetaan perC$kkC$in:
[ Slot 0: Hash | Slot 0: Offset ] [ Slot 1: Hash | Slot 1: Offset ] ...
* Hash (32-bit): Avaimen (esim. user_123) tiiviste, joka on laskettu nopealla
algoritmilla (kuten MurmurHash3 tai FNV-1a).
* Offset (32-bit): Osoite (tavuina) Data-SAB:ssa, josta varsinainen
JSON-payload alkaa.
Varaamme erikoistilat Hash-kentC$lle ilmaisemaan slotin tilaa:
* 0: TyhjC$ slotti (Empty)
* -1: Poistettu slotti (Tombstone)
* Kaikki muut: Varattu (Occupied)
2. Lock-free Operaatiot (Atomics.compareExchange)
JavaScriptin Atomics.compareExchange on tC$mC$n arkkitehtuurin sydC$n. Se
lukee arvon, vertaa sitC$ odotettuun, ja jos ne tC$smC$C$vC$t, vaihtaa tilalle
uuden arvon kaikki yhdellC$ keskeytymC$ttC6mC$llC$ CPU-syklillC$.
A. Datan kirjoittaminen (INSERT / UPDATE)
Kun core saa komennon kirjoittaa avaimen "user_123" dataan, ja uusi payload on
tallennettu Data-SAB:iin osoitteeseen 4096, indeksiin lisC$ys tapahtuu nC$in:
* Laske hash: Muuta "user_123" 32-bittiseksi luvuksi (esim. 847291).
* Laske aloitusindeksi: index = hash % kapasiteetti.
* Probing-luuppi:
* Lue slotin tila atomisesti: Atomics.load(indeksiSAB, index * 2).
* Jos tyhjC$ (0): YritC$ varata slotti.
Atomics.compareExchange(indeksiSAB, index * 2, 0, hash)
* Jos paluuarvo on 0, onnistuit! Slotti on sinun. Kirjoita offset:
Atomics.store(indeksiSAB, index * 2 + 1, 4096).
* Jos paluuarvo on jotain muuta, jokin toinen sC$ie ehti ensin. Jatka
luuppia.
* Jos varattu ja hash tC$smC$C$: TC$mC$ on UPDATE. Koska hash on jo oikein,
riittC$C$ kun pC$ivitC$t offsetin atomisesti uuteen: Atomics.exchange(indeksiSA
B, index * 2 + 1, 4096).
* Jos varattu ja hash ei tC$smC$C$ (TC6rmC$ys): Siirry seuraavaan slottiin
(index = (index + 1) % kapasiteetti) ja yritC$ uudelleen.
B. Datan lukeminen (GET)
Lukeminen on puhdas lock-free operaatio, joka ei vaadi edes compareExchangea,
pelkkC$ Atomics.load riittC$C$.
* Laske hash avaimesta "user_123".
* Mene indeksiin hash % kapasiteetti.
* Lue hash slotista.
* Jos 0, dataa ei ole olemassa (palauta null/undefined).
* Jos hash tC$smC$C$, lue offset slotin toisesta puolikkaasta.
* Jos hash ei tC$smC$C$, tarkista seuraava slotti (linear probing).
C. Datan poistaminen (DELETE)
Et voi palauttaa poistetun slotin tilaa takaisin nollaan (0), koska se
rikkoisi lineaarisen kokeilun ketjun muilta avaimilta, joiden hash oli osunut
samaan slottiin (ne olettaisivat ketjun pC$C$ttyneen).
Siksi poisto tehdC$C$n vaihtamalla Hash-kenttC$C$n "hautakivi" (Tombstone,
esim. -1).
Atomics.compareExchange(indeksiSAB, index * 2, hash, -1)
Kun uusi INSERT etsii paikkaa, se saa kirjoittaa Tombstonen pC$C$lle
(kC$sitellen sitC$ kuin tyhjC$C$ slottia). Lukijat taas tietC$vC$t hypC$tC$
Tombstonen yli ja jatkaa etsimistC$.
3. Hash-tC6rmC$ykset ja lopullinen totuus
TC$mC$ on kriittinen yksityiskohta: Kaksi tC$ysin eri avainta voi tuottaa
saman 32-bittisen hashin.
Indeksi sisC$ltC$C$ pelkkiC$ hasheja, joten se ei pysty varmuudella erottamaan
kahta tC6rmC$C$vC$C$ avainta toisistaan. Indeksin tehtC$vC$ onkin toimia
ainoastaan "nopeana karsijana".
Lopullinen varmistus tehdC$C$n aina Data-SAB:ssa:
* Indeksi sanoo: "EtsimC$si hash on offsetissa 4096".
* Core hyppC$C$ Data-SAB:iin offsettiin 4096.
* Kirjoittamassasi Data-SAB:n rakenteessa tulee aina olla mukana
alkuperC$inen avain. Esim:
[ Tila | Pituus | Avaimen Pituus (esim. 8) | Avain ("user_123") | JSON
Payload ]
* Core lukee alkuperC$isen avaimen. Jos se on tasan "user_123", data lC6ytyi.
Jos se on jotain muuta (hash-tC6rmC$ys), core palaa Hash Tableen ja jatkaa
luuppia seuraavaan slottiin.
Kokorajoitukset ja Resizing
Lock-free rakenteen pahin vihollinen on dynaaminen koon muuttaminen
(resizing). Jos Hash Table tC$yttyy (load factor yli 70%), lineaarinen kokeilu
hidastuu dramaattisesti.
Koska olet tekemC$ssC$ mquickjs-mikrokerneliin pohjautuvaa arkkitehtuuria,
kannattaa vC$lttC$C$ monimutkaisia lock-free resize -algoritmeja. Ratkaisu:
Esivaraa kerralla valtava SAB pelkC$lle indeksille, tai ota kC$yttC6C6n
segmentoidut indeksit (Array of SABs), jolloin uuden indeksisegmentin voi
lisC$tC$ lennosta vanhojen rinnalle, kun edellinen tC$yttyy.