Category: Technology
Auto-increment-kentät ovat perinteinen tapa saada tietokantaan juokseva laskuri. Yleensä niitä käytetään primary key -sarakkeen arvona. Samalla ne kuitenkin muodostavat tietokannan skaalautuvuuteen pullonkaulan, jota on todella vaikea korjata jälkeenpäin. Lisäksi ne monimutkaistavat sovellusta tarpeettomasti.
Niin millä tavalla monimutkaistavat? Yleisin ongelmatilanne on sellainen, että vaikkapa blogiartikkeliin liittyvät kuvatiedostot pitäisi saada tallennettua levylle. Niiden tiedostonimessä pitäisi käyttää artikkelin ID:tä, jotta tiedosto ei mene päällekkäin muiden kanssa. Mutta silloin blogiartikkeli täytyy ensin tallentaa tietokantaan ilman kuvia, jotta sen auto-increment-ID saadaan selville. Vasta sitten kuvat voidaan tallentaa levylle sekä päivittää niiden tiedostonimet artikkeliin tietokannassa.
Monimutkaisemmissa järjestelmissä näitä tilanteita tulee vastaan vähän väliä. Auto-incrementin käyttö "pakottaa" tekemään aina ensin INSERT-lauseen päätietokantaan, jotta uusi ID saadaan selville. Kun tämä INSERT ei voi vielä sisältää kaikkea tarvittavaa tietoa, joudutaan tekemään jälkeenpäin turha UPDATE, jolla päivitetään puuttuvat tiedot.
Skaalautuvuuten kannalta auto-increment muodostaa "single point of failure" -pisteen. Koska numeron pitää olla juokseva ja uniikki, se on pakko generoida aina samalla palvelimella. Tätä voi yrittää kiertää käyttämällä esimerkiksi segmentointia (toisella palvelimella auto-increment-arvot alkavat 1 miljardista) tai jakojäännöksiä, mutta tällaiset ratkaisut ovat aina hankalia ja vaativat virittelyä, jos palvelimia lisätään.
UUID tulee apuun
Olen itse tullut siihen tulokseen, että auto-increment-kentistä kannattaa luopua ja siirtyä kokonaan UUID:den käyttöön. UUID on 128-bittinen numero, joka on yleensä helpointa tallentaa 32-merkkisenä heksakoodina, kuten esimerkiksi "57a0809876974df79983f8a97b260607".
Viralliseen määritelmään kuuluu myös muutama tavuviiva, jolloin UUID esitetään muodossa "57a08098-7697-4df7-9983-f8a97b260607". Niitä on kuitenkin turha tallentaa tietokantaan. Viivat voi aina lisätä tilanteissa, joissa jokin protokolla niitä edellyttää. URLeissa viivaton muoto on omasta mielestäni siistimpi ja kompaktimpi. Se on myös ihan käytännössä helpompi copy-pasteta, koska viivattoman arvon saa valittua tuplaklikkaamalla. ;-)
Pythonissa UUID:t voi generoida uuid-moduulilla. Itse käytän tavallisesti uuid.uuid4()-funktiota, joka arpoo täysin satunnaisen luvun. Tällaisessa UUID:sä ei ole mukana esimerkiksi kellonaikaa tai koneen MAC-osoitetta tai muita identifioivia lähdearvoja. Kahden satunnaisen UUID:n päällekkäisyysriski vuositasolla on sama kuin putoavan meteoriitin alle jääminen, jos vuodessa generoidaan kymmenen tuhatta miljardia UUID:tä. Itselleni tämä varmuus riittää, kun en työskentele pankissa.
Tietokantojen yhteydessä UUID voidaan generoida etukäteen ennen datan tallentamista tietokantaan. Dataobjektia voidaan siis käsitellä muistissa niinkuin se olisi jo olemassa. Kuvatiedostot voidaan nimetä UUID:n mukaan valmiiksi oikein ja mahdolliset viittaukset muihin järjestelmiin (esimerkiksi NoSQL-kantoihin) voidaan valmistella hyvissä ajoin. Sitten valmis objekti INSERTataan kerralla päätietokantaan ja voidaan olla varmoja siitä, että se on heti käytettävissä kaikkine oheisriippuvuuksineen.
Skaalautuvuuden kannalta tällainen UUID-perusteinen dataobjekti voidaan tallentaa mihin tahansa tietokantaan, jos käytössä sattuu olemaan useita rinnakkaisia palvelimia. Tämä helpottaa esimerkiksi horisontaalista partitiointia ja master-master-replikointiin perustuvia järjestelmiä. Objekteja voidaan myös siirrellä tietokannasta toiseen ilman riskiä ID:n päällekkäisyydestä.
Auto-increment vs. UUID
Tietokannan suunnittelussa täytyy huomioida se, että UUID-primary-key-sarakkeet eivät ole enää numeerisessa järjestyksessä. Sen vuoksi tarvitaan aina erillinen "pub_date"-kenttä, jonka perusteella rivit voi järjestää. Päivämääräkentän käyttäminen sorttaamiseen on muutenkin siistimpää kuin auto-increment-kenttiin luottaminen, sillä primary keyn arvoa on jälkeenpäin vaikeaa tai mahdotonta muuttaa.
UUID-kenttien käyttö tuo tietokantaan myös uuden tietoturvakerroksen. Juoksevat auto-increment-arvot tekevät yleensä helpoksi arvata muita ID:itä tai käydä läpi esimerkiksi kaikki ID:t ykkösestä miljoonaan. UUID:itä taas on käytännössä mahdoton arvailla, eikä sillä ole mitään merkitystä, vaikka olisikin nähnyt yhden.
URLeissa UUID on tietysti vähän rumempi kuin perinteinen juokseva auto-increment-numero. Mutta käyttäjille näkyvät URLit pitäisi muutenkin suunnitella niin, että niissä näkyy joku merkitykselinen tunniste. Esimerkiksi omalla webbisaitillani URLeissa käytetään aina päivämääriä, otsikoita tai kategorioiden nimiä, joten taustalla käytetyt ID:t eivät vaikuta mitään.
UUIDField Djangossa
Lopuksi vielä esimerkki UUIDField-kentästä Djangolle. Tätä kenttää voi käyttää omien tietomallien primary key -sarakkeena. Alkuperäinen implementaatio on kopioitu jostain päin nettiä ja sitä on hiukan viritelty.
from django.db import models import uuid class UUIDField(models.CharField): """A UUID based primary key field.""" def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 64) kwargs['blank'] = True return super(UUIDField, self).__init__(*args, **kwargs) def pre_save(self, model_instance, add): if add: value = getattr(model_instance, self.attname) if hasattr(model_instance, self.attname) else None if not value: value = uuid.uuid4().hex setattr(model_instance, self.attname, value) return value else: return super(models.CharField, self).pre_save(model_instance, add)
Oma tietomalli määriteltäisiin sitten näin:
class Article(models.Model): uuid = UUIDField(primary_key=True, editable=False) title = models.CharField(max_length=254) # ...
Django huomaa automaattisesti, että mallille on määritelty primary_key, eikä se silloin lisää tauluun oma id-kenttäänsä. Periaatteessa kentän nimeksi voisi antaa myös "id", mutta itse olen käyttänyt nimeä "uuid" sen takia, että muistan ohjelmoidessa helpommin, minkä tyyppinen kenttä on.
Kokemusteni perusteella kaikki Django 1.2.x:n mukana tulevat sovellukset toimivat ongelmitta UUID-kenttien kanssa. Erityisesti Djangon sisäänrakennettu admin-käyttöliittymä toimii täydellisesti. Joissakin 3rd party -sovelluksissa voi teoriassa olla ongelmia, jos ne olettavat (tyhmästi), että primary key on integer-tyyppinen. Vielä tällaista ei ole kuitenkaan tullut vastaan.
Windows-käyttäjiä saattaa kiinnostaa, että TortoiseGit-projekti alkaa vaikuttaa aika toimivalta GUI-ratkaisulta. Se integroituu Windows Exploreriin samaan tapaan kuin sen Subversionia käyttävä vastine TortoiseSVN.
TortoiseGitin avulla Git-projekteihin saadaan mukaan sellaisetkin Windows-käyttäjät, jotka eivät tunne oloaan kotoisaksi komentorivin ja msysgitin parissa.
Peruskäyttö vaikuttaa hyvin samankaltaiselta kuin TortoiseSVN:nkin. Etärepository kloonataan omalle koneelle valitsemalla kontekstivalikosta "Git Clone". Tehdyt muutokset kommittoidaan valitsemalla samaisesta valikosta "Git Commit" ja klikkaamalla vielä commitin jälkeen Push, jolloin ne tallentuvat palvelimellekin. Etärepositoryn muutokset saa helposti päivitettyä omalle koneelle valitsemalla silloin tällöin valikosta "Pull". Oman työhakemiston branchiakin on helppo vaihtaa "Switch/Checkout"-valikosta, joka näyttää luettelon käytettävissä olevista haaroista.
Työskentelyn helpottamiseksi kannattaa luoda PuTTYlla ssh-avainpari ja lisätä julkinen avain Git-repository-palvelimelle ~/.ssh/authorized_keys-tiedostoon. Salaisen avaimen voi sitten määritellä TortoiseGitin asetuksiin, jolloin salasanaa ei tarvitse enää näpytellä joka kerta pushatessa tai pullatessa.
Osallistuin eilen eReading-seminaarin, jossa käsiteltiin sähköisen lukemisen nykytilaa ja tulevaisuuden suuntia Suomessa. Eräs mieleenpainavimpia lausahduksia oli erään kirjakustantajan edustajan lausunto, joka meni suunnilleen näin:
"Haluamme välttää musiikkiteollisuuden virheet. Siksi kaikki sähköiset kirjat pitää suojata DRM:llä."
Toisin sanoen kirjamiehet ovat onnistuneet ymmärtämään täysin päälaelleen sen, mikä musiikkibisneksessä mokattiin. Siellähän yritettiin monen vuoden ajan käyttää DRM:ää, ja juuri se sai ihmiset kääntymään laittomien lähteiden puoleen, koska käyttökokemuksesta tuli niin huono.
Kokeilin ostaa Akateemisen kirjakaupan uudesta latauspalvelusta sähköisen kirjan.
Kokeilu 1: Osto iPadilla. Tilaaminen ja maksaminen onnistui, mutta kun kirjaa oli tarkoitus alkaa lataamaan iPadiin, tulikin vain virheilmoitus, että tiedostomuoto on tuntematon. Ei minkäänlaisia varoituksia tai ohjeita, miten pitäisi toimia. Eikä kirjaa.
Kokeilu 2: Osto tietokoneella. Olin jo maksanut kirjan, joten sen latauslinkki oli valmiina henkilökohtaisessa kirjastossani. Tällä kertaa lataus pullautti minulle tietokoneeseeni tiedoston nimeltä URLLink.acsm. Se sisältää mystistä dataa, allekirjoittajana SecuryCast Oy. Ei kirjaa.
Kokeilu 3: Koska olen jonkin verran kokenut käyttäjä, tajusin, että tarkoitus on asentaa Adobe Digital Editions -sovellus, joka pystyy sitten avaamaan nuo .acsm-tiedostot ja lataamaan varsinaisen kirjan Akateemiselta. Ensin pitää tietenkin rekisteröidä oma Adobe ID ja kirjautua sillä sisään ja niin edelleen.
Kaiken tämän jälkeen kirja vihdoin aukesi tietokoneeseeni (iMac), mutta vain Adobe Digital Editionsin sisällä. Ei mitään mahdollisuutta siirtää kirjaa iPadiin tai Kindleen.
Tässä ollaan nyt täsmälleen samassa tilanteessa kuin musiikkibisnes oli jokunen vuosi takaperin, ja ollaan toistamassa täsmälleen samat virheet kuin silloinkin.
Päivitin tänään blogiani pyörittävän palvelimen Ubuntu 10.10 -versioon. Päivitys näytti sujuvan perinteiseen tapaan sulavasti ajamalla vain screenissä komento:
do-release-upgrade
Jos päivitystä ei ole tarjolla, pitää sallia normal-päivitykset pelkkien LTS-päivitysten sijaan tiedostossa /etc/update-manager/release-upgrades:
Prompt=normal
Muutamia jippoja tuli eteen yksittäisten palvelinsovellusten päivityksessä. Varnishin oletustiedostossa (/etc/default/varnish) on nykyään pakollinen kohta:
START=yes
Päivityksessä kannattaa oikeastaan hyväksyä kyseisen tiedoston (ja muidenkin vastaavien) uusi oletusversio, ja tehdä sitten tarvittavat omat muutokset siihen uudelleen.
CouchDB:n päivittymisessä pitää huomioida, että vanhat 0.10.0-version tietokannat jäävät /var/lib/couchdb/0.10.0 -hakemistoon. Ubuntu luo uudet tietokannat /var/lib/couchdb/1.0.1 -hakemistoon. Onneksi tietokannat ovat kuitenkin yhteensopivia ja ne on helppo siirtää sanomalla:
/etc/init.d/couchdb stop mv /var/lib/couchdb/0.10.0/*.couch /var/lib/couchdb/1.0.1 chown couchdb:couchdb /var/lib/couchdb/1.0.1/*.couch /etc/init.d/couchdb start
Nyt sitten tarkkaillaan tuleeko muita ylläreitä eteen. Redis ainakin päivittyy 2.0-versioon, mutta en ehtinyt vielä päivittää palvelinta, jossa se on käytössä.
Otin eräässä Django-projektissani vihdoin käyttöön Southin, joka hoitaa tietokannan schema-päivitykset automaattisesti. Käyttöönotossa oli hieman opiskeltavaa, mutta nyt en aio enää toteuttaa yhtäkään projektia ilman Southia.
Kehitysvaiheessa South hoitaa automaattisesti tarvittavien kenttien lisäämiset ja muutokset tietokantaan. Koodaajan tarvitsee vain muokata Djangon model-luokkiaan tavalliseen tapaan ja ajaa sitten komennot:
./manage.py schemamigration --auto <appname> ./manage.py migrate <appname>
Ei siis enää manuaalisia ALTER TABLE -komentoja ja oikeiden datatyyppien arvailemisia! Tämä helpottaa jatkuvaa kehitystä todella paljon.
South tallentaa tietokantamigraatiot myös migrations/*.py-tiedostoihin, jotka tulevat luonnostaan osaksi projektia versiohallinnassa. Nämä mahdollistavat migraatioiden ajamisen kaikille kehitys-, testi- ja tuotantopalvelimille, joissa sovellusta ajetaan. South pysyy ajan tasalla tämänhetkisestä tietokannan versiosta omalla south_migrationhistory-taulullaan, josta näkyy, mikä migraatio on ajettu tietokantaan viimeksi.
Southin käyttöönotto olemassaolevaan Django-projektiin on helppoa. Ensin se vain asennetaan normaalina Python-pakettina:
sudo pip install south
Sitten se otetaan käyttöön halutu(i)ssa Django-sovelluksissa ajamalla komento:
./manage.py convert_to_south <appname>
Sudenkuoppana South hämääntyy joskus tilanteista, joissa esimerkiksi lisätään "field_id"-niminen kenttä ja poistetaan samalla "field"-kenttä, joka oli oikeasti ForeignKey ja siksi myös nimellä "field_id" tietokannassa. Vastaavia yhdistelmiä tulee aina toisinaan vastaan.
Mikäli migraatiot saa kehitysvaiheessa aivan sekaisin, voi aina tyhjentää south_migrationhistory-taulun ja poistaa migrations-hakemiston, korjata asiat tietokantaan käsin sekä ajaa ./manage.py convert_to_south -komennon uudelleen. Tuotantovaiheessa on tietysti oltava vähän tarkempana.
Mac OS X:n sisäänrakennettu Mail-sähköpostiohjelma on aika toimiva, mutta se tekee sähköpostien signatureista melkoista sotkua. Sotku syntyy, kun signatureja muokkaa Mailin asetuksissa WYSIWYG-editorilla. Vaikka koko signaturen muuttaa saman tyyliseksi, joukkoon jää silti sekalaisia div- ja span-elementtejä sekä niiden CSS-tyylityksiä.
Jos haluaa kontrolloida täsmälleen, miltä sähköpostien signaturet näyttävät, kannattaa unohtaa WYSIWYG-editointi ja luoda signaturensa erilliseen HTML-tiedostoon vaikkapa vim-editoria käyttäen. Sen jälkeen HTML-tiedosto avataan Safarilla ja tallennetaan uuteen tiedostoon .webarchive-muotoon, joka on Mailin ymmärtämä formaatti.
Lopuksi signature pitää vielä sijoittaa oikeaan paikkaan, joka sijaitsee ~/Library/Mail/Signatures -hakemistossa. Siellä on yksi .webarchive-tiedosto kutakin signaturea varten. Hakemisto näyttää luultavasti kutakuinkin tältä:
iMac:~ kennu$ ll ~/Library/Mail/Signatures/ total 24 -rw-r--r--@ 1 kennu staff 526 21 Syy 17:26 1DA1A660-764A-4C3C-8D11-AD241161C378.webarchive -rw-r--r--@ 1 kennu staff 677 21 Syy 17:21 9723BECD-1512-4E2A-B497-BD10C8A3DEBC.webarchive -rw-r--r-- 1 kennu staff 1362 19 Syy 13:14 SignaturesByAccount.plist
Toimi siis näin:
- Sulje Mail-sovellus
- Tarkista SignaturesByAccont.plist-tiedostosta, mikä signature vastaa mitäkin tiedostoa.
- Kopioi omat .webarchive-tiedostot aiempien päälle.
Webissä on keskusteltu viime aikoina paljon Diggin uuden version ongelmista, joista yksi on alustan epästabiilius kovan kuormituksen alla. Ongelmista on syytetty siirtymistä Cassandraan, joka on alkujaan Facebookin kehittämä NoSQL-tietokanta ja nykyään Apache-projekti.
Jos asiaa kuitenkin tutkii hieman sensaatio-otsikoita syvemmälle, käy nopesti ilmi, että tietokanta ei ole Diggin ainoa ongelmakohta. Taustalla on ilmeisimmin kuormitusongelmia arkkitehtuurin monissa eri osissa. Tämän lisäksi Cassandra on muutenkin käytössä esimerkiksi Redditissä, joka ei ole kärsinyt vastaavista ongelmista.
Diggnationin uusimmassa jaksossa CEO Kevin Rose itse asiassa kertoo, että Diggin aiempi, MySQL:n varaan rakennettu versio kärsi jo niin suurista suorituskykyongelmista, että siirtyminen Cassandraan oli oikeastaan välttämätöntä. Vanhasta versiosta jouduttiin poistamaan ominaisuuksia käytöstä, koska MySQL-pohjainen arkkitehtuuri ei enää pystynyt pyörittämään niitä.
Olen itse kokeillut Cassandraa tutustumismielessä muutamissa pikkuprojekteissa. Oma kokemukseni on, että sovelluksia on suhteellisen helppo kehittää Thrift-API:lla, mutta tietokanta-alustan ylläpito ja deploymentit voivat olla tuskallisia. Jos Cassandraa verrataan esimerkiksi CouchDB:hen, niin jälkimmäinen on paljon helpommin asennettavissa (apt-get install couchdb) ja hallittavissa sisäänrakennetuilla ylläpitotyökaluilla. Tämän vaikutusta sovelluskehityksen ketteryyteen ei kannata aliarvioida .
Oma "tuomioni" Cassandralle siis on, että sen kehitystä kannattaa seurata, mutta hyvin harva projekti oikeasti hyötyy sen tuomista eduista suhteessa käytön hankaluuteen nähden. Ehkä mielenkiintoisin vaihtoehto Cassandralle tällä hetkellä on Cloudantin avoimena lähdekoodina julkaisema BigCouch, joka tuo partitioidun klusteroinnin CouchDB:hen. Mikäli nämä ominaisuudet saadaan integroitua CouchDB:n coreen, siinä alkaa olla aineksia todella universaaliksi tietokantaratkaisuksi aina sulautetuista järjestelmistä satojen miljoonien käyttäjien web-palveluihin asti.