Välimuisti, Cloud functions, PostgreSQL, Avoin data - Pääteohjaus 6
Flask-Caching
Onko sovelluksesi hidas? Kestääkö jokin laskenta kauan? Tehdäänkö samoja hitaita asioita usein uudelleen? Käytä välimuistia ja sovelluksesi nopeutuu. Käytätkö kallista pilvipalvelua, jossa prosessointiaika tai tietokantaoperaatiot maksavat? Käytä välimuistiä ja säästät kustannuksissa.
Flask-Caching-kirjastolla saa flask-sovellukseen helposti välimuistin.
- Asenna Flask-Caching
pip install Flask-Caching
- Kirjoita testaamista varten jokin todella hidas funktio. Esim.
@app.route('/prime15000') def prime15000(): return prime(15000) @app.route('/prime/<int:n>') def prime(n): if n <= 1: return "[]" # hidas alkulukulaskenta-algoritmi # https://medium.com/@GalarnykMichael/prime-numbers-using-python-824ff4b3ea19 primes = [] for possiblePrime in range(2, n): isPrime = True for num in range(2, possiblePrime): if possiblePrime % num == 0: isPrime = False if isPrime: primes.append(possiblePrime) return str(primes)
- Otetaan käyttöön välimuisti
from flask_caching import Cache config = { "DEBUG": True, "CACHE_TYPE": "SimpleCache", # välimuistin tyyppi "CACHE_DEFAULT_TIMEOUT": 60 #oletusaika välimuistille } app = Flask(__name__) app.config.from_mapping(config) cache = Cache(app)
- Lisää edellä luomaasi prime15000-funktioon cache-dekoraattori seuraavalla tavalla:
Kokeile nyt latautuuko sivu nopeammin. Ensimmäisellä kerralla lataaminen kestää vielä kauan, mutta toisella kerralla sivun pitäisi latautua samantien. cache-dekoraattori pitää olla route-dekoraattorin jälkeen.@app.route('/prime15000') @cache.cached(timeout=50) def prime15000(): return prime(15000)
- Voit cachettaa muitakin kuin flask-funktioita. Silloin täytyy
antaa dekoraattorille key_prefix-parametri, jota käytetään
välimuistin avaimena. Oletusavaimena toimii request.path eli välimuisti
toimii sivukohtaisesti.
@cache.cached(timeout=50, key_prefix='oma_tunniste')
- Haluttaessa huomioida myös funktion eri parametrit välimuistissa käytetään
memoize-versiota:
Kokeile nyt erilaisilla luvuilla kutsua prime-funktiota. esim /prime/11000 ja /prime/15000.@app.route('/prime/<int:n>') @cache.memoize(timeout=50) def prime(n): ...
- SimpleCache on yksinkertaisin versio välimuistista. Käytännössä se on vain palvelimen muistissa säilyvä dict. Esim. AppEnginessa tämä olisi instanssikohtainen välimuisti. Instanssin muistin määrä voi rajoittaa välimuistin kokoa. Käytettävissä on useita erilaisia välimuistityyppejä. Tiedostojärjestelmäversiota ei voi käyttää AppEnginella. Myöskään memcached-versio ei toimi AppEnginessa. Google tarjoaa Memorystore for Redis -välimuistin, mutta sen käyttäminen on maksullista. Kannattaa ensin kokeilla SimpleCachea, ja jos sillä ei pärjää, ostaa käyttöön RedisCachen. Googlen Redis on käytännöllinen vasta, jos sovelluksella on jatkuvasti (24/7) paljon käyttöä, koska Redis-instanssi on käynnissä koko ajan ja siitä joutuu maksamaan 24/7.
- Välimuistin käytössä täytyy miettiä tarkkaan mitä tietoja voi säilyttää välimuistissa ja missä tarvitaan aina tuoreita tietoja. Esim. reseptitietokantatyyppisessä sovelluksessa voisi välimuistia käyttää paljon ja pitkiä aikoja, koska reseptit muuttuvat hyvin harvoin eikä uusiakaan lisätä usein. Viikkotehtävissä käytetyssä tulospalvelutietokannassa tietojen täytyy päivittyä tiuhempaan, mutta esim. 30 sekunnin välimuistiviive tuloslistauksessa ei olisi vielä mikään ongelma.
Firestore clients: To cache, or not to cache? (or both?)
Cloud Functions
Google tarjoamat pilvifunktiot (Cloud functions> ovat yksi mahdollisuus App enginen lisäksi rakentaa omia sovelluksia tai erityisesti sovelluksen osia. Jos on tarve yksittäiselle toiminnolle, voi sen hyvin toteuttaa cloud functiona. App Engine soveltuu paremmin kokonaisille Flask-sovelluksille ja sovelluksille joiden pitää vastata pyyntöihin nopeammin. Edellä toteutettu alkulkulaskenta olisi sopiva pilvifunktio. Pilvifunktioina voi toteuttaa myös taustalla toimivia funktioita. Hieman pilvifunktioita vastaava mahdollisuus on Cloud Run, jolla voi suorittaa pitempikestoisia sovelluksia kuin pilvifunktioita.
Pilvifunktiot ovat tilattomia. Periaatteessa pilvifunktion ympäristö käynnistetään tyhjästä (cold start) jokaisella funktion kutsukerralla. Tämä on myös hidasta eli funktio vastaa ensimmäisellä kutsukerralla hitaammin, kuin heti seuraavilla. Käytännössä sama toimintaympäristö (instanssi) on elossa jonkun aikaa funktion kutsumisen jälkeen. Samaa ympäristöä voidaan käyttää seuraavalla kutsulla tai sitten luodaan uusi. Saman instanssin vastatessa uudelleen tulee vastaus huomattavasti nopeammin. Kaikki mitä voi myöhemmin uudelleen käyttää kannattaa pitää instanssin muistissa tallessa globaaleina muuttujina. Vrt. App Enginen instanssit. Lue Tips & Tricks.
- Lue Python Quickstart ja Your First Function: Python
- Siirrä edellä mainittujen dokumenttien ohjeiden avulla aiemmin kirjoitettu alkulukusovellus pilvifunktioksi
- Valitse käyttöön vain 128 megatavua muistia
- Katso mallia valmiista esimerkkifunktiosta. Funktiosi saa yhden parametrin (request), joka on Flaskin request-objekti
- Muista lisätä requirements.txt-tiedostoon käyttämäsi kirjastot
- Muista valita kirjoittamasi funktio sovelluksen lähtöpisteeksi (entry point)
- Sovelluksen on oltava main.py-tiedostossa
- Muista, että oletuksena Cloud Functions ei ole mikään laskentahirmu. Älä kokeile 10000 suurempaa lukua.
- Välimuisti täytyy tehdä cachetools-kirjaston
avulla, koska Flask ja Flask-Caching eivät toimi aivan tavalliseen tapaan pilvifunktiossa.
Cachetools toimii hyvin samalla tavalla kuin Flask-Caching.
- Käytä TTLCache
-
from cachetools.keys import hashkey from cachetools import cached, TTLCache cache = TTLCache(maxsize=32) @cached(cache, key=hashkey) def oma_funktio(n): ...
Kokeile mallia
from cachetools import cached, TTLCache
from cachetools.keys import hashkey
def main(request):
n = request.args.get("n", 1)
if n:
return prime(n)
else:
return prime(2)
cache = TTLCache(maxsize=100, ttl=6000)
@cached(cache, key=hashkey)
def prime(n):
if int(n) <= 1:
return "[]"
# hidas alkulukulaskenta-algoritmi
# https://medium.com/@GalarnykMichael/prime-numbers-using-python-824ff4b3ea19
primes = []
for possiblePrime in range(2, int(n)):
isPrime = True
for num in range(2, possiblePrime):
if possiblePrime % num == 0:
isPrime = False
if isPrime:
primes.append(possiblePrime)
return str(primes)
Cloud Run VS Cloud Functions: What’s the lowest cost?
Cloud functions vs App engine vs Cloud Run vs GKE
Cloud Run and Cloud Function: What do I use? And Why?
Cloud SQL
Ei pysty käyttämään appengine-sovelluksilla, jos käyttää python 2.7 -tulkkia. Voisi periaatteessa käyttää myös users.jyu.fi:ssä olevalla sovelluksella, mutta it-palvelut on estänyt tämän.
- Otetaan käyttöön PostgreSQL-tietokanta Googlen pilvipalvelussa.
- Valitse sama projekti kuin loit edellisellä viikolla
- Luo uusi Cloud SQL instanssi
- Valitse tietokantamoottoriksi PostgreSQL
- Valitse seuraavat asetukset. Huom! Älä missään tapauksessa paina Create-painiketta ennen kuin olet käynyt läpi myös kohdan Show configuration options-lisäasetukset
- Keksi sopiva Instance Id esim. ties4080. Tätä ei voi myöhemmin vaihtaa.
- Keksi tietokannan ylläpitäjän salasana
- Valitse sijainniksi europe-west1 tai sama paikka minne olet sijoittanut appengine-sovelluksesi. Valitse Single zone. Useamman zonen versiota tarvitset vain erityisen tärkeille palveluille.
- Valitse Show configuration options
- Valitse Configure machine type and storage.
- Google tarjoaa oletuksena koneen tyypiksi 2 vcpu + 3.75 GB muistia. Tämä on aivan liian kallis konfiguraatio pelkkään testikäyttöön. Valitse Shared core ja sen jälkeen myös muistin määrä minimiin eli 0.6 GB. Tämä riittää pienille tietokannoille joille ei ole valtavasti yhtäaikaisia käyttäjiä.
- Halutessasi voit valita automaattiset varmuuskopiot. Harjoitellessa tätä ei tarvita, mutta tärkeämmissä tietokannoissa varmuuskopiointi on ehdottomasti valittava.
- Valitse vielä tallennustyypiksi HDD. SSD olisi ilman muuta parempi, mutta se on myös kalliimpi. Tallennustilaksi riittää minimi eli 10 GB. Ota ruksi pois kohdasta Enable automatic storage increases
- Valitse nyt Create instance
Tarkempi ohje: Quickstart for Cloud SQL for PostgreSQL
- Odota instanssin valmistumista. Valitse sen jälkeen valmistunut instanssi.
- Kopioi talteen instanssisi ip-osoite ja yhteysnimi
- Kokeile ottaa yhteys tietokantaan Cloud Shellin avulla. Valitse linkki Connect using Cloud Shell
- Saat Cloud Shelliin valmiin komennon jolla voit avata yhteyden tietokantaasi Tarvitset aiemmin antamaasi ylläpitäjän salasanaa
- Nyt sinulla on käytössä psql-komentotyökalu, joka toimii samaan tapaan kuin sqlite3-tietokannan vastaava työkalu. Jos jonkun merkin syöttäminen suoraan näppäimistöltä ei onnistu niin kokeile vasemman ylänurkan näppäimistökuvakkeesta aukeavaa valikkoa ja sieltä send key combination-valintaa.
- Oletuksena sinulla ei ole palvelimessa vielä mitään tietokantaa. Luo nyt uusi tietokanta seuraavalla komennolla:
CREATE DATABASE ohjaus6 ENCODING='UTF8' LC_COLLATE='fi_FI.utf8' LC_CTYPE='fi_FI.utf8' TEMPLATE template0;
psql-työkalusta poistutaan komennolla \quit - psql-työkalua on helpompi käyttää omalta koneelta. Sama työkalu löytyy myös halava/jalava-koneista, mutta niistä on estetty yhteydet yliopiston verkon ulkopuolelle. Omaan linux-koneeseen saman työkalun saa, jos asentaa omaan koneeseen postgresql-tietokannan. Windows-koneeseen on psql-työkalu saatavilla suoraan zip-paketissa. Pura paketti esim. C:\mytemp-kansioon.
- Sallitaan yhteydet postgresql-tietokantaan omalta tietokoneelta. Siirry Connections-välilehdelle. Valitse Add network ja lisää oman koneesi ip-osoite
Jos tietokoneesi on läppäri ja siirtelet sitä verkosta toiseen niin myös ip-osoite vaihtuu ja joudut lisäämään jokaisen osoitteen erikseen tai lisäämään sallituksi isomman verkkoalueen.
- Ota nyt yhteys tietokantaasi. Käynnistä command prompt. Käynnistä edellä lataamasi psql-työkalu seuraavalla tavalla:
psql -U postgres -h oman.postgresql-palvelimen.ip.osoite -d ohjaus6
Tarvittava ip-osoite näkyy tietokantapalvelininstanssisi overview-sivulla
- Tallenna itsellesi resepti.sql-tiedosto. Lataa tämä tiedosto
psql-työkalussa komennolla:
\i resepti.sql
Nyt sinulla pitäisi olla reseptitietokanta luotuna. Käytetty sql-koodi on lähes sama kuin sqlite-tietokannan kanssa. Vain autoincrement-tyyppiset kentät on täytynyt muuttaa serial-tyyppisiksi ja triggerit on voitu jättää pois. - Jos haluat käyttää appengine-sovelluksessa postgresql-tietokantaasi, joudut käyttämään samaa tietokantaa myös omalla koneella testatessa. PostgreSQL-tietokantaa varten ei ole lokaalia emulaattoria.
- Silloin kun et käytä tietokantaasi, niin sammuta se. Cloud SQL -tietokanta on normaalisti jatkuvasti päällä ja kuluttaa koko ajan krediittejä. Jos et oikeasti käytä tietokantaa niin poista (delete) se kokonaan.
-
Tietokantayhteys
Voit käsitellä tietokantaasi aivan samalla tavalla kuin sqlite3-tietokantaa. Joudut vain avaamaan tietokantayhteyden Googlen ohjeiden mukaan. Huom. ei toimi python 2.7 -sovelluksilla.- Asenna psycopg-kirjasto
pip install psycopg2-binary
- Tietokantayhteys kannattaa muodostaa psycopg2-kirjastolla.
import psycopg2 #yhteystiedot kannattaa lukea ympäristömuuttujista tai tiedostosta eikä tallentaa suoraan ohjelmakoodiin conn = psycopg2.connect("dbname=tietokannannimi user=käyttäjä host=ip-osoite password=salasana") cur = conn.cursor() cur.execute("SELECT * FROM Weather") print(cur.fetchone()) # muista myös tarvittaessa commit() tai rollback() cur.close() conn.close()
- Haluatko kyselyn tulokset dict-muodossa?
import psycopg2 import psycopg2.extras conn = ... cursor = conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor)
- Connections pooling
- Passing parameters to SQL queries
- Psycop2 FAQ
- Advanced topics
- Asenna psycopg-kirjasto
Avoin data
Käytetään muiden tekemiä XML-dokumentteja ja WWW-sivuja tiedonlähteenä.
DOM-rajapinta on tuttu jo TIEA2120 WEb-käyttöliittymien ohjelmointi -kurssilta. Jos olet unohtanut niin kertaa Document Object Model (DOM) ja Javascript ja DOM. Tässä pääteohjauksessa opetellaan DOM-rajapinnan käyttäminen Python-ohjelmointikielellä.
Ulkoisen XML-datan sisällyttäminen omalle sivulle
- Aloita uusi python-ohjelma. Pyritään hakemaan ja parsimaan https://appro.mit.jyu.fi/ties4080/ohjaus/ohjaus6/data.xml- tiedosto ja sisällyttämään siitä tärkeimmät osat omalle sivulle. Käsiteltävän tiedoston rakenne on sama kuin aiemmin käytetyissä json-versioissa. Katso malliohjelman tuloste (Mallin lähdekoodi).
- Haetaan XML-dokumentti käyttäen Pythonin xml.dom.minidom ja urllib-kirjastoja.
- Tee ensin pelkästään komentoriviltä toimiva versio.
from xml.dom.minidom import parse import urllib doc = parse( urllib.urlopen("tähän sivun osoite")) print doc.toxml('UTF-8')
Kokeile tulostuuko komentoriviltä ajettaessa sivun sisältö - Kaivellaan dokumentista yksittäisiä osia. Haetaan ensimmäiseksi yleistietoja:
# Haetaan kilpailun tiedot eli ensimmäiset esiintymät nimi, kesto, alkuaika ja loppuaika elementeistä nimi = doc.getElementsByTagName("nimi")[0] kesto = doc.getElementsByTagName("kesto")[0] alkuaika = doc.getElementsByTagName("alkuaika")[0] loppuaika = doc.getElementsByTagName("loppuaika")[0]
- Tulosta kaikkien hakemiesi elementtien tekstisisältö. Muista kokeilla komentorivillä, että ohjelma toimii.
textContent-ominaisuus ei toimi minidomissa joten joudut käyttämään:
nimi.firstChild.nodeValue
- Tee samaan tapaan listaus joukkueiden nimistä. Hae kaikki joukkueet-elementit, käy ne silmukassa läpi, hae jokaisen joukkueet-elementin alta ensimmäinen nimi-elementti ja tulosta sen sisältö.
- Mitäs jos haluat tulostaa joukkueiden nimet sarjoittain? Kokeile tehdä tämä ja tulosta aina ensin sarjan nimi ja sen alle sisennettynä kaikkien sarjaan kuuluvien joukkueiden nimet
- Tee nyt sovelluksestasi web-sovellus (CGI-ohjelma, Flask-sovellus tai appengine-sovellus) ja luo XML-tiedoston sisällöstä järkevä html-dokumentti. Vrt. mallisovellus ((Mallin lähdekoodi)).
XML ja nimiavaruudet
XML-dokumenttiin voi olla sisällytetty elementtejä useasta eri nimiavaruudesta (namespace). Tämä täytyy osata huomioida dokumentin sisältöä käsiteltäessä. Käytetystä kirjastosta riippuen voi joutua käyttämään getElementsByTagNameNS-funktiota tavallisen getElementsByTagName-funktion sijaan. Esim.
mi = doc.getElementsByTagNameNS("http://www.w3.org/1998/Math/MathML", "mi")
Käyttämällä nimiavaruuden osaavaa versiota varmistaa, että ei ainakaan saa vahingossa tulokseen vääriä elementtejä. Eri nimiavaruuksissa voi olla samannimisiä elementtejä.
Joskus XML-dokumenteissa on myös CDATA-osioita, joiden parsiminen täytyy joillakin kirjastoilla tehdä poikkeavasti:
if node.firstChild.nodeName == "#cdata-section": print node.firstChild.data else: print node.firstChild.nodeValue
- Katso esimerkki, joka parsii ylen rss-feediä. Esimerkin Lähdekoodi.
- Tee sovellus, joka tulostaa https://www.w3.org/TR/XHTMLplusMathMLplusSVG/sample.xhtml-dokumentista kaikkien h2-tason otsikoiden tekstit
- Tulosta myös kaikkien mathml-osioiden mi-elementtien sisältö
- Tulosta myös kaikkien svg-polygonien pisteet (points)
ElementTree
Voit halutessasi opetella käyttämään myös ElementTree XML API-rajapintaa. ElementTree-rajapinnan käyttäminen on hieman yksinkertaisempaa kuin DOM-rajapinnan. ElementTree-rajapintaa tarvitset, jos joudut parsimaan rakenteeltaan rikkinäisiä html-dokumentteja (tagsoup...). Tällöin käytetään esimerkiksi BeautifulSoup-kirjastoa, joka toteuttaa ElementTree-rajapinnan.
Käyttäjien kommentit