martedì 27 agosto 2013

PostgreSQL fancy uptime

Dalla versione 8.1 PostgreSQL mette a disposizione una funzione interna, pg_postmaster_start_time(), che restituisce il momento di avvio dell'istanza corrente (ossia del postmaster).
Unendo tale funzione al current_timestamp e' possibile ottenere facilmente il tempo di uptime del server:

# SELECT date_trunc( 'seconds', current_timestamp - pg_postmaster_start_time() );
date_trunc
------------
00:32:37

In questo modo e' possibile ottenere l'intervallo temporale di uptime del server con precisione al secondo.

Un DBA/Sysadmin che si rispetti dovrebbe sempre controllare con precisione l'uptime del server, ma per chi vuole un uptime un po' piu' "fancy", ho creato una stored procedure, denominata appunto "uptime", che restituisce una stringa di testo con una frase descrittiva del tempo di esecuzione del server, opportunamento arrotondato.
Ad esempio:

# SELECT uptime();
uptime
--------------------
Almost 35 minutes

E ad esempio, dopo 47 minuti di attivita' la funzione di uptime riporta:

# SELECT uptime();
uptime
---------------
Almost 1 hour

La funzione opera in questo modo:
1) ottiene l'uptime in modo preciso;
2) spezza l'uptime nelle sue singole parti (giorni, ore, minute, secondi);
3) calcola gli arrotondamenti per eccesso/difetto. Ad esempio se sono passati piu' di 30 secondi si incrementa di una unita' il contatore dei minuti e si escludono i secondi; se analogamente sono passati piu' di 45 minuti si incrementa il contatore delle ore e si tralasciano i minuti, e cosi' via;
4) si decide quali "pezzi" far vedere (anni, giorni, ore, minuti);
5) si compone una stringa con opportuni separatori lessicali.

Ovviamente la funzione puo' essere migliorata, e buona parte del tempo speso dalla funzione stessa serve a calcolare cosa far vedere all'utente e con che arrotondamento. Questo a maggiore ragione colloca la funzione in ambito "fancy" e non certo come strumento di precisione per la manutenzione dell'istanza PostgreSQL.


Ci sono alcuni casi particolari: se il server e' stato avviato da meno di 10 minuti allora viene ritornato il valore speciale 'now' (da non confondere con l'equivalente testo spesso usato in PostgreSQL):

# SELECT uptime();
uptime
-------------
Almost now!

Se si cade nell'intervallo temporale di +10 e -15 minuti i minuti sono mostrati con maggiore precisione presunta, come ad esempio:

# SELECT uptime();
uptime
-------------------------
Pretty much 12 minutes

Di seguito il codice sorgente della funzione.


CREATE OR REPLACE FUNCTION uptime()
RETURNS text
AS $BODY$

DECLARE
current_uptime_interval interval;
current_description text;
current_hours float;
current_minutes float;
current_days float;
current_years float;
current_seconds float;
minutes_separator text;
hours_separator text;
days_separator text;
BEGIN

current_hours := 0;
current_minutes := 0;
current_days := 0;
current_years := 0;
current_seconds := 0;
minutes_separator := '';
days_separator := '';
hours_separator := '';
current_description := 'Pretty much ';

SELECT date_trunc( 'seconds', current_timestamp - pg_postmaster_start_time() )
INTO current_uptime_interval;

SELECT date_part( 'hour', current_uptime_interval )
INTO current_hours;

SELECT date_part( 'minutes', current_uptime_interval )
INTO current_minutes;

SELECT date_part( 'days', current_uptime_interval )
INTO current_days;

SELECT date_part( 'seconds', current_uptime_interval )
INTO current_seconds;



IF current_seconds > 30 THEN
current_seconds := 0;
current_minutes := current_minutes + 1;
current_description := 'Almost ';
END IF;

IF current_minutes >= 45 THEN
current_hours := current_hours + 1;
current_minutes := 0;
current_description := 'Almost ';
ELSEIF current_minutes <= 10 THEN current_minutes := 0; current_description := 'Almost '; END IF; IF current_hours >= 16 THEN
current_days := current_days + 1;
current_hours := 0;
current_minutes := 0;
current_description := 'Almost ';
END IF;

IF current_days >= 300 THEN
current_days := 0;
current_hours := 0;
current_minutes := 0;
current_years := current_years + 1;
current_description := 'Almost ';
END IF;


-- special case: the server started here!
IF current_years = 0
AND current_days = 0
AND current_hours = 0
AND current_minutes < 10 THEN RETURN 'Almost now!'; END IF; IF current_years > 0 AND current_days > 0 THEN
days_separator := ' and ';
END IF;
IF current_days > 0 AND current_hours > 0 THEN
hours_separator := ' and ';
END IF;
IF current_hours > 0 AND current_minutes > 0 THEN
minutes_separator := ' and ';
END IF;


SELECT current_description
|| CASE WHEN current_years = 1 THEN '1 year'
WHEN current_years > 1 THEN current_years || ' years'
ELSE ''
END
|| days_separator
|| CASE WHEN current_days = 1 THEN '1 day'
WHEN current_days > 1 THEN current_days || ' days'
ELSE ''
END
|| hours_separator
|| CASE WHEN current_hours = 1 THEN '1 hour'
WHEN current_hours > 1 THEN current_hours || ' hours'
ELSE ''
END
|| minutes_separator
|| CASE WHEN current_minutes = 1 THEN '1 minute '
WHEN current_minutes > 1 THEN current_minutes || ' minutes '
ELSE ''
END
INTO current_description;

RETURN current_description;
END;
$BODY$
LANGUAGE plpgsql;

venerdì 23 agosto 2013

Una breve (e superficiale) storia della Apple



Chi mi conosce sa che non sono un gran fan dei prodotti Apple, specialmente quelli di ultima generazione. Questo video rappresenta un veloce (e ovviamente superficiale) riassunto della storia Apple e del suo creatore.
Grande attenzione deve essere posta al minuto 5, quando si introduce la vicenda BeOS vs Next.

mercoledì 21 agosto 2013

Alcuni utilizzi di Erlang

Curiosando un po' riguardo Erlang, uno dei linguaggi che conosco solo in teoria e che prima o poi mi deciderò ad imparare,
ho scoperto che questo potente linguaggio che fa del fault-tolerance (e quindi della concorrenza) la sua forza viene usato come backend per la chat di Facebook e di Whatsapp.

martedì 20 agosto 2013

L'uso del SeqScan

Il sequential scan o scansione sequenziale e' uno dei metodo di accesso ai dati in una tabella che ogni database,
e conseguentemente PostgreSQL, fornisce. Tale metodo di accesso non e' assolutamente ottimizzato e prevede la lettura "stupida" del contenuto di una tabella in maniera sequenziale, ovvero dalla prima tupla fino all'ultima.

PostgreSQL non supporta i query hints, ovvero non consente di specificare a livello di query quale indice utilizzare per recuperare le tuple. Tuttavia PostgreSQL fornisce alcuni parametri di configurazione che possono essere configurati a livello di cluster per abilitare o disabilitare alcuni metodi di accesso. Ne consegue che per forzare l'accesso tramite indice ai dati si possono disabilitare gli altri metodi di accesso.

Questo non vale per il sequential scan: tale metodo di accesso e' da considerarsi una sorta di "ultima spiaggia" per l'accesso ai dati. Di conseguenza il sequential scan non puo' essere evitato se non c'e' altro modo di recuperare i dati.

Per meglio comprendere questo concetto si consideri una piccola tabella (ossia con poche tuple) e un indice sulla chiave primaria definita come segue:


demodb=# CREATE TABLE foo( pk SERIAL NOT NULL, description TEXT, PRIMARY KEY( pk ) );
NOTICE: CREATE TABLE will create implicit sequence "foo_pk_seq" for serial column "foo.pk"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
CREATE TABLE
demodb=# INSERT INTO foo( description ) VALUES( 'Test ' || generate_series(1, 100 ) );
INSERT 0 100
demodb=# ANALYZE foo;
ANALYZE
demodb=# SELECT relname, relpages, reltuples FROM pg_class
WHERE relname = 'foo' AND relkind = 'r';

relname | relpages | reltuples
---------+----------+-----------
foo | 1 | 100
(1 row)



Come ci si puo' aspettare, essendo la relazione piuttosto piccola (solo una pagina dati), l'accesso ai dati sara' di tipo sequenziale anche in presenza di una query risolvibile tramite l'indice:


demodb=# SHOW enable_seqscan;
enable_seqscan
----------------
on
(1 row)

demodb=# EXPLAIN ANALYZE SELECT * FROM foo WHERE pk = 10;
QUERY PLAN
---------------------------------------------------------------
Seq Scan on foo (cost=0.00..2.25 rows=1 width=11)
(actual time=0.024..0.053 rows=1 loops=1)
Filter: (pk = 10)
Total runtime: 0.097 ms
(3 rows)



Essendo presente un indice, se si disabilita il sequential scan si ottiene che l'accesso ai dati viene forzatamente fatto tramite il suddetto indice:



demodb=# SET enable_seqscan TO off;
SET
demodb=# SHOW enable_seqscan;
enable_seqscan
----------------
off
(1 row)

demodb=# EXPLAIN ANALYZE SELECT * FROM foo WHERE pk = 10;
QUERY PLAN
----------------------------------------------------------------------------
Index Scan using foo_pkey on foo (cost=0.00..8.27 rows=1 width=11)
(actual time=0.031..0.034 rows=1 loops=1)
Index Cond: (pk = 10)
Total runtime: 0.086 ms
(3 rows)



Come si puo' facilmente vedere il costo dell'accesso via indice e' superiore a quello sequenziale, poiché la relazione e' troppo piccola per trarre giovamento dal caricamento delle pagine di indice.
Ma cosa succede se l'indice non e' presente? E' sufficiente ripetere l'esperimento senza creare l'indice sulla chiave primaria per verificare il comportamento:



demodb=# DROP TABLE foo;
DROP TABLE
demodb=# CREATE TABLE foo( pk SERIAL NOT NULL, description TEXT );
NOTICE: CREATE TABLE will create implicit sequence "foo_pk_seq" for serial column "foo.pk"
CREATE TABLE
demodb=# INSERT INTO foo( description ) VALUES( 'Test ' || generate_series(1, 100 ) );
INSERT 0 100
demodb=# ANALYZE foo;
ANALYZE
demodb=# SHOW enable_seqscan;
enable_seqscan
----------------
off
(1 row)

demodb=# EXPLAIN ANALYZE SELECT * FROM foo WHERE pk = 10;
QUERY PLAN
-----------------------------------------------------------------------------
Seq Scan on foo (cost=10000000000.00..10000000002.25 rows=1 width=11)
(actual time=0.025..0.054 rows=1 loops=1)
Filter: (pk = 10)
Total runtime: 0.100 ms
(3 rows)



Come si puo' notare l'accesso ai dati e' ancora di tipo sequenziale, e non potrebbe essere altrimenti visto che non vi sono altri "percorsi" per raggiungere i dati. La cosa interessante pero' e' che il costo di accesso ai dati e' notevolmente piu' grande del caso precedente, e si tratta solo di un costo presunto, visto che quello effettivo e' rimasto invariato.
L'idea e' quindi quella di aumentare notevolmente il costo presunto di accesso per invogliare l'ottimizzatore a cercare altre strade per l'accesso ai dati.
Da notare che il costo di accesso sequenziale non viene mutato, di fatto e' il query planner che incrementa il costo presunto quando trova un metodo di accesso disabilitato.