Viime blogissa raapaisimme hieman SQLOS:n pintaa. Nyt sukelletaan hieman syvemmälle. Jotta ymmärtäisit hyvin SQL Serverin wait statseja, sinun tulisi ymmärtää, miten skedulointi (=scheduling) toimii.

Scheduling

Kuten edellisessä blogissani mainitsin, SQLOS:n toiminta poikkeaa selkeästi käyttöjärjestelmän vastaavasta järjestelmäpyyntöjen osalta. Seuraavat oliot liittyvät SQL Serverin hierarkiseen ”call stackiin”, ylhäältä alas:

  • Session
  • Request
  • Task
  • Worker thread
  • Scheduler
  • CPU

Oheinen kuva osoittaa, kuinka SQL Server skeduloi prosessoriaikaa:

Sessions

”Session” on kaikessa yksinkertaisuudessaan clientin autentikoitu tietokantayhteys SQL Serveriin. Sessiolistauksen saan SQL Serveristä täältä:

select
*
from
sys.dm_exec_sessions
where
is_user_process = 1

Huom! Tässä näkymässä ”session_id” -kenttä yksilöi kyseisen session. Kuitenkin vähintään ensimmäiset 50 session_id:tä ovat sisäisiä SQL Serverin sessioita, joten näkymä pitää filtteröidä ”is_user_process” -kentän avulla.

Muita maininnan arvoisia kenttiä tässä näkymässä ovat ”cpu_time”, joka kertoo session käyttämän kokonais CPU-ajan millisekunteina, sekä ”memory_usage”, joka kertoo, kuinka monta 8 kilotavun sivua muistia kyseinen sessio käyttää.

Näkymän ”status”-kenttä indikoi, mikäli vähintään yksi request on parhaillaan aktiivinen kyseisellä sessiolla (”Running” vs. ”Sleeping”). Requesteistä lisää seuraavassa kappaleessa tarkemmin!

Requests

”Request” on lyhyesti sanottuna SQL Server execution enginen tietokantakysely sille luodun session alaisuudessa, ja ne löytyvät täältä:

select * from sys.dm_exec_requests

Tämä näkymä on perffioptimoinnin näkökulmasta eräs tärkeimpiä, ellei jopa tärkein näkymä, joten se kannattaa painaa mieleen ja siihen kannattaa tutustua huolellisesti. Näkymästä löytyvät kaikki requestien kyselystatistiikat, kuten ”session_id” (parent sessionin avain), ”start_time” (kyselyn alkuhetki), ”query_hash” (tämä on hash-tiiviste varsinaisen kyselyn SQL-tekstistä, ja se on käytännössä aina null, ellei kyseinen request ole user session SQL-kysely, jolloin sitä voidaan hakea handlen avulla), ”plan_handle” (hash-tiiviste kyselyn planista, jolloin sitä voidaan hakea handlen avulla).

Myös ”wait_type” on tärkeä kenttä, ja se voi saada joko arvon ”SUSPENDED”, ”RUNNABLE” tai ”NULL”, mikäli kysely on juuri ajossa. Edelleen; ”last_wait_type” on keskeinen kenttä, koska se kertoo, mitä wait_typeä kysely on viimeksi kohdannut ajon aikana. Näistä nyansseista enemmän blogisarjassani tuonnempana. Viimeinen keskeinen kenttä näkymässä on ”total_elspsed_time”. Se kertoo, kuinka kauan kalenteriajallisesti requestia on prosessoitu millisekunneissa.

Tasks

Taskit ovat niitä olioita, joiden tulee suorittaa tietty työ SQLOS:ssä, mutta vitsi pilee siinä, että ne eivät itsessään suorita mitään tehtävää. Sen sijaan, voit ajatella ne eräänlaisina tiedonpalasina, kuten esimerkiksi kirjeinä, joita kirjekyyhkyt (=worker threads) välittävät paikasta toiseen. Taski on siinä ikäänkuin “säikeen kyydissä”.

Aina kun SQL Server saa user session kautta requestin, syntyy yksi tai useampi taski, jotta request voidaan suorittaa. Kyselyn paralleelisuusasetukset vaikuttavat taskien määrään. Taskit löytyvät täältä:

select * from sys.dm_os_tasks

Tätä näkymää tulee filtteröidä käyttämäsi “session_id”:n ja joskus myös “request_id”:n kanssa, jotta saat käsiisi juuri ne tehtävät, jotka on allokoitu tietylle requestille tietyssä user sessiossa. Mainittavan arvoinen on myös kenttä “worker_address”, koska se kertoo taskille dedikoidun worker threadin muistiosoitteen.

Worker threads

Jokainen tehtävä (=task) saa itselleen dedikoidun worker threadin, joka lähtee suorittamaan sille annettua tehtävää. (Tai oikeammin; worker thread pyytää threadia Windows OS:ltä suorittamaan sille osoitetun työn). Kun tehtävä kutsuu työsäiettä (=worker thread), SQL Server poimii seuraavan vapaana olevan työsäikeen ja dedikoi sen tehtävälle. Mikäli vapaata (=idle) työsäiettä ei löydy ja maksimi määrä työsäikeitä on saavutettu, koko request laitetaan jonoon, kunnes työsäie saa meneillään olevan työnsä valmiiksi ja vapautuu.

64-bittisessä systeemissä työsäikeiden määrä voidaan laskea kaavalla 512 + ((M – 4) * M), jossa “M” on loogisten corejen määrä. SQL Server poolaa automaattisesti työsäikeensä palvelun käynnistyksen yhteydessä. Tyypillinen määrä SQL Serverin thread poolin työsäikeille on siis karkeasti ottaen 512:n ja 1476:n välissä (4:stä 64:ään corea). Tätä arvoa voi toki säätää instanssin “max worker threads” -asetuksesta, mutta en suosittele. Se on oletuksellisesti 0 (=kaikki). Jokainen työsäie tarvitsee toimiakseen 2048 kilotavua muistia 64-bittisessä systeemissä, joten asetuksen säätäminen vaikuttaa myös SQL Serverin kokonaismuistitarpeeseen, vaikkakin ylimääräiset (idle) työsäikeet tuhotaan automaattisesti 15 minuutin käyttämättömyyden jälkeen, mikäli SQL Serverissä on muistipainetta. Työsäikeet löytyvät täältä:

select * from sys.dm_os_workers

Kentät “task_address” ja “scheduler_address” viittaavat tehtäviin ja schedulereihin, joihin näkymä assosioituu. Tärkein kenttä lienee “state”, koska se kertoo, missä tilassa työsäie on prosessorin (=CPU) suhteen:

  • “INIT” tarkoittaa, että SQLOS parhaillaan alustaa työsäiettä tehtävälle.
  • “RUNNING” tarkoittaa, että työsäie on parhaillaan ajossa CPU:lla.
  • “RUNNABLE” tarkoittaa, että työsäie on valmis ajoon prosessorilla.
  • “SUSPENDED” tarkoittaa, että työsäie odottaa jotain resurssia ennen suoritusta.

Tässä on oikeastaan koko wait statistiikan keräysprosessin ydin: Aina, kun työsäie ei ole “RUNNING” -tilassa, se joutuu odottamaan jotain resurssia, jolloin SQLOS kirjaa tämän odotusajaksi tietylle wait typelle.

Schedulers

Schedulerin tehtävä on aikatauluttaa taskeilla tehtävää työtä työsäikeille. Kun taski pyytää SQL Serveriltä työaikaa tietylle user requestille, juurikin scheduler niputtaa taskin työsäikeille prosessoitavaksi. Scheduler myöskin huolehtii työsäikeiden välisestä synkronoinnista sekä siitä, että kun thread quantum on ajettu, työsäie vapauttaa (=yield) itsensä. Tätä mekanismia kutsutaan nimellä “cooperative scheduling”. Thread quantum on 4 ms pituinen aikayksikkö, joka on se maksimiaika, jonka yksittäinen työsäie voi kerrallaan prosessoida taskia CPU:ssa (=“RUNNING” -tila). Scheduler siis pitää huolen siitä, että prosessori suorittaa yhtä työsäiettä kerrallaan maksimissaan 4 ms ajan, jonka jälkeen se vapauttaa suorituksen muille työsäikeille, jottei prosessori jäisi potentiaalisesti “jumiin” yhden yksittäisen säikeen suorittamisen kanssa. Tästä prosessista tarkemmin seuraavassa blogipostauksessa. Schedulerit löytyvät täältä:

select * from sys.dm_os_schedulers

SQL Serverin prosessorimäärä kerrottuna coremäärällä on sama kuin sen schedulerien kokonaismäärä – tai no, ainakin melkein: Kenttä “status” on näille schedulereilla “VISIBLE ONLINE”, kun taas legendaarinen admin-työsäie (Dedicated Administrator Connection) on merkattu näkymässä “VISIBLE ONLINE (DAC)” -statuksella, sekä edelleen sisäisille taskeille tarkoitetut schedulerit ovat muotoa “HIDDEN ONLINE”. “current_workers” -kenttä taas kertoo, Kuinka monta työsäiettä kuhunkin scheduleriin on assosioitu. “active_workers” -kenttä kertoo, kuinka moni niistä on aktiivisia tietyllä schedulerilla (“RUNNING”, “RUNNABLE”, “SUSPENDED”). “work_queue”-kenttä kertoo hyvin oleellisen asian tässä kontekstissa: Se kertoo, Kuinka monta taskia on odottamassa vapaata työsäiettä kyseisen schedulerin alla.

Yhteenveto

Kuten huomataan, SQL Serverin toiminta on hyvin looginen ja eheä käyttäjärequestien osalta:

  1. Käyttäjä ottaa SQL Serveriin tietokantayhteyden jollain sovelluksella
  2. SQL Server luo session kyseiselle loginille, kun käyttäjä on autentikoitu
  3. Kun käyttäjä lähettää SQL Serverille kyselyn, request ja taski luodaan kuvaamaan suoritettavaa työyksikköä SQL Serverissä
  4. Scheduler sitoo taskille worker threadeja, jotta tehtävä voidaan suorittaa

Seuraavassa blogissani uppoudutaan tarkemmin siihen, kuinka wait statsit toimivat, sekä miten niitä kysellään.