mardi 19 juillet 2011

ONS ( Oracle Notify Service ) & FCF ( Fast Connection Failover )

ONS est un service du noyau Oracle permettant de délivrer des messages, plus exactement des FAN ( Fast Application Notification ). Il est alimenté par le processus racgimon, ce dernier récupérant les informations dans la queue SYS$SERVICE_METRICS.

Cette fonctionnalité est présentée dans la note ID 759895.1. Pour plus de détails, il est utile de rechercher sous google avec les mots-clés suivants: rac workload management. En particulier, vous trouverez un pdf très détaillé sur le sujet écrit par Alex Gorbachev ( Pythian Group ).

Les FAN peuvent être récupérés en java de la manière suivante:

PATH=/apps/oracle/10.2.0.4/jdk/bin:$PATH

javac -classpath /apps/oracle/10.2.0.4/opmn/lib/ons.jar onc_subscriber.java
java -classpath /apps/oracle/10.2.0.4/opmn/lib/ons.jar:. onc_subscriber

/*
* Copyright (c) 2001, 2004 by Oracle. All Rights Reserved
* ONC Subscription client. This client listens for all events ONS receives
* Based on the event type decisions are made on how and whether to print the
* event body
*/
import oracle.ons.*;
import java.util.*;
import java.io.*;

import java.nio.*;
public class onc_subscriber
{
public static void main(String args[])
{
boolean debug = false;
// Set ONC-required System property for oracle.ons.oraclehome:
System.setProperty("oracle.ons.oraclehome", "/apps/oracle/crs");
Subscriber s = new Subscriber("", ""); // subscribe to all events
Notification e;
System.out.println("ONC subscriber starting");
boolean shutdown = false;
while (!shutdown)
{
e = s.receive(true); // blocking wait for notification receive
System.out.println( "** HA event received -- Printing header:" );
e.print();
System.out.println( "** Body length = " + e.body().length);
System.out.println( "** Event type = " + e.type());
if (e.type().startsWith("database")) {
if (debug) { System.out.println( "New print out"); }
/*evtPrint myEvtPrint = new evtPrint(e);*/
} else if (e.type().startsWith("javaAPI")){
System.out.println( "javaAPI");
/*oncPrint myPrint = new oncPrint(e);*/
} else {
System.out.println("Unknown event type. Not displaying body");
}
try
{
if (e.type().equals("onc/shutdown")) {
System.out.println("Shutdown event received.");
shutdown = true;
}
else {
java.lang.Thread.currentThread().sleep(100);
System.out.println("Sleep and retry.");
}
}
catch (Exception te)
{
te.printStackTrace();
}
}
s.close();
System.out.println(" ONC subscriber exiting!");
}
}

On peut ainsi être informé du démarrage d'un service:

** HA event received -- Printing header:
Notification Type:         database/event/service
Affected Components:       null
Affected Nodes:            null
Delivery Time:             1310720203166
Generating Component:      database/rac/service
Generating Node:        node   
Generating Process:   node:725120
Notification ID:           node:725120010826
Notification Creation Time:1310720203
Cluster ID:                databaseClusterId
Cluster Name:              databaseClusterName
Instance ID:               databaseInstanceId
Instance Name:             databaseInstanceName
Local Only Flag:           FALSE
Cluster Only Flag:         FALSE
Body:                      [B@4dd0567f
** Body length = 112
** Event type = database/event/service
VERSION=1.0 service=SERVICE_TEST instance=instance database=database host=node status=up card=2 reason=user
Sleep and retry.

Ou de la répartition de charge au niveau des instances ( pré-requis: activation de LBA au niveau du service ):

* HA event received -- Printing header:
Notification Type:         database/event/servicemetrics/SERVICE_TEST
Affected Components:       null
Affected Nodes:            null
Delivery Time:             1310720807564
Generating Component:      database/rac/service
Generating Node:         node
Generating Process:     node:1089766
Notification ID:            node:1089766079356
Notification Creation Time:1310720807
Cluster ID:                databaseClusterId
Cluster Name:              databaseClusterName
Instance ID:               databaseInstanceId
Instance Name:             databaseInstanceName
Local Only Flag:           FALSE
Cluster Only Flag:         FALSE
Body:                      [B@44c617a0
** Body length = 149
** Event type = database/event/servicemetrics/SERVICE_TEST
VERSION=1.0 database=database { {instance=instance 1 percent=50 flag=UNKNOWN}{instance= instance 2 percent=50 flag=UNKNOWN} } timestamp=2011-07-15 11:06:47
Sleep and retry.

FCF est un exemple d'utilisation d'ONS pour la gestion du failover. Il peut être mis en oeuvre par un client java s'appuyant sur un driver JDBC thin. Un exemple très complet est disponible à cette adresse: http://www.idevelopment.info/data/Programming/java/jdbc/High_Availability/FastConnectionFailoverExampleThin.java.

On peut le compiler et l'exécuter de cette manière:
PATH=/apps/oracle/10.2.0.4/jdk/bin:$PATH
export CLASSPATH=.:/apps/oracle/10.2.0.4/jdbc/lib/ojdbc14.jar:/apps/oracle/10.2.0.4/opmn/lib/ons.jar

javac FastConnectionFailoverExampleThin.java
java -Doracle.ons.oraclehome=/apps/oracle/crs FastConnectionFailoverExampleThin

Sur un noeud du RAC ( 10.2.0.4 ), je n'ai pas réussi à intercepter les notifications de type servicemetrics. Je me suis alors heurté à un problème de contention au niveau des AQ ( Advanced Queues ) similaire à celui décrit dans ce message: http://oraclemva.wordpress.com/2010/02/26/content-problems-with-aq/ 

jeudi 9 juin 2011

Services: load-balancing et failover

Un service est un objet de type réseau permettant de qualifier la communication entre un client et une ou plusieurs instances. En particulier, il permet de préciser les caractéristiques du load-balancing et du failover.

Jeremy Schneider a écrit un document très intéressant sur le sujet, Unleashing Oracle Services: A Comprehensive Review of "Services" in Oracle Databases.

Si on considère un RAC 10g à deux noeuds, on peut créer un service avec OEM ou en utilisant srvctl: srvctl add service -d base -s service -r instance1,instance2 -P BASIC.
Dans cet exemple, on a défini un service utilisant les deux instances et on lui a associé un failover de type TAF ( Transparent Application Failover ).

Remarque:  non public bug #6886239 "DBMS_SERVICE parameters are not added using srvctl add service. It affects 10g and 11gR1 databases, this is fixed in release 11.2 onwards. In Oracle RAC after setting up TAF although it does not fail and everything looks good, it does not configure correctly. When you check the service configuration you find no values for failover method, type and retries, those values are needed for TAF to happen."

Puis, on peut démarrer la ressource service sur les deux noeuds du RAC: srvctl start service -d base -s service.

Le statut du service se vérifie de la manière suivante: srvctl config service -d base -s service -a.

On peut aussi supprimer un service inutilisé: srvctl remove service -d base -s service -f.

Après avoir arrêté un service, on peut rencontrer le problème suivant lors du redémarrage:
PRKP-1030 : Failed to start the [service] .
CRS-1006: No more members to consider
CRS-0215: Could not start resource 'ora.[database].[service].cs'.
Dans ce cas, il faut arrêter de nouveau le service sur chaque instance en utilisant la commande dbms_services.stop_service(service, instance).

Le détail de la commande srvctl est disponible dans la doc Oracle.

Pour gérer le load-balancing et le failover de type TAF côté instance, on utilise la méthode modify_services du package dbms_services:
dbms_service.modify_service(
          service_name=>'service',
          goal=>dbms_service.goal_none,
          dtp=>FALSE,
          aq_ha_notifications=>FALSE,
          failover_method=>'BASIC',
          failover_type =>'SELECT',
          failover_retries =>10,
          failover_delay =>5,
          clb_goal=>dbms_service.clb_goal_long);
Dans cet exemple, le service n'utilise pas LBA ( Load Balancing Advisory ) et le load-balancing choisi répartit les connexions entre les deux instances en fonction de leur nombre et non pas de la charge CPU. Le failover sélectionné permet de reprendre la lecture d'un curseur dans une nouvelle session en cas de défaillance d'un noeud.

La vue dba_services permet de vérifier la configuration d'un service. La commande lsnrctl services permet de contrôler l'enregistrement du service pour un listener. La vue v$session ( ou gv$session ) permet de vérifier si le failover est bien configuré pour un client. Des vues comme gv$servicemetric ou gv$servicemetric_history délivrent des informations sur l'activité d'un service.

Le failover de type TAF est lié à la couche OCI. Il est décrit dans le white paper intitulé Transparent Application Failover.

Pour tester le load-balancing du côté serveur avec sqlplus, on peut utiliser cette chaîne de connexion:

ALIAS =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = NODE1-VIP)(PORT = 1521))
    (CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = NAME)
      ))

Pour tester le failover de type TAF côté serveur avec sqlplus, on peut utiliser ces chaînes de connexion:

ALIAS =
  (DESCRIPTION =
    (LOAD_BALANCE = on)
    (ADDRESS = (PROTOCOL = TCP)(HOST = NODE1-VIP)(PORT = 1521))
    (ADDRESS = (PROTOCOL = TCP)(HOST = NODE2-VIP)(PORT = 1521))
    (CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = NAME)
       (FAILOVER_MODE = (METHOD=PRECONNECT)(BACKUP=ALIAS))
      ))
=> vérification de la prédominance du TAF côté serveur sur le TAF côté client ( vue v$session ).
ALIAS =
  (DESCRIPTION =
    (LOAD_BALANCE = on)
    (ADDRESS = (PROTOCOL = TCP)(HOST = NODE1-VIP)(PORT = 1521))
    (ADDRESS = (PROTOCOL = TCP)(HOST = NODE2-VIP)(PORT = 1521))
    (CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = NAME)
      ))

Pour simuler un failover TAF de type SELECT, on peut utiliser la commande suivante:
alter system disconnect session 'sid,serial#' post_transaction;

mardi 12 avril 2011

Wait event latch free

Avant d'exposer le problème rencontré il y a quelques jours, je vous encourage à lire ce lien qui explique ce qu'est un latch et permet de trouver l'origine de ce type de contention: http://tech.e2sn.com/oracle/troubleshooting/latch-contention-troubleshooting.

En testant le produit DB Change Manager d'Embarcadero, ce dernier permettant principalement de gérer les DDL, je me suis heurté à un fort ralentissement d'une instance Oracle 9.2. Après avoir identifié la session à l'origine du problème ( vue v$session ), j'ai commencé par regarder les wait events: select * from v$session_wait  where sid = 20. On pouvait alors observer des wait events de type latch free. En exécutant la requête suivante: select * from v$latchname  where latch# = 98, j'ai identifié le nom du latch, cache buffers chains.

Puis, j'ai utilisé les deux requêtes suivantes:
- select count(*) from v$latch_children where name = 'cache buffers chains' => 1024 ( seule la session posant problème était active ).
- select name, gets, misses, sleeps  from v$latch_children where name = 'cache buffers chains' => 0 <= misses <= quelques centaines.

Ensuite, j'ai utilisé l'outil latchprof de T.Poder  pour identifier les requêtes à l'origine de ces latchs ( exemple: @latchprof sid,name,sqlhash % 2E6982FC 100000 , addr = 2E6982FC ). Après quelques lancements, on pouvait remarquer qu'une requête ( vue v$sql ) revenait systématiquement:
SELECTB.OWNER,B.CONSTRAINT_NAME,B.CONSTRAINT_TYPE,B.OWNER,B.TABLE_NAME,A.OWNER,A.CONSTRAINT_NAME,A.OWNER,A.TABLE_NAME FROM SYS.DBA_CONSTRAINTS A,SYS.DBA_CONSTRAINTS B, SYS.DBA_OBJECTS O
WHERE A.CONSTRAINT_TYPE='R' AND A.R_CONSTRAINT_NAME=B.CONSTRAINT_NAME AND A.R_OWNER=B.OWNER  AND O.OWNER=A.OWNER AND O.OBJECT_NAME=A.TABLE_NAME AND O.OBJECT_TYPE='VIEW'  AND B.OWNER='XXXXX' AND
A.OWNER NOT IN ('SYS','SYSTEM','DBSNMP','CTXSYS','ORDSYS','OUTLN','MDSYS','OLAPSYS','ORDPLUGINS','ORDSYS','WKSYS','WMSYS','XDB') AND NOT (B.TABLE_NAME LIKE 'BIN$%')
ORDER BY B.OWNER, B.TABLE_NAME

Un rapide coup d'oeil sur la vue v$sql_plan a permis de voir une multitude de nested loop et le non calcul des statistiques sur le dictionnaire.

Après avoir consulté la note 245051.1 sur le support d'Oracle ( anciennement Metalink ), j'ai lancé les statistiques sur le dictionnaire: execute dbms_stats.gather_schema_stats('SYS', cascade=>TRUE); pour résoudre le problème initial.

Pour aller plus loin: Latch, mutex and beyond

jeudi 6 janvier 2011

Event trace 10053

L'event trace 10053 permet de comprendre comment l'optimiseur a créé le plan d'exécution d'une requête. A chaque noeud du plan, on peut voir comment l'optimiseur a pris une décision, par exemple le choix d'un index range scan plutôt qu'un table full scan.

Pour étudier plus facilement ce type de trace, un viewer compatible 10g et 11g est disponible à cette adresse:
http://jonathanlewis.wordpress.com/2010/04/30/10053-viewer/.

Pour tenter de déchiffrer l'event trace 10053, vous pouvez consulter:
- le chapitre 14 du livre Cost Based Oracle Fundamentals écrit par J.Lewis;
- la note Metalink 338137.1: Analyzing 10053 Trace Files;
- la présentation de W.Breitling “A Look under the Hood of CBO – the 10053 Event”.

En 11g, on peut capturer une trace 10053 pour une requête appartenant à un package PL/SQL.

mardi 23 novembre 2010

Prototype d'un pool de connexion au niveau du client

/* Compilation: make -f demo_proc.mk EXE=test_rac_recette_con_cache OBJS=test_rac_recette_con_cache.o build PROCFLAGS="sqlcheck=full userid=xxx/xxx@xxx code=ansi_c define=V8" */

#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
#include unistd.h

#include sys/time.h
#include sys/types.h
#include sys/wait.h
#include sys/ipc.h
#include sys/shm.h
#include sys/sem.h

#include sqlca.h
#include sqlcpr.h

#define UNAME_LEN 30
#define PWD_LEN 30
#define CONNECT_STRING_LEN 128
#define NB_CONNEXION 50

typedef int SEMAPHORE;

/* Contexte de connexion */
EXEC SQL BEGIN DECLARE SECTION;
typedef struct {
    sql_context sql_cntxt[NB_CONNEXION];
    short h_dspnb[NB_CONNEXION];
} sCntxtConnexion;

sql_context sql_cntxt;

VARCHAR username[UNAME_LEN];
VARCHAR password[PWD_LEN];
VARCHAR connect_string[CONNECT_STRING_LEN];
EXEC SQL END DECLARE SECTION;

sCntxtConnexion *p_cntx;

SEMAPHORE sem;

void sql_error();
int trait_rqt(int i);

int detruire_sem(SEMAPHORE sem);
int changer_sem(SEMAPHORE sem, int val);
SEMAPHORE creer_sem(key_t key);
void P(SEMAPHORE sem);
void V(SEMAPHORE sem);

int main(int argc, char* argv[]) {
int i,j;
int nb_process;
pid_t pid;
int status;

key_t cle, cle_sem;
int id;

int cpt;

if (argc != 5) { printf("test_rac_recette_con   ... \n"); return 0; }

printf("Pere: user %s mot de passe %s alias %s \n", argv[1], argv[2], argv[3]);

strcpy((char *) username.arr, argv[1]);
username.len = strlen((char *) username.arr);
strcpy((char *) password.arr, argv[2]);
password.len = strlen((char *) password.arr);
strcpy((char *) connect_string.arr, argv[3]);
connect_string.len = strlen((char *) connect_string.arr);

EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--\n");

nb_process = atoi(argv[4]);
j = 1;

EXEC SQL ENABLE THREADS;

/* Création du segment de mémoire partagée */
cle = ftok(getenv("HOME"), 'A');
if (cle == -1) {
    printf("Pere: pb ftok \n");
    return -1;
}
   
/* 0666: droits */
/* ipcrm -m */
id = shmget(cle, sizeof(sCntxtConnexion), IPC_CREAT | IPC_EXCL | 0666);
if (id == -1) {
    switch (errno) {
       case EEXIST:
      printf("Pere: le segment existe deja \n");
       default:
          printf("Pere: shmget \n");
          return -1;
    }
}

p_cntx = (sCntxtConnexion *) shmat(id, NULL, SHM_R | SHM_W);
if (p_cntx == NULL) {
    printf("Pere: shmat \n");
    return -1;
}

/* Création du pool */
for i in 0..NB_CONNEXION
-1
     EXEC SQL CONTEXT ALLOCATE :sql_cntxt;
     EXEC SQL CONTEXT USE :sql_cntxt;
     EXEC SQL CONNECT :username IDENTIFIED BY :password USING :connect_string;
     p_cntx->sql_cntxt[i] = sql_cntxt;
     p_cntx->h_dspnb[i]=0;

system("ipcs -m");

/* Test du pool sans fork */
sql_cntxt = p_cntx->sql_cntxt[0];
EXEC SQL CONTEXT USE :sql_cntxt;
EXEC SQL select count(*) into :cpt from alphacompactee;
printf("Pere: cpt: %d \n", cpt);

/* Création d'un sémaphore */
cle_sem = ftok(getenv("HOME"), 'B');
if (cle_sem == -1) {
    printf("Pere: pb ftok \n");
    return -1;
}

sem = creer_sem(cle_sem);

printf("Semaphore: \n");
system("ipcs -s | grep snotter");


/* Fork des processus fils */
for(i = 0; i < nb_process; i++) {
    pid = fork();
    if (pid == 0) {
        /* printf("Fils: %d \n", i); */
        trait_rqt(i);
        return 0;
    }
}

for(i = 0; i < nb_process; i++) {
    wait(&status);
}

printf("Pere: fin des fils ... \n");


/* Libération du pool */
for i in 0..NB_CONNEXION-1
     sql_cntxt = p_cntx->sql_cntxt[i];
     EXEC SQL CONTEXT USE :sql_cntxt;
     /*EXEC SQL COMMIT RELEASE;*/
     EXEC SQL CONTEXT FREE :sql_cntxt;

/* Suppression du segment de mémoire partagée */
/* (char *) */
if (shmdt((void *) p_cntx) == -1) {
    printf("Pere: shmdt \n");
    return -1;
}

if (shmctl(id, IPC_RMID, NULL) == -1) {
    printf("Pere: shmctl(remove) \n");
    return -1;
}


/* Suppression du sémaphore */
if (detruire_sem(sem) == -1) {
    printf("Pere: detruire_sem \n");
    return -1;
};

return 0;

}

int trait_rqt(int i) {
int j;
hrtime_t point1, point2;
char tcHeure[20];
struct timeval tv;
struct tm *tm;

int cpt;

gettimeofday(&tv);
tm=localtime(&tv.tv_sec);
memset(tcHeure, '\0', sizeof(tcHeure));
sprintf(tcHeure, " %d:%02d:%02d %03d ", tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec/1000);

point1 = gethrtime();


/* Attente sur le sémaphore */
printf("ATT Fils %d wait \n", i);
P(sem);


/* Choix d'une connexion disponible */

for j in 0..NB_CONNEXION-1
  if (p_cntx->h_dspnb[j]==0) {
      p_cntx->h_dspnb[j]=1;
      break;
  }
  else {
      printf("Fils %d Conn %d deja prise \n", i, j); 
  }
  

printf("Fils %d Conn %d prise \n", i, j);

/* Libération du sémaphore */
V(sem);

point2 = gethrtime();
printf("Fils Conn %d; %s; %lld ; %lld ; %lld \n", i, tcHeure, (point2 - point1)/1000000, point1, point2);

gettimeofday(&tv);
tm=localtime(&tv.tv_sec);
memset(tcHeure, '\0', sizeof(tcHeure));
sprintf(tcHeure, " %d:%02d:%02d %03d ", tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec/1000);

point1 = gethrtime();

sql_cntxt = p_cntx->sql_cntxt[j];
EXEC SQL CONTEXT USE :sql_cntxt;

/* Exécution d'une requête */
EXEC SQL select count(*) into :cpt from alphacompactee;

point2 = gethrtime();

printf("Fils Req %d; %s; %lld ; %lld ; %lld \n", i, tcHeure, (point2 - point1)/1000000, point1, point2);

/* Attente sur le sémaphore */

printf("ATT Fils %d wait \n", i);
P(sem);
 

/* Libération de la connexion */
p_cntx->h_dspnb[j]=0;
 

/* Libération du sémaphore */
printf("Fils %d Conn %d liberee\n", i, j);
V(sem);

return 0;
}
 
void sql_error(char *msg) {
char err_msg[128];
int buf_len, msg_len;

EXEC SQL WHENEVER SQLERROR CONTINUE;

printf("\n%s\n", msg);
buf_len = sizeof (err_msg);
 sqlglm(err_msg, (unsigned int *)&buf_len,(unsigned int *)&msg_len);
if (msg_len > buf_len)
    msg_len = buf_len;
printf("%.*s\n", msg_len, err_msg);
EXEC SQL ROLLBACK RELEASE;
exit(1);

}

int detruire_sem(SEMAPHORE sem) {
if (semctl(sem, 0, IPC_RMID, 0) != 0) {
    printf("detruire_sem \n");
    return -1;
}
return 0;
}

int changer_sem(SEMAPHORE sem, int val) {
struct sembuf sb[1];

sb[0].sem_num = 0;
sb[0].sem_op = val;
sb[0].sem_flg = 0;
if (semop(sem, sb, 1) != 0) {
    printf("changer_sem \n");
    return -1;
}
return 0;
}

SEMAPHORE creer_sem(key_t key) {
SEMAPHORE sem;
int r;
union semun {
int val;
struct semid_ds *buf;
ushort *array;
} s_ctl;

sem = semget(key, 1, IPC_CREAT | 0666);
if (sem < 0) {            
    printf("creer_sem \n");
    exit(EXIT_FAILURE);
}

s_ctl.val =  1;  
r = semctl (sem, 0, SETVAL, s_ctl );
if (r < 0) {
    printf("initialisation sémaphore \n");
    exit(EXIT_FAILURE);
}
return sem;
}

void P(SEMAPHORE sem) {
changer_sem(sem, -1);
}

void V(SEMAPHORE sem) {
changer_sem(sem, 1);
}

mardi 5 octobre 2010

Problème de connexion

Soit un client PRO*C qui a épisodiquement des temps de connexion à une instance Oracle de l'ordre de quelques centaines de ms, voire plusieurs secondes.

La première étape consiste à isoler le problème. Dans le cas présent, la latence est observée au moment de l'exécution de l'instruction CONNECT.

Pour améliorer les temps de connexion, deux paramètres peuvent être intéressants à modifier:
- SDU ( http://www.sun.com/blueprints/1002/817-0370-10.pdf );
- tcp.nodelay ( désactivation de l'algorithme de Nagle lié à la couche TCP ).

Pour aller plus loin, consulter le guide d'administration sur la couche sqlnet.

Si le problème n'est pas résolu, on peut alors poser  les traces sqlnet sur le client et ensuite éventuellement sur le listener, voire le dispatcher en cas d'utilisation du mode shared server. Pour les traces sqlnet sur le client, on peut utiliser une configuration spécifique ( sqlnet.ora + tnsnames.ora ) via la variable d'environnement TNS_ADMIN. Le paramétrage des traces du listener s'effectue dans le fichier listener.ora. Puis, on lance le listener pour activer le mode trace ( exemple: set trc_level support ). L'étude du dispatcher se fait via l'event 10248.

Dans le cas présent, aucune erreur ( nserror ) n'a été détectée.  Une analyse réseau ( tcpdump sur le client et un noeud du RAC, utilisation de sondes ) a ensuite été menée, ce qui a permis de localiser le problème au niveau de la machine cliente: des paquets de type ACK retardé ou donnée ne sont pas émis immédiatement. Il est à noter que les adresses virtuelles des noeuds du RAC sont résolues directement dans le fichier host. 


En changeant de client Oracle ( 9i -> 10g ), le problème a persisté, d'où une mise en cause de la couche système, en particulier au niveau de son paramétrage TCP. Une analyse via l'outil truss n'a pas permis d'obtenir de résultats probants. L'outil Dtrace ne peut pas être utilisé, la machine cliente étant en Solaris 8.

Quelques modifications ont été effectuées au niveau du paramétrage TCP du système, sans grand succès ( exemple: tcp_time_wait_interval ). Pour dédouaner complètement le client Oracle, un client/serveur de type écho à base de socket TCP a été développé ( serveur sur un noeud du RAC ) en se basant sur les sources disponibles à cette adresse : http://cs.baylor.edu/~donahoo/practical/CSockets/textcode.html. Une fois ce dernier installé, on a pu observer que la durée de traitement moyenne de la commande recv du client était de l'ordre de quelques dizaines de micros pour un échange de 8 ko. Cependant, de temps en temps, elle peut excéder la dizaine de ms.

Suite à cette constatation, le problème est étudié par l'équipe système, qui ne trouve pas de solution.  

Lors de ces tests, la charge transactionnelle était faible ( 4 transactions par seconde ), d'où une solution possible. Si la charge augmente ou pour éviter ce type de problème sur une machine mal configurée, on peut mettre en place un pool de sockets dédiées aux connexions Oracle. Le principe est le suivant:
- Lors du démarrage du serveur d'application, on crée les sockets entre le client Oracle et l'instance, puis on met en mémoire les pointeurs de connexion dans un segment de mémoire partagée, ce dernier étant protégé par un sémaphore;
- Lors du traitement d'une transaction, le serveur crée un processus fils qui va rechercher dans le segment de mémoire partagée une connexion disponible, si le sémaphore le permet ( unicité d'accès à la ressource ). Une fois une connexion sélectionnée ( temps d'exécution inférieur à 1 ms ), la requête est exécutée et les données transmises au client. La déconnexion, quant à elle, se limite à rendre de nouveau disponible la connexion utilisée.
- Lors de l'arrêt du serveur, les sockets sont closes et les ressources système libérées.

De cette manière, la discontinuité de performance se réduit à l'échange des données entre le client et l'instance après l'exécution d'une requête. Les temps de connexion/déconnexion, quant à eux, sont considérés comme négligeables.