25 gennaio 2009

Il listener che ascoltava troppo

Il listener, per Oracle, è il processo che rimane in ascolto su una porta di rete per le richieste di connessione remote, indirizzandole successivamente al processo server o al dispatcher opportuno.

Il listener può essere controllato tramite la sua console lsnrctl (Listener Control Utility): lo si può fare partire, spegnere e riconfigurare in vari modi, anche senza farlo ripartire, grazie al comando RELOAD.

Con il comando STATUS è possibile avere una panoramica piuttosto dettagliata su che cosa sta facendo il listener:
LSNRCTL> status
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=myhost)(PORT=1521)))
STATUS of the LISTENER
------------------------
Alias LISTENER
Version TNSLSNR for Linux: Version 10.2.0.3.0 - Production
Start Date 10-JAN-2009 13:21:16
Uptime 12 days 22 hr. 17 min. 45 sec
Trace Level off
Security ON: Local OS Authentication
SNMP OFF
Listener Parameter File /u01/app/oracle/product/10.2.0/network/admin/listener.ora
Listener Log File /u01/app/oracle/product/10.2.0/network/log/listener.log
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=myhost)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC0)))
Services Summary...
Service "ORA10G" has 1 instance(s).
Instance "ORA10G", status READY, has 1 handler(s) for this service...
Service "ORA10GXDB" has 1 instance(s).
Instance "ORA10G", status READY, has 1 handler(s) for this service...
Service "ORA10G_XPT" has 1 instance(s).
Instance "ORA10G", status READY, has 1 handler(s) for this service...
Service "PLSExtProc" has 1 instance(s).
Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully


L'avvio o la chiusura del listener sono operazioni privilegiate, che quindi richiedono un certo livello di privilegi all'utente. Se ci si collega al server come utente appartenente al gruppo dba o addirittura come utente oracle, i privilegi per dette operazioni vengono automaticamente garantiti. Ciò è infatti visibile nella riga Security ON: Local OS Authentication.

Nelle versioni 9i e precedenti, però, generalmente il controllo di accesso alle operazioni privilegiate è disabilitato:
LSNRCTL> status
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
STATUS of the LISTENER
------------------------
Alias LISTENER
Version TNSLSNR for Solaris: Version 9.2.0.7.0 - Production
Start Date 27-MAY-2008 14:45:13
Uptime 240 days 19 hr. 51 min. 0 sec
Trace Level off
Security OFF
SNMP OFF
Listener Parameter File
/u01/oracle/product/9207/network/admin/listener.ora
Listener Log File /u01/oracle/product/9207/network/log/listener.log
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=ora9ihost)(PORT=1521)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "ORA9I" has 1 instance(s).
Instance "ORA9I", status UNKNOWN, has 1 handler(s) for this service...
Service "ORA9I" has 1 instance(s).
Instance "ORA9I", status READY, has 2 handler(s) for this service...
The command completed successfully

Il problema non è rilevante per quanto riguarda gli utenti locali del server su cui risiedono il database e il listener, in quanto protetti in qualche modo da password, ma perché è possibile controllare remotamente un listener non "blindato" tramite la sua console lsnrctl installata su qualsiasi altra macchina. Esiste infatti la possibilità di specificare il current_listener; vediamolo in opera utilizzando il comando version (ma status va benissimo):
LSNRCTL> version
Connecting to
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=myhost)(PORT=1521)))
TNSLSNR for Linux: Version 10.2.0.3.0 - Production
TNS for Linux: Version 10.2.0.3.0 - Production
Unix Domain Socket IPC NT Protocol Adaptor for Linux: Version 10.2.0.3.0 - Production
Oracle Bequeath NT Protocol Adapter for Linux: Version 10.2.0.3.0 - Production
TCP/IP NT Protocol Adapter for Linux: Version 10.2.0.3.0 - Production,,
The command completed successfully
LSNRCTL> set current_listener ora9ihost
Current Listener is ora9ihost
LSNRCTL> version
Connecting to
(DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=ora9ihost))(ADDRESS=(PROTOCOL=TCP)(HOST=ora9ihost)(PORT=1521)))
TNSLSNR for Solaris: Version 9.2.0.7.0 - Production
TNS for Solaris: Version 9.2.0.7.0 - Production
Unix Domain Socket IPC NT Protocol Adaptor for Solaris: Version 9.2.0.7.0 - Production
Oracle Bequeath NT Protocol Adapter for Solaris: Version 9.2.0.7.0 - Production
TCP/IP NT Protocol Adapter for Solaris: Version 9.2.0.7.0 - Production,,
The command completed successfully

Il comando VERSION fornisce le informazioni per qualsiasi database (e già queste informazioni a disposizione di tutti potrebbero essere inopportune), ma a noi interessa che con il listener 9i non opportunamente configurato è possibile a questo punto scrivere "stop" e il listener viene spento su ora9ihost.
Per 10g invece questa "feature" viene disabilitata di default, quindi cercando di fermare il listener remotamente si ottiene:
LSNRCTL> stop
Connecting to
(DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=ora10g))(ADDRESS=(PROTOCOL=TCP)(HOST=ora10g)(PORT=1521)))
TNS-01189: The listener could not authenticate the user

Inoltre, con un listener "aperto", impostando log_directory, log_file e log_status si può sovrascrivere qualsiasi file accessibile all'utente con cui gira il listener a cui ci si è collegati (meglio non immaginare che cosa è possibile fare).

In 10g, per il listener è attivo di default (ON) il nuovo parametro LOCAL_OS_AUTHENTICATION_<listener>, quindi non ci si deve preoccupare più di tanto; con il parametro impostato a "OFF" il listener si comporta come su 9i. In tal caso (10g aperto, 9i e precedenti) è opportuno impostare la password per il listener con il comando CHANGE_PASSWORD.
Una volta impostata la password, per eseguire qualsiasi operazione privilegiata è necessario prima autenticarsi con SET PASSWORD.

06 gennaio 2009

Un'interconnect RAC efficiente

Com'è noto un cluster Oracle è formato da più istanze che montano lo stesso database condiviso.
Ogni istanza ha una propria buffer cache che si gestisce autonomamente, in più esiste una caratteristica di RAC chiamata cache fusion, che sarebbe un insieme di processi che si parlano tra loro da un nodo del cluster all'altro, garantendo l'integrità dei dati attraverso le istanze.

Dato che una query, eseguita su un nodo qualunque del cluster, deve ottenere risultati consistenti, le istanze devono poter parlare tra loro tramite una connessione molto veloce, che in genere viene ottenuta tramite una rete ethernet, soprattutto per la relativa economicità della soluzione.
Le istanze trasferiscono tra loro direttamente i blocchi della SGA, che vengono quindi impacchettati secondo gli usuali protocolli di rete per essere spediti alle altre istanze del cluster. Oracle usa UDP per il trasferimento, penso soprattutto per evitare l'overhead del TCP.
La dimensione dei pacchetti trasferiti sull'interconnect è data al parametro della scheda di rete MTU, che è la dimensione in byte del più grande pacchetto o frame che può essere inviato attraverso la rete. In generale il default per tutte le interfacce di rete è 1500 byte.
Ciò implica che, trasferendo un blocco dalla SGA di un nodo a quella di un altro nodo, il blocco viene spezzettato in più frame sufficienti a contenere le sue dimensioni (es. 8K standard). Questa operazione è relativamente lenta, e poiché le prestazioni dell'interconnect possono avere un forte impatto sulle prestazioni globali del cluster, è preferibile limitare l'uso dell'interconnect, facendo in modo ad esempio che qualsiasi nodo del cluster lavori prevalentemente o unicamente su un sottoinsieme dei dati disgiunto da quello su cui lavora ciascun altro nodo.

C'è anche un modo per aumentare le prestazioni dell'interconnect: l'uso dei jumbo frame.
I jumbo frame sono dei frame (molto) più grandi di quelli standard di 1500 bytes; generalmente il limite delle loro dimensioni è 9000 bytes; questo limite è interessante perché molto vicino alle dimensioni "standard" del blocco Oracle (8K), e comunque superiore alle dimensioni dei blocchi usati in OLTP (2K, 4K, 8K).

L'idea che mi è venuta è di verificare che i blocchi transitino "intatti" sulla interconnect, in modo da massimizzare le prestazioni, al variare della MTU. Non tutte le apparecchiature di rete, infatti, supportano i jumbo frame o comunque dimensioni troppo grandi dei frame.
Il modo più diretto e anche più affidabile è usare un tool di monitoring del traffico. Io propendo per Wireshark, per la facilità e l'estrema potenza.

Il test che ho pensato è il seguente: creo una tabella 3 o 4 blocchi in un tablespace con blocksize di 8K, inserisco un po' di dati ad-hoc, "carico" il suo contenuto nella SGA dell'istanza su cui sto lavorando (una semplice select va bene, anche se l'insert ordinaria dovrebbe bastare), e poi cerco di accedere agli stessi dati da una seconda istanza del cluster (un'altra select è sufficiente).
Ipotizzo che Oracle trasferisca i blocchi in questione sull'interconnect invece che leggerli da disco, poiché si presume che il trasferimento via interconnect sia molto più veloce.

Per riconoscere i blocchi nel traffico di rete utilizzo dei "marker" sui dati: creo una tabella contenente stringhe ben visibili. In questo caso utilizzo stringhe tipo "AAAA...", "BBBB...." e così via, per 20 righe di circa 2000 caratteri ciascuna, in modo che in ogni blocco da 8192 byte ci stiano 3/4 record. "Forzo" la massima occupazione dello spazio nei blocchi utilizzando l'opzione pctfree 0.
SQL> create table aob (rn integer, rp char(2000)) pctfree 0;

Table created.

SQL> insert into aob select rownum, rpad(chr(64+rownum), 2000, chr(64+rownum))
from all_objects where rownum <= 20;

20 rows created.

SQL> commit;

Commit complete.

SQL> select rn, substr(rp, 1, 1) letter, length(rp) len, rowid
from aob order by rn;

RN LETT LEN ROWID
---------- ---- ---------- ------------------
1 A 2000 AAAM8OAAFAAABCXAAA
2 B 2000 AAAM8OAAFAAABCXAAB
3 C 2000 AAAM8OAAFAAABCXAAC
4 D 2000 AAAM8OAAFAAABCXAAD
5 E 2000 AAAM8OAAFAAABCYAAA
6 F 2000 AAAM8OAAFAAABCYAAB
7 G 2000 AAAM8OAAFAAABCYAAC
8 H 2000 AAAM8OAAFAAABCYAAD
9 I 2000 AAAM8OAAFAAABCUAAA
10 J 2000 AAAM8OAAFAAABCUAAB
11 K 2000 AAAM8OAAFAAABCUAAC
12 L 2000 AAAM8OAAFAAABCUAAD
13 M 2000 AAAM8OAAFAAABCVAAA
14 N 2000 AAAM8OAAFAAABCVAAB
15 O 2000 AAAM8OAAFAAABCVAAC
16 P 2000 AAAM8OAAFAAABCVAAD
17 Q 2000 AAAM8OAAFAAABCWAAA
18 R 2000 AAAM8OAAFAAABCWAAB
19 S 2000 AAAM8OAAFAAABCWAAC
20 T 2000 AAAM8OAAFAAABCWAAD

20 rows selected.

Nell'ultima select per brevità è presente solo una lettera per ogni riga (rp è una stringa char di 2000 caratteri tutti uguali); è evidente come i record stiano nello stesso blocco a gruppi di 4 dal rowid: la riga all'interno del blocco è indetificata dagli ultimi tre caratteri del rowid, mentre il blocco dai 5 caratteri precedenti.
Ho quindi un modo per identificare visualmente i blocchi in transito sull'interconnect: il primo sarà pieno di A, B, C e D; il secondo di E, F, G, H, e così via.

Dopo aver eseguito le istruzioni precedenti su un nodo del cluster, passo al successivo facendo semplicemente
SQL> select * from aob;

Nel frattempo ho attivato Wireshark in modo da loggare tutto il traffico di passaggio sull'interfaccia di rete dell'interconnect su uno dei due nodi del cluster.


Ecco che cosa si vede da Wireshark con la MTU a 1500 byte (il blocco visualizzato è evidenziato in blu nell'elenco dei frame):

I frame di dati sono lunghi 882 byte, e vengono riassemblati in blocchi da 8248 byte (vedere in basso a sinistra nell'immagine). Nell'immagine si vede uno dei frame contenenti la "P"; servono perfino più di due frame per poter trasmettere anche una sola riga, lunga almeno 2002 byte.
Il frame di 1500 byte è quindi addirittura di gran lunga sottoutilizzato in questo caso.


Ora vediamo in dettaglio il traffico con la MTU a 9000 byte.
Ecco il blocco che comincia con la riga delle "I":
Più in basso si passa dalla "K" alla "L":
Ecco il blocco della "A" (notare che non sono in ordine, giustamente):
Ecco la fine del blocco della "A", "D":
Nei dati del blocco è presente la dimensione del frame, 8282 byte:
Ciò significa che, con l'MTU impostata a 9000 byte, i blocchi transitano integri sull'interconnect, garantendone il massimo delle prestazioni.
Probabilmente è possibile arrivare ad un valore ottimale per l'MTU, di poco superiore o di poco inferiore, guardando anche gli altri tipi di comunicazione sull'interconnect, ma per ora ci fermiamo qui.