WordPressin WP-Cron on yksi niistä järjestelmän osista, jotka näyttävät harmittomilta, mutta kätkevät sisäänsä eleganttia logiikkaa ja muutaman klassisen ohjelmistokehityksen sudenkuopan. Näistä ehkä kiinnostavin – ja ajoittain tuskallisin – liittyy cron locking -mekanismiin ja kilpajuoksuongelmiin.
WP-Cron ei ole oikea käyttöjärjestelmätason cron. Se on simulaatio. Ja simulaatiot, kuten tiedämme, ovat usein täynnä hienovaraisia kompromisseja.
WP-Cronin perusrealiteetti
WP-Cron toimii pyynnön yhteydessä. Kun joku lataa sivun, WordPress tarkistaa:
“Onko ajastettuja tehtäviä, jotka pitäisi suorittaa?”
Jos on, WordPress käynnistää cron-prosessin.
Tämä tarkoittaa yhtä tärkeää asiaa:
Cron ei ole jatkuva taustaprosessi. Se on opportunistinen.
Ei käyttäjiä → ei cron-ajamista.
Paljon käyttäjiä → paljon cron-tarkistuksia.
Ja tästä alkaa mielenkiintoinen dynamiikka.
Cron locking: miksi sitä edes tarvitaan?
Ajattele hetki tilannetta ilman lockingia.
Sivustolla on paljon liikennettä. Useita pyyntöjä osuu palvelimelle lähes samanaikaisesti. Jokainen pyyntö tarkistaa cronin.
Kaikki huomaavat:
“Ahaa, tehtävä pitäisi ajaa.”
Ilman lockingia:
-
sama cron-tehtävä voisi käynnistyä monta kertaa
-
raskas logiikka monistuisi
-
tietokanta kärsisi
-
CPU itkisi hiljaa nurkassa
Cron locking on WordPressin tapa sanoa:
“Vain yksi cron-prosessi kerrallaan.”
Locking on koordinaatiomekanismi
Cron locking ei ole turvallisuusominaisuus. Se on synkronointimekanismi.
Se estää:
-
päällekkäiset ajot
-
redundantit operaatiot
-
resurssien tuhlauksen
Kyse on klassisesta concurrency-ongelmasta.
Miten WordPress toteuttaa cron lockingin?
WP-Cron käyttää transienttia nimeltä:
doing_cron
Kun cron käynnistyy:
-
WordPress asettaa lockin
-
lockilla on aikaraja
-
muut prosessit näkevät lockin
-
rinnakkaiset ajot estetään
Eleganttia. Kevyttä. Käytännöllistä.
Ja silti… ei täydellistä.
Kilpajuoksuongelmat: missä realismi iskee?
Kilpajuoksu (race condition) syntyy, kun:
-
useita prosesseja toimii samanaikaisesti
-
järjestelmän tila muuttuu nopeasti
-
lopputulos riippuu ajoituksesta
WP-Cron + korkea liikenne = täydellinen laboratorio kilpajuoksuille.
Klassinen skenaario
Kaksi pyyntöä saapuu lähes samanaikaisesti.
-
Molemmat tarkistavat cron-tilan
-
Molemmat näkevät “ei lockia”
-
Molemmat yrittävät asettaa lockin
Tässä kohtaa kaikki riippuu nanosekunneista.
Transienttien käsittely ei ole atominen operaatio koko järjestelmän tasolla. Locking ei ole täydellinen mutex.
Ja näin syntyy:
-
kaksoisajot
-
päällekkäiset cronit
-
satunnaiset ilmiöt
Ei usein. Mutta riittävän usein tuotannossa.
Locking ei ole absoluuttinen takuu
Tämä on tärkeä oivallus.
Cron locking on best-effort -mekanismi.
Se vähentää kilpajuoksuja.
Se ei poista niitä täysin.
Web-ympäristössä täydellinen synkronointi on vaikeaa, koska:
-
useita PHP-prosesseja
-
useita palvelimia (load balancing)
-
välimuistit
-
transient storage -strategiat
WordPress tekee pragmaattisen kompromissin.
Transientit ja distributed reality
Kun transientit tallennetaan:
-
tietokantaan
-
object cacheen
-
Redis/Memcachediin
locking-käyttäytyminen muuttuu hienovaraisesti.
Single server → melko ennustettava
Distributed cache → viiveitä, synkronointieroja
Lock ei ole aina välittömästi näkyvä kaikille prosesseille.
Ja concurrency-ongelmat rakastavat tällaisia tilanteita.
Kilpajuoksujen seuraukset käytännössä
Useimmiten kilpajuoksu ei kaada sivustoa. Se aiheuttaa outoja sivuvaikutuksia.
Esimerkiksi:
-
sama email lähetetään kahdesti
-
sama API-kutsu tehdään useasti
-
sama raportti generoidaan rinnakkain
-
data päivittyy epäjohdonmukaisesti
Nämä ovat niitä virheitä, jotka tuntuvat “satunnaisilta”.
Mutta ne eivät ole satunnaisia.
Ne ovat ajoitusongelmia.
Idempotenssi: concurrency-maailman supervoima
Kun cron-tehtäviä suunnitellaan, yksi käsite nousee ylitse muiden:
Idempotenssi.
Idempotentti operaatio tarkoittaa:
Voidaan suorittaa useasti ilman haitallisia sivuvaikutuksia.
Jos cron ajetaan kahdesti:
-
ei duplikaatteja
-
ei datakorruptiota
-
ei kaaosta
WP-Cron-maailmassa tämä ei ole vain hyvä käytäntö.
Se on selviytymisstrategia.
Locking vs logiikan kestävyys
Cron locking yrittää estää kaksoisajot.
Mutta kypsä arkkitehtuuri olettaa:
“Kaksoisajot ovat mahdollisia.”
Tämä ajattelutavan muutos on kriittinen.
Locking on optimointi.
Idempotenssi on turvaverkko.
Korkean liikenteen sivustot ja cron-dynamiikka
Mitä enemmän liikennettä:
-
sitä useammin cron tarkistetaan
-
sitä enemmän concurrency-tilanteita
-
sitä suurempi kilpajuoksuriski
Ironia on herkullinen.
WP-Cron toimii parhaiten:
-
matalan liikenteen sivustoilla
Ja joutuu kovimmalle koetukselle:
-
korkean liikenteen ympäristöissä
Oikea cron vs WP-Cron
Tästä syystä monissa tuotantoympäristöissä käytetään:
Oikeaa server cron -ajastusta.
Server cron:
-
käynnistää WP-Cronin hallitusti
-
vähentää kilpailutilanteita
-
lisää ennustettavuutta
WP-Cron:
-
opportunistinen
-
reaktiivinen
-
elegantti mutta epätäydellinen
Kumpikaan ei ole “väärä”. Ne palvelevat eri konteksteja.
Kilpajuoksuongelmien filosofia
Kilpajuoksut eivät ole WordPress-ongelma.
Ne ovat distributed systems -ongelma.
Kun useita prosesseja toimii:
-
samassa tilassa
-
samassa ajassa
-
ilman täydellistä synkronointia
kilpajuoksut ovat väistämättömiä.
WP-Cron ei riko tätä sääntöä. Se elää sen sisällä.
Lopuksi: locking ei ole pelastus, vaan kompromissi
Cron locking ei ole virheetön lukitusmekanismi.
Se on:
-
kevyt
-
nopea
-
riittävän hyvä useimpiin tilanteisiin
Ja juuri tämä “riittävän hyvä” on WordPressin design-filosofian ydin.
Täydellinen locking vaatisi:
-
raskaita synkronointimekanismeja
-
suorituskykyuhrauksia
-
monimutkaista infrastruktuuria
WordPress valitsee käytännöllisyyden.
Kypsä kehittäjä ei kysy:
“Miten estän kaikki kilpajuoksut?”
Kypsä kehittäjä kysyy:
“Miten järjestelmä kestää kilpajuoksut?”
Ja juuri siinä kohtaa cron-logiikka muuttuu mekaanisesta ajastuksesta resilientiksi systeemisuunnitteluksi.
Kilpajuoksut eivät ole poikkeus.
Ne ovat moniprosessimaailman luonnonlaki.
