viernes, 13 de noviembre de 2009

Oracle 11g Result Caching and PHP OCI8

Oracle Database 11g introduced "server-side"and "client-side" result caches.

The database cache is enabled with the RESULT_CACHE_MODE database parameter, which has several modes. With the default mode, queries for which you want results to be cached need a hint added:

$s = oci_parse($c, "select /*+ result_cache */ * from employee"); 

No PHP changes are required.

The client result cache (i.e. in PHP OCI8) is ideal for small queries from infrequently modified tables, such as lookup tables. It can reduce PHP statement processing time and significantly reduce database CPU usage, allowing any database to handle more PHP processes and users. The client-side cache is per PHP process.

A key feature of the cache is that Oracle automatically handles cache entry invalidation when a database change invalidates the stored results. Oracle will check the cache entries each time any round trip to the database occurs. If no round trip has happened with a configurable "lag" time, the cache is assumed stale.

The Oracle® Call Interface Programmer's Guide, 11g Release 1 (11.1) contains the best description of the feature and has more about when to use it and how to manage it.

To demonstrate client caching in PHP, the database parameter CLIENT_RESULT_CACHE_SIZE can be set to a non zero value and the Oracle 11g database restarted:

$ sqlplus / as sysdba
SQL> alter system set client_result_cache_size=64M scope=spfile;
SQL> startup force

In PHP, the key to using the client-cache is to pass OCI_DEFAULT to oci_execute() as shown in crc.php. The query hint is also needed:


$c = oci_pconnect('hr', 'hrpwd', '//localhost/orcl');

for ($i = 0; $i < 1000; $i++) {
$s = oci_parse($c,
"select /*+ result_cache */ * from employees where rownum < 2");
oci_execute($s, OCI_DEFAULT);
oci_fetch_all($s, $res);
}

?>

Before executing the PHP script, run this query in the SQL*Plus session:

SQL> select parse_calls, executions, sql_text
2 from v$sql
3 where sql_text like '%employees%';

PARSE_CALLS EXECUTIONS SQL_TEXT
----------- ---------- ---------------------------------------------------
1 1 select parse_calls, executions, sql_text from v$sql
where sql_text like '%employees%'

This shows the database being accessed when the query is executed. Initially it shows just the monitoring query itself.

In another terminal window, run crc.php from the command line or run it in a browser – it doesn't display any results.

$ php crc.php 

Re-running the monitoring query shows that during the 1000 loop iterations, the database executed the PHP query just twice, once for the initial execution and the second time by a subsequent cache validation check:

PARSE_CALLS EXECUTIONS SQL_TEXT
----------- ---------- ---------------------------------------------------
2 2 select /*+ result_cache */ * from employees where
rownum < 2
2 2 select parse_calls, executions, sql_text from v$sql
where sql_text like '%employees%'

So, for 998 executions, the database wasn't involved. Instead, the client result cache was used for the query results.

Now edit crc.php and remove OCI_DEFAULT from the execute call:

    oci_execute($s); 

Re-run the script:

$ php crc.php 

The monitoring query now shows the modified query was executed once per loop iteration:

PARSE_CALLS EXECUTIONS SQL_TEXT
----------- ---------- ---------------------------------------------------
4 1002 select /*+ result_cache */ * from employees where
rownum < 2
3 3 select parse_calls, executions, sql_text from v$sql
where sql_text like '%employees%'

This shows that without OCI_DEFAULT the client query result cache is not used and each iteration has to be processed in the database. The reason is that the implicit commit is treated as an event that possibly invalidates the cache. (I was testing with 11.1.0.6 I imagine this is something that could be considered for optimization in a future release.)

A dedicated view CLIENT_RESULT_CACHE_STATS$ is periodically updated with statistics on client caching. For short tests like this example where the process quickly runs and terminates, it may not give meaningful results and V$SQL can be more useful.

martes, 8 de septiembre de 2009

Cómo Instalar Certificado de Seguridad en Linux

#mkdir $APACHE_HOME/conf/ssl.key

#cd $APACHE_HOME/conf/ssl.key

#openssl genrsa -des3 -out archivo.key 1024

#openssl req -new -key archivo.key -out archivo.csr

Frase de Desafío: tufrasededesafio



**************************Datos de creación del certificado:***********************

Dominio: www.tudominio.com
Empresa u Organización: NombredetuOrganizacion
Unidad de Negocio: DepartamentooDireccion
Localidad: tulocalidad
Provincia: tuprovincia
País: MX

Vigencia: 1 año
Contacto Técnico: contactotecnico
Contacto Corporativo: contactocorporativo
Contacto de Facturación: contactodefacturacion
Orden de Compra: 123456
PIN para soporte: e946813e1ad0703ee62c16d6ec6abc2b

*************************************************************************************


Create a Self-Signed Certificate

#openssl req -new -key archivo.key -x509 -out archivo.crt

Configurar las rutas de los archivos recién creados en el ssl.conf

Configurar el host virtual existente o agregar uno nuevo para hacer correr el https en el ssl.conf

Iniciar apache con "apachectl startssl"

Para cambiar un dominio solo es necesario modificar el servername en el ssl.conf, no es necesario editar el httpd.conf

viernes, 15 de mayo de 2009

Instalar F-Spot en Fedora 10 desde el source

Instalé recientemente f-spot desde el repositorio de Fedora 10 x86_64 pero la aplicación era algo inestable y fallaba con frecuencia, así que decidí instalarla desde el source para ver si su funcionamiento era mejor.

Pueden descargar el software desde esta liga.

Para instalar f-spot 0.5.0.3 desde el fuente tienes que instalar algunas dependencias para la compilación.

Estos son los paquetes necesarios:

yum install mono-devel gtk-sharp2-devel gnome-sharp-devel libexif-devel libgphoto2-devel lcms gtk-sharp2-gapi ndesk-dbus-devel ndesk-dbus-glib-devel

Una vez instalados estos paquetes con sus dependencias, descomprimimos el paquete y comenzamos con la compilación.

[root@ecolinux Descargas]# tar -jxvf f-spot-0.5.0.3.tar.bz2

[root@ecolinux Descargas]# cd f-spot-0.5.0.3
[root@ecolinux f-spot-0.5.0.3]# ./configure
[root@ecolinux f-spot-0.5.0.3]# make
[root@ecolinux f-spot-0.5.0.3]# make install

Una vez hecho esto probamos que nuestra instalación haya sido exitosa:

[root@ecolinux f-spot-0.5.0.3]# f-spot

Es probable que te marque algunos errores durante la compilación o durante el make y es debido a que no encuentre algunas librerías, para lo cual es necesario exportar la siguiente variable:

[root@ecolinux f-spot-0.5.0.3]# export MONO_PATH=/usr/lib64/mono
[root@ecolinux f-spot-0.5.0.3]# ./configure
[root@ecolinux f-spot-0.5.0.3]# make
[root@ecolinux f-spot-0.5.0.3]# make install
[root@ecolinux f-spot-0.5.0.3]# f-spot

Y con esto quedaría solucionado el problema de las librerías y f-spot debería funcionar perfectamente.

Error when starting OpenOffice on Fedora 10

Decidí instalar openoffice por separado en lugar de hacerlo desde el dvd de instalación de Fedora 10, así que entré al sitio y descargue el tar.gz. Esto porque necesitaba instalar el jdk de Sun y no el que viene por defecto con Fedora.

Hoy me decidí a instalar openoffice y los rpm's funcionaron bien pero al momento de iniciarlo desde el menú de gnome, simplemente no arrancaba.

Al leer el archivo README de la carpeta de instalación venía una variable que es necesario dejar en blanco.

Se agrega "unset SESSION_MANAGER" al archivo soffice que se encuentra dentro de la carpeta de instalación de openoffice, en mi caso "/opt/openoffice.org3/program/soffice".

Esto no solucionó el problema y había que hacer algo adicional.

Lo corrí desde la terminal y me dió el siguiente error:

[root@ecolinux readmes]# openoffice.org3
/opt/openoffice.org3/program/soffice.bin: error while loading shared libraries: libuno_sal.so.3: cannot open shared object file: No such file or directory


El problema está en que openoffice no encuentra algunas librerías y hay que hacer una liga a la ubicación donde se encuentran dichos archivos:

[root@ecolinux readmes]# cd /opt/openoffice.org/basis3.0
[root@ecolinux basis3.0]# mv ure-link ure-link.backup
[root@ecolinux basis3.0]# ln -s /usr/lib64/openoffice.org/ure ure-link

En mi caso es /usr/lib64 porque tengo instalado Fedora 10 de 64 bits y con eso se solucionó el problema.

viernes, 8 de mayo de 2009

Descargar Oracle con wget

En varias ocasiones he necesitado descargar archivos del sitio de Oracle, ya sea para bajar nuevas versiones de la base de datos, application server, sqldeveloper, etc.

El caso es que siempre los había descargado desde el explorador Web y nunca había tenido problema. Hasta que esta semana el archivo de 1.8GB de la versión 11g de la base de datos no alcanzaba a bajar completo y tronaba la conexión del explorador teniendo que reiniciar la descarga en varias ocasiones.

Por eso decidí utilizar wget para la descarga de archivos desde el sitio de Oracle pero me encontré con el siguiente problema:

# wget -c http://download-llnw.oracle.com:80/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip

--2009-05-08 10:20:43-- http://download-llnw.oracle.com/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip
Resolviendo download-llnw.oracle.com... 208.111.133.53, 208.111.133.54
Connecting to download-llnw.oracle.com|208.111.133.53|:80... conectado.
Petición HTTP enviada, esperando respuesta... 302 Found
Localización: https://profile.oracle.com/jsp/realms/otnLogin.jsp?remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http%3a%2f%2fdownload-llnw.oracle.com%3a80%2fotn%2flinux%2foracle11g%2flinux.x64_11gR1_database_1013.zip [siguiendo]
Redirecciona a otra URL de autentificación...
--2009-05-08 10:20:44-- https://profile.oracle.com/jsp/realms/otnLogin.jsp?remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http%3a%2f%2fdownload-llnw.oracle.com%3a80%2fotn%2flinux%2foracle11g%2flinux.x64_11gR1_database_1013.zip
Resolviendo profile.oracle.com... 141.146.8.116
Connecting to profile.oracle.com|141.146.8.116|:443... conectado.
Petición HTTP enviada, esperando respuesta... 200 OK
Longitud: 4082 (4,0K) [text/html]
Saving to: `otnLogin.jsp?remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http:%2F%2Fdownload-llnw.oracle.com:80%2Fotn%2Flinux%2Foracle11g%2Flinux.x64_11gR1_database_1013.zip'

wget está guardando la página de autentificación...
100%[======================================>] 4.082 10,6K/s in 0,4s

2009-05-08 10:20:45 (10,6 KB/s) - `otnLogin.jsp?remoteIp=148.235.72.10&globalId=&redirectUrl=http:%2F%2Fdownload-llnw.oracle.com:80%2Fotn%2Flinux%2Foracle11g%2Flinux.x64_11gR1_database_1013.zip' saved [4082/4082]

En esa página de autentificación tenemos que poner nuestro usuario y contraseña de Oracle para poder descargar archivos del sitio.

Intentamos entonces con las opciones --http-user y --http-password de wget:

# wget -c http://download-llnw.oracle.com:80/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip --http-user=xxx --http-password=xxx

Obtenemos el mismo resultado que en el intento anterior:

--2009-05-08 10:29:05-- http://download-llnw.oracle.com/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip
Resolviendo download-llnw.oracle.com... 208.111.133.52, 208.111.133.54
Connecting to download-llnw.oracle.com|208.111.133.52|:80... conectado.
Petición HTTP enviada, esperando respuesta... 302 Found
Localización: https://profile.oracle.com/jsp/realms/otnLogin.jsp?remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http%3a%2f%2fdownload-llnw.oracle.com%3a80%2fotn%2flinux%2foracle11g%2flinux.x64_11gR1_database_1013.zip [siguiendo]
--2009-05-08 10:29:05-- https://profile.oracle.com/jsp/realms/otnLogin.jsp?remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http%3a%2f%2fdownload-llnw.oracle.com%3a80%2fotn%2flinux%2foracle11g%2flinux.x64_11gR1_database_1013.zip
Resolviendo profile.oracle.com... 141.146.8.116
Connecting to profile.oracle.com|141.146.8.116|:443... conectado.
Petición HTTP enviada, esperando respuesta... 200 OK
Longitud: 4082 (4,0K) [text/html]
Saving to: `otnLogin.jsp?remoteIp=148.235.72.10&globalId=&redirectUrl=http:%2F%2Fdownload-llnw.oracle.com:80%2Fotn%2Flinux%2Foracle11g%2Flinux.x64_11gR1_database_1013.zip'

50% [=============================> ] 4.082 11,5K/s in 0,3s

2009-05-08 10:29:06 (11,5 KB/s) - `otnLogin.jsp?remoteIp=148.235.72.10&globalId=&redirectUrl=http:%2F%2Fdownload-llnw.oracle.com:80%2Fotn%2Flinux%2Foracle11g%2Flinux.x64_11gR1_database_1013.zip' saved [4082/4082]

Se vuelve a guardar la forma de autentificación ya que el enlace de descarga directo no funciona para wget sin antes haber hecho la autentificación.

Tampoco me funcionó autentificarme en el explorador Web y después intentar con wget pero leí que a algunos les dió resultado.

La forma que yo encontré para hacerlo fue la siguiente:

Primero obtienes la URL de descarga de uno los intentos fallidos:

Localización:https://profile.oracle.com/jsp/realms/otnLogin.jsp?remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http%3a%2f%2fdownload-llnw.oracle.com%3a80%2fotn%2flinux%2foracle11g%2flinux.x64_11gR1_database_1013.zip

La parte en color azul es la URL de la cual wget va a intentar la descarga y ésta nos va a llevar a la forma de autentificación. Aquí es donde entra la parte en color rojo y la opción --post-data de wget y se usa como sigue:

$ wget -c --no-check-certificate --post-data="remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http://download-llnw.oracle.com:80/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip&username=xxx&password=xxx" https://profile.oracle.com/jsp/reg/loginHandler.jsp?

*Se sustituyen las x con la IP y con el usuario y contraseña del sitio de Oracle. A la parte que dice "redirectURL" tenemos que sustituirle los caracteres %2f por /.

La opción:
-c : se usa para poder continuar con la descarga en otro momento haciendo resumen.
--no-check-certificate : para que no verifique el certificado de seguridad del sitio.
--post-data : para enviar ciertos valores a la URL tales como usuario y contraseña, URL de descarga del archivo.

Una vez hecho esto el archivo comienza a descargarse sin problema:

$ wget -c --no-check-certificate --post-data="remoteIp=148.xxx.xxx.xxx&globalId=&redirectUrl=http://download-llnw.oracle.com:80/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip&username=xxx&password=xxx" https://profile.oracle.com/jsp/reg/loginHandler.jsp?

--2009-05-08 10:47:03-- https://profile.oracle.com/jsp/reg/loginHandler.jsp?
Resolviendo profile.oracle.com... 141.146.8.116
Connecting to profile.oracle.com|141.146.8.116|:443... conectado.
Petición HTTP enviada, esperando respuesta... 302 Moved Temporarily
Localización: http://download-llnw.oracle.com:80/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip [siguiendo]
--2009-05-08 10:47:05-- http://download-llnw.oracle.com/otn/linux/oracle11g/linux.x64_11gR1_database_1013.zip
Connecting to 192.168.254.4:3128... conectado.
Petición Proxy enviada, esperando respuesta... 206 Partial Content
Longitud: 1913850002 (1.8G), 1192940242 (1.1G) remaining [application/zip]
Saving to: `linux.x64_11gR1_database_1013.zip'

44% [++++++++++++++++++++++===> ] 845,732,744 228K/s eta 77m 27s

Espero les sea de utilidad.

miércoles, 6 de mayo de 2009

How to purge Oracle Recycle Bin

Recientemente se presentó un problema en una de nuestras bases de datos (ver. 10.2.0.3). El tablespace afectado mostraba un espacio utilizado negativo y esto afectaba a una de nuestras aplicaciones al no poder recuperar archivos adjuntos de la misma base para mostrarlos en pantalla.

Al perecer el problema se deriva de un bug de esta versión de la base de datos pero que se soluciona haciendo el upgrade a la versión 10.2.0.4. 

La causa principal es la saturación de la papelera de reciclaje de Oracle y la solución aplicada fue únicamente vaciar la misma de la siguiente manera:

Primero se revisa que la papelera tenga contenido: 

SQL> select count(1) from dba_recyclebin; 

  COUNT(1)

----------

       603

 

Se elimina el contenido de la papelera:

SQL> purge dba_recyclebin;

 Papelera de reciclaje de DBA depurada.


Con esto el problema quedó resuelto.

viernes, 6 de marzo de 2009

How To Enable Graphical Boot with Plymouth [Fedora Tip]

Thanks to SendDerek for this guide.

Type the following into the terminal to configure grub:

su -
gedit /boot/grub/grub.conf

Then, within the grub.conf file, add the bold text below (this value will also depend upon which screen resolution you’d like. To get this value, visit this page):

title Fedora (2.6.27.5-117.fc10.i686)
root (hd0,9)
kernel /boot/vmlinuz-2.6.27.5-117.fc10.i686 ro root=UUID=a61c8338-e373-4389-ae00-32942185f7c2 rhgb quiet vga=792
initrd /boot/initrd-2.6.27.5-117.fc10.i686.img

If you’d like to add more themes, you’ll need to first download and install them and then set them as default. To do this, type the following into the terminal, replacing what’s within the {} with the loader of your choice):

su -
yum install plymouth-plugin-{fade-in,pulser,spinfinity}

Then, set your theme as default:

su -
plymouth-set-default-plugin pluginname
/usr/libexec/plymouth/plymouth-update-initrd

domingo, 1 de marzo de 2009

Broadcom Wireless on Fedora 10

RPMs for the Broadcom 802.11 STA Wireless Driver are now available from the rpmfusion.org repos for Fedora 8, 9 and 10.

This is an official-release of Broadcom's IEEE 802.11a/b/g/n hybrid Linux device driver for use with Broadcom's BCM4311-, BCM4312-, BCM4321-, and BCM4322-based hardware. This driver also supports the incorrectly identified BCM4328 chipset which is actually a BCM4321 or BCM4322 chipset.

Previously I explained how to build the Broadcom STA driver from source but now the installation and updates can all be taken care of using yum and the rpmfusion non-free repository. Just follow these two simple steps:

1) Enable the rpmfusion non-free repository.

The broadcom-wl and wl-kmod RPMs that we need are in the rpmfusion non-free repository which also requires the rpmfusion free repository. To enable these repos in Fedora simply do this:

su -c 'rpm -Uvh http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm\
http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-stable.noarch.rpm'

2) Update and install the driver package

Now that you have the appropriate repository enabled, to install the driver package we first ensure that we have the latest updates and then install the "broadcom-wl" package which will bring with it the required "kmod-wl" package:

su -
yum update
yum install broadcom-wl

3) Make a couple of adjustments

At this stage you should reboot and provided you have Network Manager running (default setting for F10) it should detect your Broadcom wireless device and you should be able to connect to your wireless network.

If you're having problems then it is likely some conflict between Network Manager and the network service. To ensure that Network Manager can use the wireless go to System > Administration > Network and select the wireless device (if it's not there then this doesn't apply to you). Edit the device and check "Controlled by Network Manager" and "Activate device when computer starts". Close Network Configuration, save changes and then reboot.

You should also read the license and readme which are located in /usr/share/doc/broadcom-wl-5.10.27.6/

4) Enjoy your wireless!

That's all there is to it. At this stage you may need to reboot (if you didn't already) in order to enable the new driver and any new kernel that was installed during the update.

When future kernels are released a simple "yum update" command should install the new kernel and also pull in the updated Broadcom driver for that new kernel.

Share and Enjoy!

Cómo Instalar drivers de nVidia en Fedora 10

Installation using RPMFusion:

First Install the repository configuration files for YUM. Run the following (enter 'root' password when prompted):

[mirandam@charon ~]$ su -c 'rpm -ivh http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm \
http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-stable.noarch.rpm'

[mirandam@charon ~]$ su -c 'rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-*'

Second Select the proper KMOD (Fedora Kernel Module) which matches the driver you need. This MUST match the specific driver for the PCI ID you determined earlier.

KMOD's for Fedora 10:

Latest VersionLegacy 173.14.xxLegacy version 96.43.xx
kmod-nvidiakmod-nvidia-173xxkmod-nvidia-96xx

KMOD's for Fedora 9 (Note the specific names between the legacy and newest drivers):

Latest VersionLegacy 173.14.xxLegacy 96.43.xxLegacy 71.86.xx
kmod-nvidia-newestkmod-nvidiakmod-nvidia-96xxkmod-nvidia-legacy

Third Install the proper KMOD using the yum command (enter 'root' password when prompted):

[mirandam@charon ~]$ su -c 'yum install kmod-nvidia'

NOTE: YUM will automatically determine any extra packages needed for the installation. If a new kernel is needed, then a reboot is required before the Nvidia driver will load.

Note: Advanced users who use 'XEN' or 'PAE' kernels, may have support for the nvidia driver. To see other KMOD's for your kernel search through yum:

[mirandam@charon ~]$ yum list *kmod-nvidia\*


Instalación utilizando el empaquetador de nVidia:

En esta pequeña guía aprenderemos a instalar los drivers para las tarjetas aceleradoras nVidia
en Fedora manualmente
, y además la instalación y activación de efectos de escritorio
(Compiz Fusion).

Lo primero es descargar el driver de la página oficial de descargas de nVidia; se tratra de un archivo binario con extensión .run, que contiene el instalador. En el momento de instalar el driver, es necesario recompilar algunas cosas en el kernel, pero resulta que Fedora no viene con paquetes de desarrollo por defecto, por lo que mientras descargamos el driver, vamos instalando algunos paquetes necesarios con el siguiente comando:

$ su
# yum install gcc make binutils kernel-devel

Una vez instalados, y con el archivo .run descargado, podemos iniciar la instalación del driver. Puesto que el instalador de driver requiere que el servidor de las X esté detenido, presionamos la combinación de teclas Ctrl+Alt+F2 para iniciar una sesión de consola fuera del entorno gráfico, nos logueamos con el usuario root, y ejecutamos el comando:

# init 3

Esto hará que el servidor xorg se detenga. Ahora, vamos a hacer un backup de nuestro archivo xorg.conf, por si algo falla (es una buena costumbre), ejecutando el siguiente comando:

# cp -v /etc/X11/xorg.conf{,.bak}

Es hora de iniciar el instalador del driver con el siguiente comando:

# sh /ruta/del/driver/NVIDIA-Linux-x86-XX.XX-pkg1.run

Nos aparece un cuadro de diálogo que nos pide que aceptemos la licencia de uso; le damos Accept. Luego nos pregunta si deseamos buscar una versión precompilada de nuestro kernel, a lo cual le damos No. Después de ello, el instalador intentará recompilar el kernel por si mismo.

Una vez el instalador ha hecho su trabajo, nos pregunta si deseamos ejecutar el programa nvidia-xconfig, que modificará el archivo xorg.conf con los valores adecuados para activar la aceleración gráfica. Le decimos que Si, y listo. Ahora para entrar de nuevo en una sesión gráfica ejecutamos el comando:

# init 5

Instalación del Compiz Fusion en Fedora

Fedora ya viene con parte de Compiz Fusion, pero no incluye paquetes importantes como el ccsm o el fusion-icon. Por tanto, y para estar seguros, ejecutamos el siguiente comando:

$ su
# yum install compiz compiz-gnome compiz-fusion \
compiz-fusion-gnome compiz-fusion-extras-gnome \
compiz-fusion-extras compiz-manager gnome-compiz-manager \
compizconfig-python ccsm emerald emerald-themes fusion-icon
Ten en cuenta que en mi caso uso Gnome, por lo que debes modificar un poco el comando si usas KDE.

Una vez instalados estos paquetes, ya puedes activar los efectos de escritorio ejecutando el comando fusion-icon. Si deseas que los efectos se activen cada vez que inicies sesión, abres la utilidad Sesiones--> haces clic en Añadir, y pones el nombre y el comando a ejecutar.

Clic en Guardar, y listo.

Para cambiar las opciones de los efectos y personalizarlos, vas a Sistema -> Preferencias -> Visualización y comportamiento -> Administrador de Opciones de CompizConfig.

Es todo! Ya puedes disfrutar de los increíbles efectos de visuales que nos ofrece Compiz Fusion, y como no, de algunos juegos que requieren aceleración gráfica.

miércoles, 25 de febrero de 2009

Transparent Application Failover - Oracle RAC

Ahora toca el turno a las pruebas de alta disponibilidad que el ambiente Real Application Clucter nos ofrece, el cual es otro de los puntos clave que debemos entender para llevar a cabo una implementación exitosa de cualquier aplicación conectada a una base de datos Oracle en ambiente RAC.
Antecedentes
===========
Iniciaré con una breve explicación de que es Transparent Application Failover (TAF)...


TAF es una característica que permite a los cliente de oracle reconectarse a otra instancia en caso de que ocurriera una falla en la instancia a la cual se encontraba conectado. El servidor envía una notificación para iniciar el proceso de 'failover' en el cliente.

TAF puede operar en uno de dos tipos:

  • Sesion Failover. Este modo recreará las conexiones perdidas y las sesiones.
  • Select Failover. Este modo volvera a ejecutar los queries que estaban en progreso.

Que a su vez puede usar uno de dos métodos:

  • Basic. Establece la conexión al momento de llevar a cabo el 'failover'. Esta opción casi no requiere trabajo extra en el lado del servidor hasta que ocurre el 'failover'.
  • Preconnect. Provee un 'failover' mucho más rápido pero requiere que la(s) instancia(s) de respaldo tengan de antemando la sesión creada, por lo que se requieren más recursos para soportar todas las conexiones.

Habilitar TAF implica llevar a cabo ciertos pasos de configuración manual, ya sea a nivel cliente al configurar la cadena de conexión, o bien a nivel servidor configurando ciertos atributos. Si ambas opciones de configuración son utilizadas, la configuración del servidor supersede a la del cliente.

A continuación se muestra un ejemplo de la cadena de conexión para configurar TAF en el cliente, cabe aclarar que existen otros parámetros o valores que pueden ser utilizados:

RAC10G =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = srvrac1vip.oratest.com)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = srvrac2vip.oratest.com)(PORT = 1521))
(LOAD_BALANCE=yes)
(FAILOVER=yes)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = rac10g.oratest.com)
(FAILOVER_MODE=
(TYPE=select)
(METHOD=basic)
)
)
)


La siguiente sentencia SQL nos permite ver el resultado de utilizar la cadena de conexión anterior:

SELECT MACHINE, FAILOVER_TYPE, FAILOVER_METHOD, FAILED_OVER, COUNT(*)
FROM V$SESSION
GROUP BY MACHINE, FAILOVER_TYPE, FAILOVER_METHOD, FAILED_OVER;

y obtendríamos el siguiente resultado similar al siguiente antes de realizar el 'failover':

MACHINE FAILOVER_TYPE FAILOVER_METH FAILED_OVER COUNT(*)
--------------- ----------------------- ---------------------------- -------------------- --------
srvrac2 NONE NONE NO 29
JUANJO-LAP SELECT BASIC NO 1


la salida después de llevarse a cabo el 'failover', sería algo parecido a lo siguiente:


MACHINE FAILOVER_TYPE FAILOVER_METH FAILED_OVER COUNT(*)
-------------- ----------------------- ---------------------------- -------------------- ---------
JUANJO-LAP SELECT BASIC YES 1
srvrac1 NONE NONE NO 29

Referencias
=========

  • Oracle® Database Net Services Administrator’s Guide

Pruebas
======

Ahora bien, describo las pruebas que realice:

  1. Se tiene una base de datos Oracle(10.2.0.1.0) en RAC con dos nodos.
  2. La arquitectura de ambos servidores es similar, la única diferencia es que el nodo 1 tiene 2GB RAM y el nodo 2 solamente cuenta con 1GB RAM.
  3. Se relizaron pruebas simulando dos tipos de eventos: perdida de conectivadad por problemas de red (se desconecto el cable de red del nodo activo) y colapso de una instancia (ejecutando un shutdown abort en la instancia activa)
  4. Se probaron las diferentes combinaciones entre tipos y metodos de TAF.
  5. El cliente utilizado fue sqlplus(10.1.0.2.0)

Los resultados son los siguientes:

PRUEBA DE FAILOVER CON DESCONEXIÓN CABLE DE RED

  • TYPE=SELECT Y METHOD=BASIC

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g2

SQL> set timing on

En este momento se desconecto el cable de red del nodo 2

SQL> select instance_name from v$instance;
select instance_name from v$instance;
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel
Elapsed: 00:00:18.92

Como se puede observar le casi 19 segundos darse cuenta de que la conexión ya no existía y mandar el error, sin embargo la sesión no se perdió ya que al volver a ejecutar la sentencia se obtuvo el siguiente resultado:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g1
Elapsed: 00:00:00.00

Otro punto interesante a comentar es que el comportamiento esperado con la opción TYPE=SELECT no ocurrió, ya que la sentencia ejecutada mostró un error (ORA-03113) y no el resultado de la misma. Debido a esto se decidió intentarlo nuevamente, reconectando el cable de red del nodo 2 y desconectando el cable de red del nodo 1, en el que actualmente nos encontrabamos. El resultado fue el siguiente:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g1
Elapsed: 00:01:04.12

Como se puede observar, ahora le tomó mucho más tiempo en regresar el resultado, sin embargo no mostró error alguno.

  • TYPE=SELECT Y METHOD=PRECONNECT

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g1

SQL> set timing on

En este momento se desconecto el cable de red del nodo 1.

SQL> select instance_name from v$instance;
select instance_name from v$instance;
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel
Elapsed: 00:00:19.04

Como se puede observar le tomo poco más de 19 segundos darse cuenta de que la conexión ya no existía y mandar el error, sin embargo la sesión no se perdió ya que al volver a ejecutar la sentencia se obtuvo el siguiente resultado:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g2
Elapsed: 00:00:00.01

Nuevamente se puede observar que el comportamiento esperado con TYPE=SELECT no ocurrió y el tiempo transcurrido para recibir el ORA-03113 fue similiar a la primer prueba realizada anteriormente. Debido a esto no pude encontrar una diferencia palpable entre los metodos BASIC y PRECONNECT así como, en primera instancia observé un comportamiento inestable en el tipo SELECT.

Cabe aclarar que estos resultados pueden deberse a la naturaleza de las pruebas que se realizaron (Desconectar cables de red), aunque no pude probarlo, por lo que decidí cambiar el tipo de evento...

PRUEBA DE FAILOVER CON SHUTDOWN ABORT

  • TYPE=SESSION Y METHOD=BASIC

Averiguamos a que instancia estamos conectados:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g1

SQL> set timing on

Este es el momento en el que se envía el 'shutdown abort' a la instancia rac10g1, justo al mismo tiempo que ejecutamos la siguiente sentencia SQL:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g2
Elapsed: 00:05:12.85

Como se puede observar, toma un poco mas de 5 segundos para que la sentencia regrese el resultado, el cual nos indica que nos encontramos en una instancia diferente a la original. En este caso, cabe aclarar que, aunque el tipo de failover es SESSION, el resultado se genera, en vez de el error (ORA-03113) que sería esperado, ya que probablemente primero ocurrio el ABORT y despues el SELECT una vez realizado el failover de la sesión. Sin embargo, lo interesante es obtener el tiempo que toma llevar a cabo el failover.

  • TYPE=SELECT Y METHOD=BASIC

Averiguamos a que instancia estamos conectados:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g1

SQL> set timing on

Este es el momento en el que se envía el 'shutdown abort' a la instancia rac10g1, justo al mismo tiempo que ejecutamos la siguiente sentencia SQL:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g2
Elapsed: 00:00:04.43

Como se puede observar, toma 4.43 segundos para que la sentencia regrese el resultado, el cual nos indica que nos encontramos en una instancia diferente a la original. Aqui el resultado es muy similar a la prueba anterior, lo cual nos indica que practicamente no existe impacto en el tiempo del failover si el tipo elegido es SELECT o SESSION.

  • TYPE=SELECT Y METHOD=PRECONNECT

Averiguamos a que instancia estamos conectados:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g1

SQL> set timing on;

Este es el momento en el que se envía el 'shutdown abort' a la instancia rac10g1, justo al mismo tiempo que ejecutamos la siguiente sentencia SQL:

SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
rac10g2
Elapsed: 00:00:03.51

Como se puede observar, toma 3.51 segundos para que la sentencia regrese el resultado, el cual nos indica que nos encontramos en una instancia diferente a la original. El tiempo obtenido como resultado demuestra que el método PRECONNECT es alrededor de 1 segundo más rápido (En una red local 100Mbps con poco tráfico) que el método BASIC para efectuar el failover de la conexión pero requiere de una conexión existente desde el inicio a cada una de las instancias, por lo que el costo beneficio debe ser evaluado cuidadosamente.

Conclusiones
==========

  • Al llevar a cabo una implementación de cualquier aplicación que se conecte a un ambiente de base de datos Oracle RAC, o bien, decidir migrar una instancia "standalone" a RAC, es necesario evaluar los requerimientos de negocio que la aplicación tendra que soportar, a fin de seleccionar los tipos y metodos de failover adecuados para cada aplicación.
  • El método PRECONNECT es solo un poco más rápido que el método BASIC y tiene un costo mayor de recursos al requerir sesiones preexistentes en cada una de los nodos que conforman el RAC.

Cómo balancear sesiones en RAC

Uno de los puntos clave para llevar a cabo una implementación exitosa de una aplicación conectada a una base de datos Oracle en Real Application Cluster es entender y configurar el balanceo de las sesiones y la alta disponibilidad que el ambiente RAC nos ofrece.

Este post esta dedicado a entender el balanceo de sesiones. La configuración de Alta Disponibilidad será tratada en otro momento.

Comunmente, pueden existir problemas debidos a las diferentes aplicaciones existentes y a su naturaleza de conexión con la base de datos. Entre los problemas más comunes que me ha tocado observar se encuentran los siguientes:

  • Las aplicaciones no pueden conectarse al ambiente en modo RAC. Únicamente se pueden conectar a uno de los nodos.
  • Uno de los nodos es quien procesa la mayor parte de la carga mientras que el/los otros(s) nodo(s) prácticamente no realizan ningún trabajo.

A continuación explicaré de que forma Oracle realiza el balanceo de carga en un entorno Real Application Clusters, y para ello partiré de las siguientes primicias:

  • Todos los tipos de balanceo disponibles (9i – 10g, ya que no he probado en 11g) ocurren al momento de iniciar la conexión.
  • Las buenas aplicaciones se conectan una sola vez y permanecen conectadas (El costo de establecer una conexión con la base de datos es muy alto).

Ahora bien, los diferentes tipos de balanceo existentes son:

  • Aleatorio. Es configurado a nivel cliente en la cadena de conexión o mediante hardware de balanceo y aleatoriamente distribuyen la conexión a las diferentes instancias. La parte negativa de este método es que no se toma en cuenta la carga en el nodo seleccionado o bien si el nodo está disponible, por lo cual pudieran causarse 'timeouts' a nivel TCP/IP.
    Para confiugurar este método se debe crear una cadena de conexión como la siguiente:
    RAC10G =
    (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = srvrac1vip.oratest.com)(PORT = 1521))
    (ADDRESS = (PROTOCOL = TCP)(HOST = srvrac2vip.oratest.com)(PORT = 1521))
    (LOAD_BALANCE=yes)
    (CONNECT_DATA =
    (SERVER = DEDICATED)
    (SERVICE_NAME = rac10g.oratest.com)
    )
    )

  • Basado en la carga del nodo. El balanceo se realiza a nivel listener y es el método predefinido de Oracle. Este método redirige las conexiones dependiendo del perfil de carga que el PMON de cada una de las instancias reporta dinámicamente al listener. La frecuencia de actualización del valor del perfil de carga depende de la carga misma, es decir, entre mayor sea la carga mayor es la frecuencia con la que el PMON actualiza el perfil de carga. Sin embargo, lo anterior está basado en la carga total en el NODO, no en la carga de las sesiones.
    Este método puede resultar muy bueno para conexiones que tienen una vida corta pero puede degradar el desempeño para las conexiones persistentes cuando la carga cambia a través del tiempo, es decir, el balanco inicial puede resultar finalmente desbalanceado.
    Este metodo no se recomienda para servidores de aplicación o metodos de conexión a través de un 'pool' de conexiones.

  • Basado en el número de sesiones. El balanceo se realiza a nivel listener y es utilizado para distribuir el número de conexiones en cada instancia tomando en cuenta únicamente el número de sesiones conectadas a cada uno de los nodos.
    Este método permite evitar las llamadas “tormentas de conexiones” las cuales no son más que muchas conexiones haciendo una operación de 'logon' en un intervalo muy pequeño de tiempo.
    PMON registra la información de carga de nodo aproximadamente cada 60 segundos en promedio y es por este motivo que las "tormentas de conexiones" no pueden ser bien balanceadas mediante el metodo basado en la carga del nodo. El impacto directo de las "tormentas de conexiones" es una gran pérdida de rendimiento en el nodo afectado y como consecuencia una pérdida general de rendimiento y escalabilidad del cluster.
    Para configurar este método es necesario definir el siguiente parámetro del listener.
    PREFER_LEAST_LOADED_NODE_ =OFF

Ahora bien, entendamos como funciona el proceso de conexión a una base de datos en RAC:

  1. El cliente desea iniciar una conexión a la base de datos y busca en su cadena de conexión la dirección correspondiente a la instancia.
    Si la cadena de conexión tiene configurado un balanceo, aleatoriamente seleccionará uno de los destinos configurados y enviará la petición de conexión.
  2. Cuando el listener recibe la petición de conexión a la instancia, verificará de acuerdo al método configurado (B o C) a cual de las instancias se debe asignar la conexión.
  3. Si la instancia en el nodo local es la candidata para la conexión, entonces el listener inicia el proceso de autenticación.
  4. Si la instacia candidata para la conexión se encuentra en otro nodo, el listener regresa la cadena de conexión hacia el listener correspondiente al que el cliente deberá intentar conectarse e iniciar el proceso de autenticación.

En conclusión, es necesario llevar a cabo un estudio en función del tipo de aplicaciones que se desplieguen en un entorno RAC y cuidadosamente seleccionar la opción a utilizar. Aquí, la recomendación por mi parte es utilizar una combinación de la opción A con alguna de las otras dos opciones (B o C).


Referencias
=========

  • Metalink nota 300903.1
  • Oracle® Database Net Services Administrator’s Guide

jueves, 19 de febrero de 2009

Manejo Automático de Memoria

Con la llegada de 10g, el parámetro de inicialización SGA_TARGET fue introducido. La forma en que oracle presentaba este parámetro era...

"Put away your scripts and let Oracle automatically resize your memory pools."

"Haz a un lado tus scripts, y permite que Oracle reconfigure de forma automática los pools de memoria".

Anteriormente, en versiones menores a 9.2.0.X, un cambio en la estructura de memoria iba acompañado de un bounce de la base de datos.

Ya en 9iR2 lo que solíamos hacer era sobredimensionar el SGA_MAX_SIZE, y aumentar las estructuras de memoria como fueran necesarias. En forma personal, siempre veo esto como mala práctica; quizás se justifica para una base de datos completamente nueva, en la cual se desconoce la carga que vas a tener, y que difícilmente se permitirá hacer un bounce.

Siempre he tenido mis dudas sobre el manejo automático de lo que sea en Oracle (Quedé sorprendido con el buen funcionamiento del UNDO_MANAGEMENT), e incluso recuerdo, que cuando tuve la oportunidad de preguntarle a Ken Jacobs (Dr. Dba) sobre el manejo automático de memoria, no se animó a decir que funcionaba a la perfección, simplemente dijo que detrás de toda esta tecnología, había mucha experiencia de DBAs, y que Oracle estaba haciendo su mejor esfuerzo para facilitar la administración.

De alguna forma estoy de acuerdo, pero es un tanto difícil lograr que las cosas funcionen bien. Doy algunos ejemplos claros.

Si se tiene una aplicación mal diseñada, que no usa bind variables, el sistema estará pidiendo a gritos aumentar el tamaño del shared_pool a costa del buffer_cache.

Otro ejemplo que me ha tocado vivir, es: cuando llega una compañía tipo QUEST o BMC Software ofreciendo productos que ayudan a hacer "Tuning Mágico" eligiendo "n" posibilidades de reescritura de una sentencia SQL. Cada uno de los "n" planes de ejecución se van contra el shared_pool, y de esta forma volvemos a pegarle a los tamaños de las estructuras.

Una carga masiva de información, o recreación de objetos, pudiera incrementar el tamaño del buffer_cache y disminuir alguna otra estructura, etc...

Recientemente en un ambiente productivo, se reportó una lentitud generalizada del sistema, generamos un reporte de AWR en un lapso de una hora y nos encontramos con lo siguiente:


Top 5 Timed Events
~~~~~~~~~~~~~~~~~~
Event Waits Time (s)
------------------------------ ------------ -----------
CPU time 103,907
cursor: pin S wait on X 1,881,946 35,170
...


El problema generalizado de la base de datos era relacionado al CPU. A nivel sistema operativo, "sar" nos mostraba lo siguiente:


prod$ sar 1 10

%usr %sys %wio %idle
92 8 0 0
95 5 0 0
96 4 0 0
96 4 0 0
97 3 0 0
92 8 0 0
92 8 0 0
94 6 0 0
93 7 0 0
96 4 0 0

94 6 0 0



La cantidad de uso de CPU era muy alta, y a nivel base de datos las esperas más altas eran "cursor: pin S wait on X", ¿pero exáctamente a qué se debía?

Lo primero que se revisó fue Metalink, y se encontró la nota 731233.1 Revisando las operaciones de Resize que se estaban ejecutando, se observó que cada 30 segundos había operaciones similares a las siguientes:



SELECT component,
oper_type ,
final_size ,
TO_CHAR(start_time ,'mm/dd/yyyy') started
FROM v$sga_resize_ops
WHERE status ='COMPLETE'
ORDER BY started DESC,
component;



COMPONENT OPER_TYPE FINAL_SIZE STARTED
--------------------- ------------- ------------ -----------
DEFAULT buffer cache SHRINK 7482638336 11/18/2008
shared pool GROW 3070230528 11/18/2008
DEFAULT buffer cache GROW 7616856064 11/18/2008
shared pool SHRINK 2936012800 11/18/2008
DEFAULT buffer cache SHRINK 7482638336 11/18/2008
shared pool GROW 3070230528 11/18/2008
DEFAULT buffer cache GROW 7616856064 11/18/2008
shared pool SHRINK 2936012800 11/18/2008
DEFAULT buffer cache SHRINK 7482638336 11/18/2008
shared pool GROW 3070230528 11/18/2008
DEFAULT buffer cache GROW 7616856064 11/18/2008
shared pool SHRINK 2936012800 11/18/2008
DEFAULT buffer cache GROW 7482638336 11/18/2008
shared pool SHRINK 3070230528 11/18/2008
DEFAULT buffer cache SHRINK 7331643392 11/18/2008
shared pool GROW 3221225472 11/18/2008
DEFAULT buffer cache SHRINK 7482638336 11/18/2008
shared pool GROW 3070230528 11/18/2008
DEFAULT buffer cache GROW 7616856064 11/18/2008
shared pool SHRINK 2936012800 11/18/2008




Así que ¿qué se puede hacer?, creo que 3 cosas.

1) Deshabilitar por completo el Automatic Memory Management (creo yo que es la mejor opción) SGA_TARGET=0.

2) Dejar valores por default más elevados, es decir, si sabemos que como mínimo requerimos el shared_pool de 900m, incluir en el archivo de inicialización shared_pool=900m

3) Aparentemente aplicar un par de parches (7189722 y 6528336) para evitar los crecimientos y decrementos de estructuras tan frecuentemente (no lo he probado).

El problema con el cliente, en esta ocasión se debió a un mantenimiento programado, la instancia usa un init y no un spfile, dentro del init, el shared_pool estaba definido muy pequeño, el db_cahce_size y otros pools no estaban definidos, y el sga_target en mas de 10GB. 

Ahora, debido a que no suelo usar manejo automático de memoria, quizás lo que diga a continuación pudiera no ser válido (simplemente así creo que funcionaría).

Cuando uno tiene un spfile, y manejo automático de memoria, Oracle usa parámetros como:

*.__db_cache_size=58720256
*.__java_pool_size=8388608
*.__large_pool_size=4194304
*.__shared_pool_size=159383552
*.__streams_pool_size=50331648

Estos se pueden ver cuando creamos un pfile a partir de un spfile. Si se reiniciara oracle, por lo menos se tiene un registro de cómo se comportan de forma "optima" las estructuras de memoria en la instancia. Usando los valores con "__" como valores iniciales, y si existen valores predefinidos sin "__" se usan como cota inferior, o valor mínimo. 

Debido a que se usa un archivo de init en esta instancia, al hacer el bounce de la base de datos, las estructuras vuelven a su tamaño original (no hay parámetros "__" en el init), y esto nos lleva a una gran cantidad de cambios en las estructuras de memoria. 

Cuando el problema se presentó, el shared_pool estaba creciendo cerca de los 3gb, y el db_cache_size muy cerca de los 7gb. 

Este tipo de problemas me ha tocado verlos sólo en instancias muy concurridas y con SGAs muy grandes. De manera personal creo que el activar el manejo automático no siempre es la mejor opción, con los advisors activados, podemos darnos cuenta de qué valores son los óptimos, y programar los cambios en estructuras durante periodos de mantenimiento.

Perfiles de desempeño

Recientemente he estado con clientes ayudando a resolver serios problemas de desempeño. Los ambientes, por diversos que sean, son muy similares, es decir, siempre se cuenta con una base de datos que corresponde a una aplicación, y esta aplicación tiene muchas "mini-aplicaciones" relacionadas.

Los clientes generalmente se dan cuenta de que sufren por problemas de desempeño cuando sus usuarios comienzan a quejarse... Y la pregunta es ¿Qué está causando el problema de desmpeño?

La gente suele enfocarse a ver el sistema como "Una Aplicación" e intenta optimizar todos los reportes que se generan dentro de la misma aplicación, pero la realidad, es que sólo se podría estar atacando un porcentaje muy bajo de los problemas en realidad.

Imaginemos que una aplicación es un "E-Business Suite" de oracle; de entrada tenemos carga generada por las sentencias SQL de reportes, SQLs de las formas, SQLs de sesiones en Framework, SQLs de Discoverer, SQLs de interfaces, etc... La probelmática puede venir de muchos lados a la vez. 

Dada esta problemática, la forma más sencilla que he encontrado para hacer el monitoreo/análisis es crear un trigger de sesión que registre la actividad de cada sesión a una tabla de auditoria en la base de datos.

Aquí el script de creación de objetos:

trigger_auditoria.sql

/* se crea la tabla que mantendrá
los datos de auditoria. */
CREATE TABLE "SYS"."AUDITORIA_HECG"
( "ENTRADA" DATE,
"SALIDA" DATE,
"USUARIO_SISTEMA" VARCHAR2(30),
"USUARIO_BASE" VARCHAR2(30),
"EQUIPO" VARCHAR2(64),
"PROGRAMA" VARCHAR2(64),
"MODULO" VARCHAR2(48),
"ACCION" VARCHAR2(32),
"CPU" NUMBER,
"LECTURAS_LOGICAS" NUMBER,
"LECTURAS_FISICAS" NUMBER,
"LECTURAS_FISICAS_BYTES" NUMBER,
"ESCRITURAS_FISICAS" NUMBER,
"ESCRITURAS_FISICAS_BYTES" NUMBER,
"PREV_SQL" NUMBER
)
TABLESPACE "SYSAUX" ;

/* ahora se crea el trigger
que llenará los datos de
las sesiones */

CREATE OR REPLACE TRIGGER "SYS"."AUD_LOGGOF" before logoff ON DATABASE
DECLARE mi_sesion NUMBER;
mi_cpu NUMBER;-- stat 12
mi_pr NUMBER;-- stat 54
mi_prb number;--stat 58
mi_pw NUMBER;-- stat 62
mi_pwb number; --stat 66
mi_lr NUMBER;-- stat 9

BEGIN
FOR estadisticas IN
(SELECT name,
my.statistic#,
VALUE,
sid
FROM v$mystat my,
v$statname st
WHERE my.statistic# IN(9, 12, 54, 58, 62, 66)
AND my.statistic# = st.statistic#
)
LOOP
CASE
WHEN estadisticas.statistic# = 9 THEN
mi_lr := estadisticas.VALUE;
WHEN estadisticas.statistic# = 12 THEN
mi_cpu := estadisticas.VALUE;
WHEN estadisticas.statistic# = 54 THEN
mi_pr := estadisticas.VALUE;
WHEN estadisticas.statistic# = 58 THEN
mi_prb := estadisticas.VALUE;
WHEN estadisticas.statistic# = 62 THEN
mi_pw := estadisticas.VALUE;
WHEN estadisticas.statistic# = 66 THEN
mi_pwb := estadisticas.VALUE;
END CASE;
mi_sesion := estadisticas.sid;
END LOOP;
INSERT INTO AUDITORIA_HECG
SELECT logon_time,
sysdate,
osuser,
username,
machine,
program,
MODULE,
ACTION,
mi_cpu,
mi_lr,
mi_pr,
mi_prb,
mi_pw,
mi_pwb,
prev_hash_value
FROM v$session
WHERE sid = mi_sesion;
END;


Los campos que tiene la tabla de auditoría son los siguientes:

ENTRADA Se registra el logontime de la sesión a la base de datos.
SALIDA Es la hora en que la sesión se desconecta de la base de datos.
USUARIO_SISTEMA Usuario a nivel sistema operativo.
USUARIO_BASE Usuario que se utilizó para conectarse a la base de datos.
EQUIPO Máquina desde la cuál se realizó la conexión a oracle
PROGRAMA Programa utilizado para realizar la conexión (SQL Plus, TOAD, etc.).
MODULO Información adicional sobre la conexión.
ACCION Acción que se realiza dentro del módulo.
CPU Tiempo en centésimas de segundo de uso de CPU para la sesión.
LECTURAS_LOGICAS Cantidad de bloques lógicos leídos. 
LECTURAS_FISICAS Cantidad de bloqes leídos físicos.
LECTURAS_FISICAS_BYTES Cantidad en bytes de lecturas físicas (en caso de que se tengan múltiples pools)
ESCRITURAS_FISICAS Bloques escritos hacia la base de datos.
ESCRITURAS_FISICAS_BYTES Cantidad en bytes de bloques escritos.
PREV_SQL Última sentencia SQL ejecutadapor la sesión.


Una vez creado el trigger, la información empezará a caer en la tabla a medida que las sesiones van terminando. 

Los campos como módulo, acción, prev_sql, están en la tabla ya que me han ayudado a identificar sesiones y crear perfiles asociados con programas o código en específico.

Un pequeño ejemplo de la información:


1 SELECT COUNT(1) Sesiones,
2 programa,
3 SUM(cpu) / 100 "CPU Segs.",
4 SUM(lecturas_logicas) "Lecturas Logicas",
5 SUM(lecturas_fisicas) "Lecturas Físicas"
6 FROM auditoria_rit
7 group by programa
8* order by 5 desc

SESIONES PROGRAMA CPU Segs. Lecturas Logicas Lecturas Físicas
---------- ------------------ ---------- ---------------- ----------------
5259 Monitor.exe 805.03 10181777 161536
42216 SQL*Plus 1689.14 18391422 107703
11 rman.exe 30.57 538894 16157
4 ORACLE.EXE (J001) 34.66 553426 12075
12 oradim.exe 16.35 139138 10178
46 sqlplus.exe 74.2 2465261 6406
26 ORACLE.EXE (J002) 32.91 476683 5634
6 ORACLE.EXE (J003) 12.32 236175 4760
6 SQL Developer 185.47 2059364 3986
32 T.O.A.D. 11.54 30644 2522
1 JDBC Thin Client 1.25 30364 47

11 filas seleccionadas.


Los límites para explotar la información son infinitos, se pueden agrupar por cierta fecha de inicio, por horarios durante el día, por usuarios, una vez que se sabe si el problema es CPU o I/O físico, puedes ir hacia los programas o módulos que más afectan a la base de datos.

Si llegaran a tener muchos valores en Nulo, es decir, un valor de usuario genérico, un application server como equipo, módulo y acción en nulo, prev_sql como "SELECT 1 FROM DUAL", creo que sería hora de pensar un poco en instrumentar el código de nuestra aplicación, por lo menos para añadir un módulo y/o acción con el paquete 

DBMS_APPLICATION_INFO



SQL> exec dbms_application_info.set_module('CARGA','Auditoria');

Procedimiento PL/SQL terminado correctamente.

SQL> exit

c:\> sqlplus "/ as sysdba"

SQL> SELECT entrada,
2 usuario_sistema,
3 usuario_base,
4 programa,
5 modulo,
6 accion
7 FROM auditoria_hecg;

ENTRADA USUARIO_SISTEMA USUARIO_BA PROGRAMA MODUL ACCION
--------- --------------- ---------- ----------- ----- ----------
21-OCT-08 HUGO-WIN\Hugo SYS sqlplus.exe CARGA Auditoria



De esta forma nos será más sencillo encontrar qué es lo que cada sesión realizó en el sistema.

Muchas veces no se mide el impacto que pudiera causar el monitoreo dentro de un sistema, o queries que se ejecutan relativamente rápido, pero que por la cantidad de veces que se ejecutan en el sistema, representan el 30% de las lecturas físicas en el sistema.

Este trigger no es muy intrusivo, no genera carga fuerte en el sistema, y la información que arroja es muy valiosa.

Hay que decir que para versión 9i, se deben de realizar cambios, agradezco a Issachar Zamora, los cambios que relizó en el trigger para que funcionará bien en 9i (las estadísticas de 9i no incluyen escrituras ni lecturas en bytes, además de que el stat_id cambia para algunas otras). 

Una vez que tenga el trigger para 9i lo anexaré a este post.

Adicionalmente suelo poner un índice por (entrada, modulo), ya que los queries que suelo ejecutar van filtrados por fecha o por módulo en específico en un rango de fechas.

Si alguien tiene una idea de cómo mejorar, o qué parámetros cree conveniente añadir en el trigger, no duden en comentarlo.

Cómo evitar un Hard Parse pesado

Este último mes y medio he estado muy metido resolviendo problemas de desempeño con algunos clientes, y aquí expongo un caso con el cuál nunca me había enfrentado (o por lo menos eso creo)y pudiera ser de utilidad para alguien. 

Dentro de un E-Business suite, un usuario se quejaba sobre el desempeño de una forma de Order Entry que al hacer un drilldown de GL a XLAIQDRL, 
el sistema no respondía en mucho tiempo.

Lo que logramos identificar en este problema, fue un acceso muy pesado a la tabla MTL_TRANSACTION_ACCOUNTS.

Como primer intento de solución, encontramos una nota en metalink que pudiera ayudar a resolver el problema, la cual indica aplicar un 
parche junto con la creación de un índice.


CREATE INDEX "INV"."MTL_TRANSACTION_ACCOUNTS_N79" ON
"INV"."MTL_TRANSACTION_ACCOUNTS" ("GL_BATCH_ID", "REFERENCE_ACCOUNT", "TRANSACTION_DATE")
COMPUTE STATISTICS
TABLESPACE "APPS_TS_TX_IDX";


Al hacer los cambios sugeridos, y al revisar el proceso nuevamente, nos encontramos con las siguientes estadísticas en el trace


call count cpu elapsed disk query
------- ------ -------- ---------- ---------- ----------
Parse 1 2.86 2.78 0 0
Execute 1 28.87 28.43 0 0
Fetch 1 114.17 111.90 120 756369
------- ------ -------- ---------- ---------- ----------
total 3 145.90 143.12 120 756369


Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 44

Rows Row Source Operation
------- ---------------------------------------------------
10 SORT ORDER BY (cr=756369 pr=120 pw=0 time=111904779 us)
11176 UNION-ALL (cr=756369 pr=120 pw=0 time=22934845 us)
0 FILTER (cr=0 pr=0 pw=0 time=7 us)


La cantidad de bloques leídos de memoria es mucha, pero quizás por el tipo de accesos que hace, pudiera considerarse normal, el plan de ejecución
consta de más de 590 líneas.

Al hacer un cambio en el índice de orden de columnas


CREATE INDEX "INV"."MTL_TRANSACTION_ACCOUNTS_N79" ON
"INV"."MTL_TRANSACTION_ACCOUNTS" ("GL_BATCH_ID", "REFERENCE_ACCOUNT", "TRANSACTION_DATE")
COMPUTE STATISTICS
TABLESPACE "APPS_TS_TX_IDX";


se logró lo siguiente:


call count cpu elapsed disk query
------- ------ -------- ---------- ---------- ----------
Parse 1 3.31 3.23 0 0
Execute 1 34.25 33.44 0 0
Fetch 1 27.81 27.15 0 331474
------- ------ -------- ---------- ---------- ----------
total 3 65.37 63.83 0 331474

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 44

Rows Row Source Operation
------- ---------------------------------------------------
10 SORT ORDER BY (cr=331474 pr=0 pw=0 time=27155623 us)
11176 UNION-ALL (cr=331474 pr=0 pw=0 time=22074357 us)
0 FILTER (cr=0 pr=0 pw=0 time=8 us)
0 FAST DUAL (cr=0 pr=0 pw=0 time=0 us)


Como se puede ver, el cambio del orden de las columnas, hizo que se leyeran menos de la mitad de los bloques de memoria, y por lo mismo, el query se ejecutó en menos de la mitad del tiempo que antes.

Pero hay un detalle importante a notar, 


call count cpu elapsed
------- ------ -------- ----------
Parse 1 3.31 3.23
Execute 1 34.25 33.44
Fetch 1 27.81 27.15

Misses in library cache during parse: 1

10 SORT ORDER BY (cr=331474 pr=0 pw=0 time=27155623 us)


Como se puede observar, la sentencia SQL no se encontraba en memoria, por lo cuál se obligó a un Hard Parse. La llamada execute, se dice que nos muestra 
estadísticas en la fase de ejecución de un cursor "y... ¿qué diablos es eso?"... Pues bien, parece ser que para una sentencia select, el tiempo de esta llamada,
es el tiempo que Oracle gasta en generar el plan de ejecución.

En el trace, justo después de la llamada 


PARSING IN CURSOR #47 len=7409 dep=0 uid=44 oct=3 lid=44 tim=7410852239759 hv=948570397 ad='bd8a6170'
SELECT TRX_CLASS_NAME,TRX_TYPE_NAME,TRX_NUMBER_DISPLAYED,TRX_DATE

PARSING IN CURSOR #49 len=198 dep=2 uid=0 oct=3 lid=0 tim=7410852558503 hv=4125641360 ad='bebe5ac0'
EXEC #49:c=0,e=931,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=7410852560307
PARSING IN CURSOR #49 len=493 dep=2 uid=0 oct=3 lid=0 tim=7410852561080 hv=2584065658 ad='bdfbfd78'
EXEC #49:c=0,e=514,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=7410852563193
PARSING IN CURSOR #49 len=789 dep=2 uid=0 oct=3 lid=0 tim=7410852564507 hv=3159716790 ad='baf88850'
... (60 llamadas similares) ...
EXEC #49:c=0,e=617,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=7410880914683
PARSING IN CURSOR #50 len=192 dep=1 uid=0 oct=3 lid=0 tim=7410880915705 hv=3136611056 ad='bea06ed8'
EXEC #50:c=0,e=614,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=7410880916713

EXEC #47:c=34380000,e=33582714,p=1,cr=244,cu=0,mis=0,r=0,dep=0,og=1,tim=7410885823104


Todas las llamadas de cursor #49 y #50 eran queries recursivos, muchos sobre la tabla association$. Y claro, si vas a generar un plan de ejecución de +550 líneas, pues imagínense las posibilidades. Por supuesto que esto no sucedía si la sentencia SQL ya estaba en memoria y sólo un "Soft Parse" sucedía, pero por la naturaleza de la aplicación, y de la lógica del negocio, no solía ser un query tan frecuente, y generalmente a los 10 minutos se encontraba fuera del shared_pool, obligando a un "hard parse" en la siguiente ejecución (34 segundos en el execute).

Así que opté por una solución alterna. 

¿Cómo se puede precalcular el plan de ejecución de una sentencia SQL?

La respuesta es sencilla. OUTLINES.

Yo había usado con anterioridad Outlines para hacer cambios en los planes de ejecución en aplicaciones de código cerrado, y la lógica que usaba era:

Cuando Oracle reciba una sentencia SQL y se calcule su Hash_value (soft parse), al usar outlines, en lugar de generar un plan de ejecución, use este otro ya "PRECALCULADO". Así que, ¿Qué sucedería si genero un outline para que use su mismo plan de ejecución?, el resultado fue el siguiente:

Al yo conocer el Hash Value "hv=948570397", y sabiendo que en ese momento, la sentencia estaba en el shared_pool, ejecuté lo siguiente:


SQL> BEGIN
2 DBMS_OUTLN.create_outline(
3 hash_value => 948570397,
4 child_number => 0,
5 category => 'RIT_OUTLINES');
6 END;
7 /

Procedimiento PL/SQL terminado correctamente.


Algunas veces marca un error de End Of Communication Chanel, pero sí genera el outline (me ha sucedido con 10.2.0.2 en linux suse y solaris).


SQL> SELECT name,
2 category,
3 used
4 FROM dba_outlines;

NAME CATEGORY USED
------------------------------ -------------- ------
SYS_OUTLINE_08110513401290701 RIT_OUTLINES UNUSED



Ahora alteramos el sistema para que se use nuestro outline


SQL> ALTER system
2 SET use_stored_outlines = rit_outlines;

Sistema modificado.

SQL> alter system flush shared_pool;

Sistema modificado.


De esta forma bajamos la sentencia SQL de memoria y obligamos a un "Hard Parse". Intentamos nuestra prueba nuevamente, y estos fueron los resultados:


call count cpu elapsed disk query
------- ------ -------- ---------- ---------- ----------
Parse 1 3.98 3.98 0 449
Execute 1 2.70 2.62 0 0
Fetch 1 28.92 28.24 0 331299
------- ------ -------- ---------- ---------- ----------
total 3 35.60 34.85 0 331748

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 44

Rows Row Source Operation
------- ---------------------------------------------------
10 SORT ORDER BY (cr=331299 pr=0 pw=0 time=28242017 us)
11176 UNION-ALL (cr=331299 pr=0 pw=0 time=22968921 us)


Como se puede ver, se realizo un "hard parse", y el tiempo que se utilizó en la llamada Execute fue de menos de 3 segundos (comparado contra los 34 segundos).

Revisamos que nuestro outline haya sido utilizado:


SQL> SELECT name,
2 category,
3 used
4 FROM dba_outlines;

NAME CATEGORY USED
------------------------------ -------------- ------
SYS_OUTLINE_08110513401290701 RIT_OUTLINES USED




Cabe decir que no se puede usar un parámetro de init para usar los outlines, por lo que se recomienda un trigger para dejarlo activarlo en cada startup (o bien a nivel sesión). Debe de estar habilitado el Query Rewrite y se debe de tener mucho cuidado al usarlos, ya que puede haber algunas implicaciones al hacer uso de outlines.

Para el usuario final, el tiempo bajó de 5 minutos a sólo 30 segundos, lo cual fue muy bueno. Así que ya saben, cuando su generación de un plan de ejecución sea muy tardado, siempre está la opción de precompilarlo en un outline.

Funciones determinísticas y Group by vs. Distinct(2)

Recientemente un comentario sobre la entrada de Group by vs. Distinct, me hizo dudar un poco sobre lo que había escrito en relación a group by vs. distinct, pero a la vez me obligó a investigar un poco más, y estas son mis observaciones.

Intentando plasmar lo que se tiene en el comentario, tenemos lo siguiente:

Comenzamos creando una tabla


SQL> CREATE TABLE valores(valor NUMBER(1))
2 TABLESPACE users;

Tabla creada.


Una vez que se tiene la tabla, insertamos 100 registros con sólo 10 valores distintos


SQL> INSERT
2 INTO valores
3 SELECT MOD(rownum, 10)
4 FROM dba_objects
5 WHERE rownum < 101;

100 filas creadas.



Cramos una función "tardada" para probar los ejemplos


CREATE OR REPLACE FUNCTION tardada(numero IN NUMBER)
RETURN NUMBER IS salida NUMBER;
BEGIN
SELECT COUNT(1)
INTO salida
FROM dba_tables
WHERE MOD(rownum, 10) = numero;
RETURN salida;
END;
/


Primero vamos a evaluar el tiempo y estadísticas sin agrupar la información


SQL> set autot traceonly stat
SQL> set timing on

SQL> SELECT tardada(valor)
2 FROM valores;

100 filas seleccionadas.

Transcurrido: 00:00:05.67

Estadísticas
----------------------------------------------------------
100 recursive calls
246414 consistent gets
100 sorts (memory)
100 rows processed


Se procesó la información en más de 5 segundos consumiendo 246,000 bloques. Se procesan 100 registros, y se hacen 100 sorts

Si lo hacemos con un distinct, estas son las estadísticas

SQL> SELECT DISTINCT tardada(valor)
2 FROM valores;

Transcurrido: 00:00:05.68

Estadísticas
----------------------------------------------------------
100 recursive calls
246407 consistent gets
100 sorts (memory)
2 rows processed


Como vemos, se usan prácticamente los mismos recursos, pero al final nos regresa sólamente 2 registros. La función se ejecuta 100 veces.

Ahora lo intentamos hacer con un group by:


SQL> SELECT tardada(valor)
2 FROM valores
3 GROUP BY tardada(valor);

Transcurrido: 00:00:05.76

Estadísticas
----------------------------------------------------------
100 recursive calls
246407 consistent gets
100 sorts (memory)
2 rows processed


En este caso son las mismas estadísticas que con el distinct. En el siguiente ejemplo, que fue el que me llegó a confundir, se tiene lo siguiente:


SQL> SELECT tardada(valor)
2 FROM valores
3 GROUP BY valor;

10 filas seleccionadas.

Transcurrido: 00:00:00.65

Estadísticas
----------------------------------------------------------
10 recursive calls
24647 consistent gets
10 sorts (memory)
10 rows processed


El tiempo de ejecución fue menor a un segundo, la lectura de bloques se redujo a un 10% (24,000) bloques. El resultado nos puede llevar a la conclusión de que de esta forma, sólo se evalúa la función 10 veces debido a que sólo hay 10 valores distintos y no las 100 veces que son los registros totales de la tabla. Esto es correcto de cierta forma, es decir, la función se evalúa 10 veces porque al agrupar por "valor", nos quedamos con 10 registros a evaluar en la función. Aparentemente no hay forma de reescribir de forma simple este ejemplo de group by con un distinct, pero creo que se podría hacer de la siguiente forma:


SQL> SELECT tardada(valor)
2 FROM
3 (SELECT DISTINCT valor valor
4 FROM valores)
5 ;

10 filas seleccionadas.

Transcurrido: 00:00:00.64

Estadísticas
----------------------------------------------------------
10 recursive calls
24647 consistent gets
10 sorts (memory)
10 rows processed


Como se puede ver, se tiene el mismo consumo de recursos que en la primera situación, sin embargo creo que son dos cosas distintas los primeros ejemplos a estos últimos, ya que los últimos ejemplos, primero agrupan los registros y luego evalúan la función, es por eso que regresan 10 registros; los primeros ejemplos, evalúan la función y después agrupan, por esto, sólo regresan 2 registros.

Para poder comprobar lo que digo, voy a generar una función que siempre regrese un valor distinto:


SQL> CREATE sequence secuencia START WITH 1;

Secuencia creada.

SQL> CREATE OR REPLACE FUNCTION rapida(numero IN NUMBER) RETURN NUMBER IS salida NUMBER;
2 BEGIN
3 SELECT secuencia.nextval
4 INTO salida
5 FROM dual;
6 RETURN salida;
7 END;
8 /

Función creada.


Si nosotros ejecutamos los primeros ejemplos del distinct o del group by, las respuestas serán similares a la siguiente:



SQL> SELECT DISTINCT rapida(valor)
2 FROM valores;

RAPIDA(VALOR)
-------------
1
22
25
30
34
42
43
51
54
57
...
82
92
98

100 filas seleccionadas.

SQL> DROP sequence secuencia;

Secuencia borrada.

SQL> CREATE sequence secuencia START WITH 1;

Secuencia creada.

SQL> SELECT rapida(valor)
2 FROM valores
3 GROUP BY rapida(valor);

RAPIDA(VALOR)
-------------
1
22
25
30
34
42
43
51
54
57
...
82
92
98

100 filas seleccionadas.


Con esto, vemos que se comportan igual el distinct y el group by de mis primeros ejemplos, e incluso podemos observar que la función hash que se utiliza para evaluar duplicados es la misma, ya que regresa en el mismo orden los registros. 

Ahora vamos con el ejemplo que ejecuta solamente 10 veces la función:


SQL> DROP sequence secuencia;

Secuencia borrada.

SQL> CREATE sequence secuencia START WITH 1;

Secuencia creada.

SQL> SELECT rapida(valor)
2 FROM valores
3 GROUP BY valor;

RAPIDA(VALOR)
-------------
1
2
3
4
5
6
7
8
9
10

10 filas seleccionadas.



Esto nos ayuda a entender que la agrupación se realiza antes de entrar a la función hash del group by o del distinct, y pudiera ser que no obtengamos el resultado deseado (aunque es un resultado muy válido, simplemente hay que cuidar el sentido).

Ahora, esto nos pone a pensar en que si tenemos una función que siempre que se introduzca un valor se obtendrá el mismo resultado, oracle debería de ser capaz de optimizar nuestro query para ejecutar menos veces la función. 

Es aquí donde se tiene la función determinística:


SQL> CREATE OR REPLACE FUNCTION tardada2(numero IN NUMBER) RETURN NUMBER
2 DETERMINISTIC
3 IS salida NUMBER;
4 BEGIN
5 SELECT COUNT(1)
6 INTO salida
7 FROM dba_tables
8 WHERE MOD(rownum, 10) = numero;
9 RETURN salida;
10 END;
11 /

Función creada.


Y ahora probamos nuestros ejemplos iniciales


SQL> SELECT DISTINCT tardada2(valor)
2 FROM valores;

Transcurrido: 00:00:00.64

Estadísticas
----------------------------------------------------------
10 recursive calls
24647 consistent gets
10 sorts (memory)
2 rows processed

SQL> SELECT tardada2(valor)
2 FROM valores
3 GROUP BY tardada2(valor);

Transcurrido: 00:00:00.64

Estadísticas
----------------------------------------------------------
10 recursive calls
24647 consistent gets
10 sorts (memory)
2 rows processed



Como se puede observar, tanto en el distinct como en el group by, oracle puede hacer uso de un "cache" de resultados de funciones.

Ya como último ejemplo, si una función no es determinística, Oracle puede usar un caché. Es algo que alguna vez vi en un simposio de de Hotsos. Desconozco en su totalidad la forma de evaluar si usa o no un cache, pero el ejemplo es el siguiente:


SQL> SELECT DISTINCT
2 (SELECT tardada(valor)
3 FROM dual)
4 FROM valores;

Transcurrido: 00:00:00.64

Estadísticas
----------------------------------------------------------
58 recursive calls
24680 consistent gets
10 sorts (memory)
2 rows processed


Como se puede ver, la llamada a la función se mete dentro de un query, y con eso, oracle puede hacer uso del cache. Hay un poco más de llamadas recursivas, quizá porque oracle debe determinar si puede o no hacer uso del cache de funciones.

Así que de todo esto, me quedo con la misma impresión que en el fondo, group by y distinct son prácticamente iguales, pero lo que sí es importante, es el definir correctamente nuestras funciones, si sabemos que es una función determinística, crearla como tal, en caso de que no, pues simplemente omitir "DETERMINISTIC".