Google Cloud Run, Datastore ja Firestore
Tähän asti sovellukset on ajettu yhdellä palvelimella, jolla on oma tiedostojärjestelmä ja SQLite-tietokanta. Tällä luennolla siirrytään Googlen pilveen: sovellus ajetaan Cloud Run -palvelussa, ja tiedot tallennetaan hajautettuun Firestore-tietokantaan. Luennon lopussa käsitellään Firestore-tietokannan suunnittelua viikkotehtävän näkökulmasta.
Sovellus pilvessä
Palvelimettomassa (serverless) pilvipalvelussa sovelluksen kehittäjä ei ylläpidä palvelimia. Hän toimittaa sovelluksen, ja pilvipalvelu huolehtii sen ajamisesta, päivityksistä ja skaalautumisesta. Googlen pilvessä tähän on kaksi palvelua:
- Cloud Run ajaa sovellusta konttina. Google suosittelee sitä kaikille uusille sovelluksille.
- App Engine on vanhempi palvelu, jolla tätä kurssia on aiemmin toteutettu. Se on edelleen toiminnassa, ja se esitellään lyhyesti omassa luvussaan.
Molemmissa palveluissa sovelluksesta käynnistetään tarpeen mukaan yksi tai useampia instansseja. Mitä enemmän sovelluksella on liikennettä tai mitä hitaammin se vastaa pyyntöihin, sitä herkemmin palvelu käynnistää lisää instansseja. Lisäinstanssit nopeuttavat vastauksia mutta kuluttavat resursseja, ja Google laskuttaa niistä.
Peräkkäiset pyynnöt voivat osua eri instansseille, ja instanssit käynnistyvät ja sammuvat ilman ennakkovaroitusta. Kaikki pysyvä tieto on siksi tallennettava tietokantaan, ei muuttujiin eikä paikallisiin tiedostoihin.
Flaskin evästepohjainen sessio toimii silti, koska tieto kulkee selaimen mukana: riittää, että kaikilla instansseilla on sama salainen avain. Palvelimen muistiin tallennettu sessio sen sijaan katoaisi heti, kun pyyntö osuu toiselle instanssille.
Cloud Run
Cloud Run ajaa HTTP-pyyntöihin vastaavaa sovellusta konttina. Konttia ei tarvitse rakentaa itse: kun sovellus julkaistaan lähdekoodista, Google rakentaa sen automaattisesti Cloud Build -palvelussa ja tallentaa sen Artifact Registryyn.
Mikä on kontti?
Ohjelmistokontti (engl. container) on paketti, jossa sovelluksen mukana on kaikki, mitä se tarvitsee toimiakseen: ohjelmointikielen tulkki, kirjastot, asetukset ja käyttöjärjestelmän perustyökalut. Koska kontti sisältää oman ympäristönsä, sovellus toimii samalla tavalla kehittäjän koneella, testissä ja pilvessä. Ei tarvitse arvailla, onko palvelimella oikea Python-versio tai tarvittavat kirjastot.
Kontti muistuttaa virtuaalikonetta, mutta on paljon kevyempi. Virtuaalikone sisältää kokonaisen käyttöjärjestelmän ytimineen. Kontit sen sijaan jakavat isäntäkoneen käyttöjärjestelmän ytimen, ja jokainen kontti on vain muista eristetty prosessi. Siksi kontti käynnistyy sekunneissa ja samalle koneelle mahtuu niitä paljon.
Kaksi käsitettä kannattaa erottaa toisistaan:
- Levykuva (engl. container image) on valmis, muuttumaton paketti, joka tallennetaan esimerkiksi Artifact Registryyn. Sitä kutsutaan myös kontin levykuvaksi.
- Kontti on levykuvasta käynnistetty, käynnissä oleva sovellus. Samasta levykuvasta voi käynnistää monta konttia yhtä aikaa — Cloud Runissa jokainen instanssi on yksi kontti.
Kontin levykuva ei ole bitti bitiltä kopioitu kiintolevy, kuten sana voisi antaa ymmärtää, vaan kokoelma tiedostoja, joista kontin tiedostojärjestelmä muodostetaan. Arkikielessä käytetään usein myös englannin sanaa image.
Tunnetuin konttien tekemiseen ja ajamiseen käytetty työkalu on
Docker.
Siinä levykuvan rakentaminen kuvataan Dockerfile-tiedostossa. Tällä
kurssilla Dockeria ei tarvita, koska Cloud Run rakentaa levykuvan
lähdekoodista itse.
Nimi tulee rahtikontista. Rahtikontin sisällöstä riippumatta sitä voi käsitellä samoilla nostureilla ja kuljettaa samoilla laivoilla ja junilla. Ohjelmistokontti on samalla tavalla vakiomuotoinen: sama kontti toimii missä tahansa konttien ajamiseen tarkoitetussa ympäristössä. Sana kontti on vakiintunut suomenkieliseen tietotekniikkaan, ja sitä käytetään myös tässä. Englannin container käännetään toisinaan myös säiliöksi, mutta tässä merkityksessä se on harvinainen ja voi sekoittua muihin säiliö-sanan merkityksiin.
Ominaisuudet
- Automaattinen skaalautuminen. Yksi instanssi käsittelee useita pyyntöjä samanaikaisesti (oletuksena enintään 80), ja kuormituksen kasvaessa käynnistetään lisää instansseja.
- Skaalautuminen nollaan. Kun pyyntöjä ei tule, yhtään instanssia ei ole käynnissä eikä niistä makseta. Seuraava pyyntö joutuu odottamaan instanssin käynnistymistä, mikä hidastaa ensimmäistä vastausta (kylmäkäynnistys).
- Tiedostojärjestelmä on muistissa. Tiedostoja voi kirjoittaa, mutta ne vievät instanssin muistia ja katoavat instanssin mukana. Niitä voi käyttää väliaikaistiedostoina, ei pysyvään tallennukseen.
- Mikä tahansa kieli. Koska ajettava yksikkö on kontti, sovelluksen voi kirjoittaa millä tahansa kielellä ja käyttää mitä tahansa kirjastoja.
- Alue valitaan palvelukohtaisesti. Suomessa sijaitseva alue on
europe-north1(Hamina).
Flask-sovellus Cloud Runiin
Julkaistavassa hakemistossa tarvitaan kolme tiedostoa. Sovellus on tavallinen
Flask-sovellus, joka lukee kuunneltavan portin PORT-ympäristömuuttujasta
ajettaessa omalla koneella:
# main.py
import os
from flask import Flask
app = Flask(__name__)
app.secret_key = os.environ["SECRET_KEY"]
@app.route("/")
def etusivu():
return "Hei Cloud Runista!"
if __name__ == "__main__":
# vain omalla koneella; Cloud Runissa sovelluksen käynnistää Gunicorn
app.run(debug=True, port=int(os.environ.get("PORT", 8080)))
Riippuvuudet luetellaan requirements.txt-tiedostossa. Tuotannossa
Flaskin omaa kehityspalvelinta ei käytetä, vaan pyynnöt käsittelee
Gunicorn-palvelin:
Flask
gunicorn
google-cloud-firestore
Procfile kertoo, miten kontti käynnistää sovelluksen.
Cloud Run välittää kuunneltavan portin PORT-ympäristömuuttujassa:
web: gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
Gunicorn ja muut WSGI-palvelimet
Flask-sovellus ei itse ota vastaan HTTP-yhteyksiä. Se on WSGI-sovellus: funktio, jolle annetaan pyynnön tiedot ja joka palauttaa vastauksen. Varsinaisen verkkoliikenteen hoitaa WSGI-palvelin, joka vastaanottaa HTTP-pyynnöt, muuntaa ne WSGI-muotoon, kutsuu sovellusta ja lähettää vastauksen takaisin selaimelle. Periaate on sama kuin CGI- ja WSGI-luennolla.
flask run ja app.run() käynnistävät Flaskin mukana tulevan
kehityspalvelimen. Se on tarkoitettu vain kehitykseen: sitä
ei ole suunniteltu
turvalliseksi, vakaaksi eikä tehokkaaksi. Tuotannossa käytetään erillistä
WSGI-palvelinta.
Gunicorn (Green Unicorn) on
Pythonilla kirjoitettu WSGI-palvelin. Se käynnistää
pääprosessin, joka ei
itse käsittele pyyntöjä vaan hallitsee joukkoa työprosesseja: se käynnistää ne,
jakaa pyynnöt niille ja käynnistää kaatuneen prosessin uudelleen. Yllä olevan
Procfile-rivin valitsimet tarkoittavat seuraavaa:
--bind :$PORTkuuntelee porttia, jonka Cloud Run antaaPORT-ympäristömuuttujassa.--workers 1 --threads 8käynnistää yhden työprosessin, jossa on kahdeksan säiettä. Cloud Runin instanssilla on yleensä vain yksi prosessori, ja kun instansseja tarvitaan lisää, Cloud Run käynnistää niitä itse.--timeout 0poistaa Gunicornin oman aikakatkaisun, koska Cloud Run valvoo pyyntöjen kestoa itse.main:appkertoo, että sovellus onmain.py-tiedostonapp-muuttujassa.
Gunicorn toimii vain Unix-tyyppisissä käyttöjärjestelmissä, kuten Linuxissa ja macOS:ssä. Windowsissa sovellus ajetaan kehityksen aikana Flaskin kehityspalvelimella, joka riittää siihen hyvin. Gunicornin voi halutessaan ajaa Windowsin Linux-alijärjestelmässä (WSL). Cloud Runissa kontti on Linux, joten Gunicorn toimii siellä aina.
Gunicorn ei ole ainoa vaihtoehto. Flaskin dokumentaatio esittelee useita WSGI-palvelimia:
| Palvelin | Ominaisuudet |
|---|---|
| Gunicorn | Yleisin valinta Linuxilla. Helppo asentaa ja määritellä. Ei toimi Windowsissa. |
| Waitress | Kokonaan Pythonilla toteutettu, toimii myös Windowsissa. Hyvä valinta, kun sama palvelin halutaan kaikkiin ympäristöihin. |
| mod_wsgi | Apache-palvelimen moduuli. Sopii, kun sovellus halutaan ajaa osana olemassa olevaa Apache-palvelinta. |
| uWSGI | Tehokas ja monipuolinen, mutta monimutkainen määritellä. Projekti on ylläpitotilassa: siihen tehdään enää korjauksia, ei uusia ominaisuuksia. Uusiin projekteihin kannattaa valita jokin muu. |
Asynkronisille Python-kehyksille on vastaava rajapinta ASGI ja sille omat palvelimensa, esimerkiksi Uvicorn ja Hypercorn. Tunnetuimpia ASGI-kehyksiä ovat FastAPI ja Quart. Quart on Flaskin asynkroninen vastine: se kuuluu samaan Pallets-projektiin kuin Flask, ja sen rajapinta on lähes sama, joten Flask-sovelluksen voi siirtää Quartiin melko pienin muutoksin. Tällä kurssilla käytetään Flaskia, joten ASGI-palvelimia ei tarvita.
Julkaisu tehdään Google
Cloud CLI:n gcloud-komennolla samassa hakemistossa:
gcloud run deploy tulospalvelu --source . \
--region europe-north1 \
--allow-unauthenticated \
--max-instances 2 \
--set-secrets SECRET_KEY=flask-avain:latest
Komento rakentaa kontin, julkaisee sen ja tulostaa osoitteen, josta sovellus
löytyy. --allow-unauthenticated tekee palvelusta julkisen,
--max-instances rajoittaa instanssien määrää, ja
--set-secrets välittää salaisen avaimen Secret Manageriin tallennetusta
salaisuudesta ympäristömuuttujaksi.
Avaimen voisi välittää myös tavallisena ympäristömuuttujana
(--set-env-vars), mutta silloin se näkyy kaikille, jotka pääsevät
katsomaan palvelun asetuksia. Secret Manager säilyttää sen erillään palvelun
asetuksista. Salaisuus luodaan kerran:
python3 -c "import secrets; print(secrets.token_hex(32), end='')" \
| gcloud secrets create flask-avain --data-file=-
Kustannukset
Oletuksena Cloud Run laskuttaa vain siltä ajalta, jonka instanssi käsittelee pyyntöjä: prosessoriajasta, muistista ja pyyntöjen määrästä. Kuukausittainen ilmaiskiintiö on laskutustilikohtainen ja riittää hyvin viikkotehtävien sovelluksiin.
- Cloud Runin käyttöönotto vaatii laskutustilin, vaikka käyttö pysyisi ilmaiskiintiön sisällä.
- Jokainen julkaisu tallentaa uuden kontin Artifact Registryyn. Tallennustila maksaa, kun se ylittää ilmaiskiintiön, joten vanhoja levykuvia kannattaa poistaa.
--max-instancesrajoittaa kustannuksia silloin, kun sovellus käyttäytyy odottamattomasti, esimerkiksi jää silmukkaan.--min-instancespoistaisi kylmäkäynnistykset, mutta pitäisi instanssin käynnissä jatkuvasti ja kuluttaisi ilmaiskiintiön nopeasti. Jätä se oletusarvoon 0.- Aseta Google Cloudin laskutukseen budjetti ja budjettihälytys, niin tiedät ajoissa, jos kulut alkavat kasvaa.
App Engine
App Engine oli Googlen ensimmäinen palvelimeton sovellusalusta, ja sillä tätä kurssia on aiemmin toteutettu. Palvelu on edelleen toiminnassa ja saa päivityksiä, mutta Google suosittelee uusille käyttäjille Cloud Runia. Vastaan tulee silti usein vanhoja ohjeita ja esimerkkejä, joten on hyvä tuntea erot.
App Enginessa on kaksi ympäristöä. Standardiympäristössä sovellus ajetaan Googlen valmiissa ajoympäristössä, ja sillä on päivittäinen ilmaiskiintiö. Joustava ympäristö ajaa sovelluksen kontissa virtuaalikoneella, ja siitä maksetaan aina.
| Ominaisuus | App Engine (standardi) | Cloud Run |
|---|---|---|
| Ajettava yksikkö | lähdekoodi valmiissa ajoympäristössä | kontti, joka voidaan rakentaa lähdekoodista automaattisesti |
| Asetukset | app.yaml |
komentorivin valitsimet, Procfile tai Dockerfile |
| Julkaisu | gcloud app deploy |
gcloud run deploy --source . |
| Skaalautuminen nollaan | kyllä | kyllä |
| Laskutusperuste | instanssitunnit | prosessori- ja muistisekunnit pyyntöjen käsittelyn ajalta |
| Tiedostoihin kirjoittaminen | vain /tmp-hakemistoon |
koko tiedostojärjestelmään, joka on muistissa |
| Alue | yksi alue projektia kohden, ei vaihdettavissa jälkikäteen | valittavissa palvelukohtaisesti |
| Googlen suositus uusille sovelluksille | ei | kyllä |
Vanhoissa App Engine -esimerkeissä käytetään usein App Enginen omia palveluja kuten NDB-kirjastoa ja Memcachea. Ne toimivat vain App Enginessa, eikä niitä käytetä tällä kurssilla. Firestorea käytetään samalla Googlen Python-kirjastolla riippumatta siitä, ajetaanko sovellus App Enginessa, Cloud Runissa vai omalla koneella.
Datastore ja Firestore
Datastore ja Firestore ovat Googlen hajautettuja NoSQL-tietokantoja. Ne eivät ole kaksi erillistä tuotetta: Firestore on Datastoren seuraava versio, ja vanhat Datastore-tietokannat on päivitetty automaattisesti Firestoreksi. Firestore-tietokanta luodaan jommassakummassa kahdesta tilasta:
- Native-tila on varsinainen Firestore: dokumentit ja kokoelmat, reaaliaikaiset päivitykset ja käyttö suoraan selaimesta. Google suosittelee sitä kaikille uusille sovelluksille.
- Datastore-tila toimii vanhan Datastoren rajapinnalla. Se on täysin tuettu ja tarkoitettu sovelluksille, jotka jo käyttävät Datastore-rajapintaa.
Tila valitaan tietokantaa luotaessa. Sen voi vaihtaa vain, jos tietokanta on tyhjä. Samassa projektissa voi olla useita tietokantoja eri tiloissa, mutta ilmaiskiintiö koskee niistä vain yhtä.
Valitse tietokantaa luodessasi Native-tila. Datastore-tila esitellään vain siksi, että se tulee vastaan vanhoissa ohjeissa ja App Engine -esimerkeissä. Viikkotehtävien ja pääteohjausten koodi ei toimi Datastore-tilassa, koska siinä Firestoren kirjasto ei saa yhteyttä tietokantaan.
Vertailu
| Ominaisuus | Firestore (Native-tila) | Datastore-tila |
|---|---|---|
| Tietomalli | kokoelmat ja dokumentit; dokumentilla voi olla alikokoelmia | entiteetit, lajit (kind) ja avaimet; hierarkia muodostetaan esivanhempipolkuna |
| Python-kirjasto | google-cloud-firestore |
google-cloud-datastore tai google-cloud-ndb |
| Käyttö selaimesta ja mobiilisovelluksesta | kyllä, Firebase-kirjastoilla; pääsy rajataan tietoturvasäännöillä | ei, vain palvelimelta |
| Reaaliaikaiset päivitykset | kyllä: sovellus voi kuunnella dokumentin tai kyselyn muutoksia | ei |
| Kyselyt | Python-kirjaston metodit | Python-kirjaston metodit ja SQL:n kaltainen GQL-kyselykieli |
Liitokset (JOIN) |
ei | ei |
| Merkkijonovertailu | kirjainkoon huomioiva | kirjainkoon huomioiva |
| Johdonmukaisuus | vahva | vahva (vanhassa Datastoressa kyselyt olivat vain lopulta johdonmukaisia) |
| Hierarkian muuttaminen | dokumentin polku on pysyvä; siirto tehdään kopioimalla ja poistamalla | esivanhempi on osa avainta eikä sitä voi muuttaa jälkikäteen |
| Dokumentin tai entiteetin enimmäiskoko | 1 MiB | 1 MiB |
| Hinnoittelu ja ilmaiskiintiö | samat molemmissa | samat molemmissa |
| Emulaattori omalla koneella | gcloud emulators firestore start |
gcloud emulators firestore start
--database-mode=datastore-mode |
| Käyttötarkoitus | uudet sovellukset | sovellukset, jotka jo käyttävät Datastore-rajapintaa |
Firestoresta on lisäksi olemassa kaksi versiota, Standard ja Enterprise. Tällä kurssilla käytetään Standard-versiota, ja tämän luennon tiedot koskevat sitä.
Firestoren käyttö Pythonista
Firestorea käytetään Googlen tekemällä Python-kirjastolla
google-cloud-firestore, joka asennetaan pipillä kuten muutkin
kirjastot. Kirjasto hoitaa yhteyden tietokantaan, tunnistautumisen ja tietojen
muuntamisen: ohjelmoija vain kutsuu sen metodeja. SQLiten kanssa vastaava rooli oli
Pythonin sqlite3-moduulilla.
Firestore koostuu kokoelmista, joissa on dokumentteja. Dokumentti on joukko kenttiä, ja Pythonissa se käsitellään sanakirjana. Kyselyt tehdään aina yhteen kokoelmaan kerrallaan:
from google.cloud import firestore
from google.cloud.firestore_v1.base_query import FieldFilter
# Tietokantayhteyttä edustava olio luodaan kerran. Cloud Runissa
# tunnistautuminen tapahtuu automaattisesti palvelun oman palvelutilin avulla.
db = firestore.Client()
# uusi dokumentti; Firestore keksii tunnisteen itse
_, viite = db.collection("kilpailut").add({
"nimi": "Jäärogaining",
"alkuaika": "2025-03-15 08:00:00",
})
# yksi dokumentti tunnisteen perusteella
kilpailu = db.collection("kilpailut").document(viite.id).get()
if kilpailu.exists:
print(kilpailu.to_dict()["nimi"])
# kysely: kaikki samannimiset kilpailut
kysely = db.collection("kilpailut").where(
filter=FieldFilter("nimi", "==", "Jäärogaining"))
for dokumentti in kysely.stream():
print(dokumentti.id, dokumentti.to_dict())
Tarkemmat ohjeet ja esimerkit ovat pääteohjauksessa
4 ja google-cloud-firestore-kirjaston
dokumentaatiossa.
Kehitys omalla koneella
Sovellusta ei tarvitse julkaista jokaisen muutoksen jälkeen. Flask-sovelluksen voi ajaa omalla koneella tavalliseen tapaan, ja tietokantana käytetään Google Cloud CLI:n mukana tulevaa emulaattoria. Emulaattori käynnistetään omaan pääteikkunaansa:
gcloud emulators firestore start --host-port=localhost:8081
Sovellukselle kerrotaan emulaattorin osoite ympäristömuuttujalla. Kun muuttuja on
asetettu, google-cloud-firestore-kirjasto ottaa yhteyden emulaattoriin
eikä pilvessä olevaan tietokantaan:
export FIRESTORE_EMULATOR_HOST=localhost:8081
export GOOGLE_CLOUD_PROJECT=demo-tulospalvelu
export SECRET_KEY=paikallinen-kehitysavain
python3 main.py
- Emulaattori käyttää oletuksena porttia 8080, jota myös sovellus käyttää. Siksi emulaattorille annetaan yllä oma portti.
- Emulaattori säilyttää tiedot vain muistissa. Kun se suljetaan, kaikki tallennettu katoaa.
Firestore-tietokannan suunnittelu
Relaatiotietokannassa suunnittelu alkaa tiedon rakenteesta, ja kyselyt kirjoitetaan sen jälkeen. Firestoressa järjestys on päinvastainen: ensin mietitään, mitä kyselyjä sovellus tekee, ja tietokanta rakennetaan niitä varten. Liitoksia ei ole, joten yhden sivun tarvitsemat tiedot kannattaa löytää mahdollisimman harvasta dokumentista.
Palvelimella vai selaimessa?
Native-tilassa Firestorea voi käyttää sekä palvelimelta että suoraan selaimesta javascriptillä. Moni asia on makuasia: saman toiminnon voi toteuttaa kummalla puolella tahansa. Viikkotehtävässä on kuitenkin määritelty, mitkä asiat on tehtävä palvelimella ja mitkä selaimessa.
Ratkaiseva ero on pääsynhallinta. Palvelimella ajettava koodi käyttää tietokantaa palvelun omilla oikeuksilla, ja sovellus itse päättää, mitä käyttäjä saa tehdä. Selaimesta tietokantaa käytetään suoraan, joten pääsy on rajattava Firestoren tietoturvasäännöillä. Ilman niitä kuka tahansa voi lukea ja muuttaa koko tietokantaa.
Dokumenttirakenne ja kustannukset
Firestoressa maksetaan jokaisesta luetusta, kirjoitetusta ja poistetusta dokumentista. Ilmaiskiintiö on päivittäinen:
| Toiminto | Ilmaiseksi |
|---|---|
| Dokumenttien lukuja | 50 000 päivässä |
| Dokumenttien kirjoituksia | 20 000 päivässä |
| Dokumenttien poistoja | 20 000 päivässä |
| Tallennettua tietoa | 1 GiB |
Dokumenttien määrä on siis suoraan kustannus. Jos yksi sivu näyttää tiedot kymmenestä dokumentista, sivun lataaminen maksaa kymmenen lukua; jos samat tiedot ovat yhdessä dokumentissa, se maksaa yhden. Ero voi helposti olla kymmenkertainen. Välimuisti vähentäisi lukuja, mutta siihen palataan vasta viimeisissä tehtävissä — yritä nyt löytää hyvä rakenne ilman sitä.
Kaikkea ei kuitenkaan voi koota yhteen dokumenttiin. Dokumentin enimmäiskoko on 1 MiB. Viikkotehtävän mallidata saattaisi vielä mahtua, mutta jos järjestelmä olisi käytössä vuosia, raja tulisi väistämättä vastaan. Älä tee tätä virhettä.
Esimerkkilaskelma: chat-sovellus
Tarkastellaan pientä IRC-tyyppistä chat-sovellusta, jossa on käyttäjiä, keskusteluryhmiä ja viestejä. Ryhmän sivu näyttää ryhmän uusimmat viestit, ja uusi viesti lähetetään lomakkeella. Viestit voi tallentaa Firestoreen kahdella tavalla:
- Malli A: jokainen viesti on oma dokumenttinsa. Ryhmä on
dokumentti kokoelmassa
ryhmat, ja sen viestit ovat alikokoelmassaryhmat/{ryhmä}/viestit. - Malli B: ryhmä on yksi dokumentti. Ryhmän kaikki viestit tallennetaan taulukkona ryhmän omaan dokumenttiin.
Molemmissa malleissa viestiin tallennetaan lähettäjän nimi. Jos viestissä olisi vain viittaus käyttäjädokumenttiin, jokaisen viestin näyttäminen vaatisi lisäksi käyttäjädokumentin lukemisen. Tiedon toistaminen tällä tavoin (denormalisointi) on NoSQL-tietokannoissa tavallista, vaikka relaatiotietokannassa sitä vältettäisiin.
Laskelman oletukset: viesti vie tallennettuna noin 300 tavua, ryhmän sivu näyttää 50 uusinta viestiä, ja käyttäjä avaa ryhmän sivun keskimäärin kymmenen kertaa päivässä.
| Suure | Pieni sovellus | Kymmenkertainen käyttö |
|---|---|---|
| Käyttäjiä / ryhmiä | 50 / 5 | 500 / 20 |
| Uusia viestejä päivässä | 200 | 2 000 |
| Ryhmäsivun latauksia päivässä | 500 | 5 000 |
| Malli A: lukuja päivässä | 500 × (1 + 50) = 25 500 — ilmaiskiintiön sisällä | 5 000 × (1 + 50) = 255 000 — kiintiö ylittyy |
| Malli B: lukuja päivässä | 500 | 5 000 |
| Malli B: ryhmädokumentti kasvaa | noin 12 kt päivässä | noin 30 kt päivässä |
| Malli B: dokumentti täynnä (1 MiB) | noin 87 päivän kuluttua | noin 35 päivän kuluttua |
Kirjoituksia syntyy molemmissa malleissa yksi jokaista viestiä kohden, joten niissä ei ole eroa, ja kirjoitusten ilmaiskiintiö (20 000 päivässä) riittää molemmissa tilanteissa. Myöskään tallennustila ei ole ongelma: vuoden viestit vievät pienessä sovelluksessa noin 22 Mt ja kymmenkertaisessa noin 220 Mt, kun ilmaiskiintiö on 1 GiB.
Mallin A kymmenkertaisessa käytössä luvuista noin 205 000 päivässä ylittää ilmaiskiintiön, eli kuukaudessa noin 6,2 miljoonaa lukua. Noin 0,06 dollarin hinnalla sataatuhatta lukua kohden se tekee noin 3,7 dollaria kuukaudessa. Hinnat vaihtelevat tietokannan sijainnin mukaan, joten luku on suuntaa-antava.
Mitä laskelmasta voi päätellä?
- Malli B näyttää halvalta, mutta ei toimi. Luettavia dokumentteja on vähän, mutta ryhmädokumentti täyttyy muutamassa kuukaudessa, minkä jälkeen uusia viestejä ei voi enää tallentaa. Lisäksi jokainen sivunlataus siirtää koko dokumentin, jonka koko kasvaa päivä päivältä, ja kaikki ryhmän viestit kirjoitetaan samaan dokumenttiin, mikä tuo esiin kirjoitustahdin rajat vilkkaissa ryhmissä. Suuri taulukko kannattaa myös jättää indeksoimatta, sillä dokumentin indeksimerkinnöillä on oma ylärajansa.
- Malli A toimii aina, mutta lukeminen maksaa. Kustannus riippuu suoraan siitä, montako viestiä sivulla näytetään. Jos sivu näyttäisi 50 viestin sijaan 20, kymmenkertaisen käytön luvut putoaisivat 105 000:een päivässä ja kuukausikustannus noin dollariin. Sivun rakenne vaikuttaa kustannuksiin yhtä paljon kuin tietokannan rakenne.
- Paras ratkaisu on kompromissi. Viestit voi koota esimerkiksi sadan viestin dokumentteihin: kun dokumentti täyttyy, aloitetaan uusi. Ryhmän sivu lukee silloin yhden tai kaksi dokumenttia, dokumentin koko pysyy rajattuna, ja kymmenkertaisessakin käytössä luvut mahtuvat ilmaiskiintiöön. Hinta on monimutkaisempi ohjelmakoodi.
Kirjoitustahti
Yhden dokumentin kirjoitukset käsitellään peräkkäin: dokumentti ja sen indeksit päivitetään kerralla, ja muutos vahvistetaan useaan palvelimeen ennen kuin kirjoitus on valmis. Siksi yhtä dokumenttia ei voi päivittää rajattoman nopeasti. Tarkka raja riippuu kuormasta — kirjoitustahdista, samanaikaisista pyynnöistä ja indeksien määrästä — ja sen ylittäminen näkyy hitautena ja virheinä.
Ajatellaan oikeaa rogaining-tapahtumaa. Joukkueet lähtevät yhteislähdössä, ja useita joukkueita saapuu letkassa ensimmäiselle rastille. Jos kaikki leimaukset olisivat yhdessä dokumentissa, kymmenet joukkueet kirjoittaisivat samaan dokumenttiin lähes samalla hetkellä. Vähintään kunkin joukkueen leimaukset on tallennettava omaan dokumenttiinsa.
Jos kokoelman dokumenteissa on indeksoitu kenttä, jonka arvo kasvaa dokumentista toiseen — kuten leimauksen aikaleima — kokoelmaan voi kirjoittaa enintään noin 500 kertaa sekunnissa. Viikkotehtävissä raja ei tule vastaan, mutta se kannattaa tietää. Jos kentän perusteella ei tehdä kyselyjä, sen voi jättää indeksoimatta, jolloin rajoitusta ei ole.
Tunnisteet
Käytä Firestoren itse luomia tunnisteita. Firestore hajauttaa ne tasaisesti, jolloin uudet dokumentit jakautuvat eri palvelimille. Peräkkäiset tunnisteet (1, 2, 3, …), joita pääteohjauksessa käytetään havainnollisuuden vuoksi, kasaavat kirjoitukset samaan kohtaan ja hidastavat tietokantaa suuressa kuormassa.
Järkevä kompromissi
Joukkueiden, sarjojen ja kilpailujen tietoja muutetaan harvoin, mutta niitä luetaan usein. Leimauksia kirjoitetaan kilpailun aikana jatkuvasti. Mieti siis:
- Mitä kyselyjä kukin sivu tekee, ja kuinka monta dokumenttia se joutuu lukemaan?
- Mitkä tiedot luetaan aina yhdessä ja voisivat siksi olla samassa dokumentissa?
- Mitkä tiedot muuttuvat usein ja kannattaa siksi erottaa omiin dokumentteihinsa?
- Millainen kokoelmien ja alikokoelmien hierarkia tukee näitä kyselyjä?
Viikkotehtävän tietokanta kannattaa jakaa useaan dokumenttiin samaan tapaan kuin pääteohjauksen reseptitietokanta. Ihan kaikesta ei kuitenkaan kannata tehdä omaa dokumenttiaan kustannusten takia. Etsi järkevä kompromissi.
Lisätietoa
- Cloud Run: Flask-sovelluksen julkaiseminen
- Cloud Runin hinnoittelu ja ilmaiskiintiö
- Docker: What is a container? — Dockerin oma johdanto kontteihin
- Johdanto Docker-konttiteknologiaan — Seinäjoen ammattikorkeakoulun suomenkielinen opas
- Flask: Deploying to Production — WSGI-palvelimet ja niiden käyttö Flaskin kanssa
- Gunicornin dokumentaatio
- App Enginen ja Cloud Runin vertailu
- Native-tilan ja Datastore-tilan valinta
- Firestoren ilmaiskiintiö ja rajoitukset
- Firestoren parhaat käytännöt
- Datastore-tilan testaaminen Firestore-emulaattorilla
Käyttäjien kommentit