jueves, 16 de agosto de 2007

Tablas y Secuencias Temporales

Hola a todos, hoy se me ocurre escribir sobre tablas y secuencias temporales en postgresql. Las tablas temporales y secuencias temporales son aquellas que solo viven mientras la conexion esta activa y despues son destruidas por el backend de postgresql cuando nos desconectamos. Son similares a las tablas y secuencias normales salvo que son de corta duracion y para crearlas solo basta agregar la palabra temporary a la sintaxis ya conocida, por ejemplo:

CREATE TEMPORARY TABLE clientes AS select * from clientes;

CREATE TEMPORARY SEQUENCE alguna_secuencia START 1;

Saludos.

martes, 14 de agosto de 2007

Obtimizando Select count(*) para tablas con mas de 10 millones de registros en Postgresql

Hola a todos, el dia de hoy me complace escribir sobre la optimización de la consulta select count(*) en postgresql, cuando tenemos tablas muy grandes a veces el problema es el tiempo que se lleva al procesar una consulta lineal del tipo select count(*), obviamente el primer mecanismo seria crear un indice sobre dicha tabla empleando CREATE INDEX. Por ejemplo en una tabla que tengo de nombre produccion tengo al rededor de 11916936 registros, ya son muchos registros :).

A continuación muestro la estructura interna de la tabla produccion:


sicodelico=> \d produccion;
Tabla «public.produccion»
Columna | Tipo | Modificadores
-------------------+---------+---------------------------------------------------------
id | integer | not null default nextval('produccion_id_seq'::regclass)
no_serie | integer | not null default 0
id_modelo | integer | not null
id_personal | integer | not null
fecha_fabricacion | date | not null default '2005-01-01'::date
fecha_salida | date |
Índices:
«produccion_pkey» PRIMARY KEY, btree (id)
Restricciones de llave foránea:
«produccion_id_modelo_fkey» FOREIGN KEY (id_modelo) REFERENCES modelos(id)
«produccion_id_personal_fkey» FOREIGN KEY (id_personal) REFERENCES personal(id)



Pues bien, empezemos creando el indice de la tabla produccion para busquedas mediante numero_serie de la siguiente manera:


CREATE INDEX no_serie_idx ON produccion(no_serie);


Aqui es necesario hacer notar que el crear muchos indices puede traer consecuencias nefastas, ya que cada indice ocupa espacio en disco, y muchos indices pueden ralentizar nuestro sistema por el I/O excedente.

Bueno, bueno, ahora lo que necesitamos hacer es ordenar fisicamente (esto es a nivel de cluster) la información, basandonos en el indice ya creado.

Para hacer esta ordenación nos valemos del comando CLUSTER de postgresql.


CLUSTER no_serie_idx on produccion;


Ya con esto tenemos ordenados nuestros datos con forme a los clusters contiguos del disco duro y con esto evitamos desplazamientos inecesarios de la cabeza lectora del disco duro.

En mi ejemplo tarde 34 segundos para completar el select count(*).

Aqui muestro la salida del Explain Analyze:


sicodelico=> EXPLAIN ANALYZE SELECT count(*) from produccion;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=231018.70..231018.71 rows=1 width=0) (actual time=33775.520..33775.522 rows=1 loops=1)
-> Seq Scan on produccion_large (cost=0.00..201226.36 rows=11916936 width=0) (actual time=0.068..17800.818 rows=11916936 loops=1)
Total runtime: 33775.597 ms


Cuando hice la prueba sin optimizar el disco tardo poco mas de 1 minuto, y sin indice mejor ya ni lo menciono.


Saludos.

sábado, 11 de agosto de 2007

Eliminar restriccion Primary Key en PostgreSQL

Para eliminar la restriccion primary key en postgresql empleariamos la sintaxis siguiente:


ALTER TABLE mitabla DROP CONSTRAINT mitabla_pkey;


En donde mitabla es el nombre de la tabla obviamente y mitabla_pkey es el indice primary key.

Saluditos.

Postgresql y el particionamiento de tablas (Partitioning Tables)

A veces cuando estamos manejando tablas con un numero grande de registros, varios millones o mas es necesario particionar las tablas para obtener un mejor desempeño a la hora de consultar dichas tablas. A este proceso se le suele denominar "Partitioning" y es le metodo usado para descomponer una enorme tabla (father) en un cojunto de tablas hijas (child).

Algunas de las enormes ventajas son:

  • Desempeño mejorado para la obtencion de registros (Querys).

  • Desempeño mejorado para la actualización (update).

  • Indices mas pequeños para cada tabla hija contra indices grandes y dificiles de colocar en memoria en una tabla grande.

  • Eliminación rapida de registros. Empleando DROP TABLE en vez de DELETE.

  • Los datos pueden ser migrados o respaldados en medios economicos y pequeños como DVDs, discos duros extraibles, etc.



En postgresql el tipo de partitioning soportado se denomina particionado mediante herencia de tablas. Cada partición puede ser creada como una tabla hija de una unica tabla padre. La tabla padre normalmente debe de ser una tabla vacia, que representara a todo el conjunto de datos.

Los dos tipos de particionado mas comunes en postgresql son:


  1. Particionar por rangos. La tabla es particionada mediante rangos definidos en base a la columna de llave primary o cualquier columna que no se solape entre los rangos de valores asignados a diferentes tablas hijas.


  2. Particionar por lista. La tabla es particionada listando los valores de cada una de las llaves en cada particion.



En postgresql para poder utilizar dicha técnica es necesario seguir algunos pasos:


  1. Crear la tabla "maestra", de la cual todas las tablas hijas heredaran. Esta tabla no contendra datos, no debe de tener ningun tipo de restricción (check), no debe de tener indice ni nada por el estilo.

  2. Crear todas las tablas hijas heredando de la tabla maestra. Por lo regular estas tablas heredaran todos los campos de la tabla padre y no es necesario crearlos nosotros mismos.

  3. Agregar a todas las tablas hijas las restricciones correspondientes sobre los datos que albergaran. Algunos ejemplos de esto serian: CHECK ( pais IN ('México', 'Argentina')), CHECK ( id_cliente BETWEEN 100 AND 200 ) ... CHECK ( id_cliente BETWEEN 5000 AND 5200), en estos dos ultimos ejemplos para evitar traslapes entre los ids de clientes.

  4. Para cada particion, crear un indice para la columna(s) llave, tambien se pueden poner los indices que a cada quien le convengan. El indice para la llave primaria no es estrictamente necesario, pero en la mayoria de los casos ayuda mucho. Si tu necesitas una llave unica o llave primaria, necesitaras crearla para cada tabla hija.

  5. Definir una regla (RULE) o trigger para redirigir las modificaciones de la tabla padre (master) a la apropiada particion.

  6. Verificar que este habilitado a on el parametro constraint_exlusion (SET constraint_exclusion = on;) en el archivo de configuracion postgresql.conf para que los querys sean optimizados para el partitioning.



Pues bien, procedamos a realizar un ejemplo con esto.

Empecemos creando la tabla maestra para nuestro ejemplo.

El esquema de la tabla quedaria de la siguiente forma:


sicodelico=> \d produccion;
Tabla «public.produccion»
Columna | Tipo | Modificadores
-------------------+---------+---------------
id | integer | not null
no_serie | integer |
id_modelo | integer |
id_personal | integer |
fecha_fabricacion | date |
fecha_salida | date |


Una vez creada la tabla maestra procedemos a crear las tabla hijas que en nuestro ejemplo particionaremos en base al modelo del producto fabricado, que seran 4 tablas hijas en base a los 4 modelos almacenados en nuestra base de datos.

Un leve vistazo de la tabla de modelos nos arroja lo siguiente:


sicodelico=> SELECT * from modelos order by id;
id | modelo | caracteristicas | descripcion | imagen
----+-----------------+------------------------+----------------------+--------
1 | ODIN | 5 litros por minuto | calentador de paso |
2 | DELTA 01 | 7 litros por minuto | calentador de paso |
3 | DELTA 01-PLUS | 9 litros por minuto | calentador de paso |
4 | DELTA 02 | 13 LITROS POR MINUTO | CALENTADOR DE PASO |

(4 filas)


Ahora crearemos las tablas hijas heredando de la tabla "produccion", aqui es donde se crean las restricciones (CHECK) para los datos que albergaran cada tabla hija.


CREATE TABLE produccion_odin ( CHECK (id_modelo = 1) ) INHERITS (produccion);
CREATE TABLE produccion_delta01 ( CHECK (id_modelo = 2) ) INHERITS (produccion);
CREATE TABLE produccion_delta01plus ( CHECK (id_modelo = 3) ) INHERITS (produccion);
CREATE TABLE produccion_delta02 ( CHECK (id_modelo = 4) ) INHERITS (produccion);


Las restricciones CHECK tambien se pueden agregar despues mediante la sintaxis del ALTER TABLE.

Mediante las restricciones impuestas en las tablas aseguramos que no almacenen información que no les corresponde, por ejemplo que en la tabla produccion_odin que contiene registro de todos los calentadores del modelo ODIN no tengan calentadores de otros modelos, como DELTA02.

Ahora necesitamos crear los indices para cada tabla hija creada segun nos convenga para agilizar la busqueda de registros o garantizar la unicidad de los mismos, si necesitamos alguna llave primaria, aqui es donde podemos crearla.

Para el caso de las llaves primarias nos valemos de la sintaxis del ALTER TABLE como por ejemplo: ALTER TABLE produccion_odin ADD PRIMARY KEY (id);.

Para nuestro ejemplo nos conformamos con unos simples indices Btree. Quedarian de la siguiente manera:


CREATE INDEX produccion_odin_id ON produccion_odin (id);
CREATE INDEX produccion_delta01_id ON produccion_delta01 (id);
CREATE INDEX produccion_delta01plus_id ON produccion_delta01plus (id);
CREATE INDEX produccion_delta02_id ON produccion_delta02 (id);


Bien, ahora procederemos a crear el juego de reglas para garantizar el llenado en cascada de los datos y que cuando estos caigan en la tabla maestra sean redirigidos a sus respectivas tablas.


-- Regla para produccion_odin
CREATE RULE produccion_odin_insert_rule AS
ON INSERT TO produccion WHERE
( id_modelo = 1 )
DO INSTEAD
INSERT INTO produccion_odin VALUES ( NEW.id,
NEW.no_serie,
NEW.id_modelo,
NEW.id_personal,
NEW.fecha_fabricacion,
NEW.fecha_salida);

-- Regla para produccion_delta01
CREATE RULE produccion_delta01_insert_rule AS
ON INSERT TO produccion WHERE
( id_modelo = 2 )
DO INSTEAD
INSERT INTO produccion_delta01 VALUES ( NEW.id,
NEW.no_serie,
NEW.id_modelo,
NEW.id_personal,
NEW.fecha_fabricacion,
NEW.fecha_salida);

-- Regla para produccion_delta01plus
CREATE RULE produccion_delta01plus_insert_rule AS
ON INSERT TO produccion WHERE
( id_modelo = 3 )
DO INSTEAD
INSERT INTO produccion_delta01plus VALUES ( NEW.id,
NEW.no_serie,
NEW.id_modelo,
NEW.id_personal,
NEW.fecha_fabricacion,
NEW.fecha_salida);

-- Regla para produccion_delta02
CREATE RULE produccion_delta02_insert_rule AS
ON INSERT TO produccion WHERE
( id_modelo = 4 )
DO INSTEAD
INSERT INTO produccion_delta02 VALUES ( NEW.id,
NEW.no_serie,
NEW.id_modelo,
NEW.id_personal,
NEW.fecha_fabricacion,
NEW.fecha_salida);



Ya con esto tenemos gestionada la metodologia para la inserción en multiples tablas, tambien es importante notar que se pueden usar triggers en vez de reglas para este fin.

Ahora procedemos a crear una vista que contendra todos los valores como si se tratase de una tabla enorme:


CREATE VIEW produccion_total AS
SELECT * FROM produccion_odin
UNION ALL SELECT * FROM produccion_delta01
UNION ALL SELECT * FROM produccion_delta01plus
UNION ALL SELECT * FROM produccion_delta02;


Con esto ya tenemos nuestro esquema de tablas particionado, solo nos falta habilitar el obtimizador de consultas para evitar la busqueda de algun dato en todas las particiones y eso se hace asi desde dentro de psql:

SET constraint_exclusion = on;

o modificando el postgresql.conf de la siguiente manera, anexando la siguiente linea:

constraint_exclusion = on

Pues ya con esto me despido y espero les sirva.

Indices Parciales en Postgresql

Postgresql soporta lo denominado indice parcial con expresiones arbitrarios el cual es un indice construido sobre un subconjunto de los registros de una tabla.

Un motivo para la utilización de este tipo de indices esta justificado con la siguiente premisa: si todas las consultas que a usted le resulten útiles caen dentro de un cierto rango ¿por qué construir un índice sobre toda la tabla y sufrir el costo de espacio y tiempo asociado a ello?

Un ejemplo de este tipo de indices quedaria de la siguiente forma:

CREATE INDEX sueldos_idx ON sueldos(sueldo) WHERE sueldo > 15000.00 and sueldo < 30000.00;

Aqui el indice se crea solo para los sueldos almacenados entre 15000.00 y 30000.00 y si en una fabrica la mayoria de los empleados recaen en esos sueldos, aqui es donde se utilizaria ahorrando espacio por el indice generado.

Saludos.

viernes, 10 de agosto de 2007

Cambiar el propietario de una tabla en PostgreSQL

Para cambiar el propietario de una tabla x en postgres nos valemos del comando alter para efectuar dicha operación, la sintaxis para este proposito seria de la siguiente manera:


ALTER TABLE tablax OWNER TO usuario_nuevo;


Para que la siguiente instrucción surta efecto solo basta ser el propietario de la tabla o ser el usuario PostgreSQL.

Saludos a todos.



miércoles, 1 de agosto de 2007

Configurar el protocolo SSL en Apache 2.2 con Ubuntu Feisty Fawn

Hola a todos, el dia de hoy voy a documentar el como instalar en nuestro ubuntu feisty fawn el protocolo SSL (Secure Socket Layer) para nuestro Apache 2.2.

A diferencia de lo que pensaba antes de hecharlo a andar, es muy facil instalarlo.

Pues comencemos.

1. Primero que nada necesitamos instalar el servidor web apache que en ubuntu y cualquier distribucion basada en debian es cosa de un simple apt-get.

# apt-get install apache2

2. Ahora nos toca habilitar el modulo de SSL en nuestro recien instalado apache.

#a2enmod ssl

3. Aqui deberiamos de poder crear el certificado ssl mediante el comando apache2-ssl-certificate, pero para nuestra desgracia, al parecer en esta versión de ubuntu no se incluyo y necesitamos realizar unos pasos adicionales para subsanar dicho error :( , y quedaria asi.

Necesitamos descargar el siguiente archivo: apache2-ssl.tar.gz y ya una vez descargado procedemos a descomprimirlo, lo cual nos deja dos archivos en nuestro directorio. Uno con el nombre de ssleay.cnf el cual deberemos copiar en /usr/share/apache2 y otro con nombre apache2-ssl-certificate que copiaremos a /usr/sbin, ahora solo falta crear el directorio en donde residira el certificado ssl y el cual es /etc/apache2/ssl.

Listo, con esto ya tenemos el comando para generar el certificado de autenticidad, y podemos proceder a configurar nuestro Apache2 con SSL :) .

4. Creamos nuestro certificado de la siguiente manera:

# apache2-ssl-certificate

El comando nos arroja la siguiente salida esperando algunas respuestas:


creating selfsigned certificate
replace it with one signed by a certification authority (CA)

enter your ServerName at the Common Name prompt

If you want your certificate to expire after x days call this programm
with -days x
Generating a 1024 bit RSA private key
........++++++
....................++++++
writing new private key to '/etc/apache2/ssl/apache.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [GB]:MX
State or Province Name (full name) [Some-State]:Distrito Federal
Locality Name (eg, city) []:Mexico
Organization Name (eg, company; recommended) []:Sistemas y Conectividad
Organizational Unit Name (eg, section) []:Sistemas y Conectividad
server name (eg. ssl.domain.tld; required!!!) []:localhost
Email Address []:webmaster@localhost


5. Ahora necesitamos crear algunos archivos de configuracion para apache y para eso nos valemos de cualquier editor de textos como emacs o vi.

Nos basamos en los archivos de configuracion estandar de apache en nuestro ubuntu de la siguiente manera:


cp /etc/apache2/sites-available/default /etc/apache2/sites-available/ssl
ln -s /etc/apache2/sites-available/ssl /etc/apache2/sites-enabled/ssl


Ahora modifiquemos el archivo /etc/apache2/sites-enabled/ssl con cualquier editor de textos y agreguemos el numero de puerto de escucha de Apache2 SSL y algunas cosas mas como habilitar la directiva SSLEngine a On y fijar la ruta de SSLCertificateFile a /etc/apache2/ssl/apache.pem:


# Agregar puerto 443 default para SSL en NameVirtualHost
NameVirtualHost *:443

ServerAdmin webmaster@localhost

# Modificar el DocumentRoot hacia el directorio
# que querramos para SSL
DocumentRoot /home/www/ssl/htdocs

Options Indexes FollowSymLinks MultiViews
AllowOverride All


# Opciones de configuracion de nuestro directorio SSL

Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
# This directive allows us to have apache2's default start page
# in /apache2-default/, but still have / go to the right place
#RedirectMatch ^/$ /apache2-default/


ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

AllowOverride None
Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all


ErrorLog /var/log/apache2/error.log

# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn

CustomLog /var/log/apache2/access.log combined
ServerSignature On

# Aqui agregamos el soporte SSL para nuestro apache asi como la ruta
# del certificado creado anteriormente.
SSLEngine On
SSLCertificateFile /etc/apache2/ssl/apache.pem

Alias /doc/ "/usr/share/doc/"

Options Indexes MultiViews FollowSymLinks
AllowOverride None
Order deny,allow
Deny from all
Allow from 127.0.0.0/255.0.0.0 ::1/128






6. Ahora necesitamos modificar el archivo: /etc/apache2/sites-enabled/000-default agregandole el puerto 80 al servidor web para las conexiones no seguras. El cambio quedaria asi:


# Puerto 80 predeterminado para el servidor web sin SSL
NameVirtualHost *:80

# Tambien aqui le agregamos el puerto 80

ServerAdmin webmaster@localhost

DocumentRoot /home/www/

Options Indexes FollowSymLinks MultiViews
AllowOverride All


Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
# This directive allows us to have apache2's default start page
# in /apache2-default/, but still have / go to the right place
#RedirectMatch ^/$ /apache2-default/


ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

AllowOverride None
Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all


ErrorLog /var/log/apache2/error.log

# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn

CustomLog /var/log/apache2/access.log combined
ServerSignature On

Alias /doc/ "/usr/share/doc/"

Options Indexes MultiViews FollowSymLinks
AllowOverride None
Order deny,allow
Deny from all
Allow from 127.0.0.0/255.0.0.0 ::1/128






Esos son los unicos cambios que se deben realizar en el archivo de default.

7. Ahora solo nos falta agregar el nuevo puerto (443) en el archivo /etc/apache2/ports.conf para que apache realice un fork() y cree el servidor a la escucha en ese puerto. El archivo ya modificado quedaria asi:


juliocs@andromeda:~$ cat /etc/apache2/ports.conf
Listen 80
Listen 443


8 Ya con esto solo nos falta reiniciar el apache con el comando:


# /etc/init.d/apache2 force-reload


Aqui muestro la salida del comando nmap contra localhost mostrando a nuestro apache con SSL habilitado:


juliocs@andromeda:~$ nmap localhost

Starting Nmap 4.20 ( http://insecure.org ) at 2007-08-01 05:53 CDT
Interesting ports on localhost (127.0.0.1):
Not shown: 1684 closed ports
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
139/tcp open netbios-ssn
443/tcp open https
445/tcp open microsoft-ds
631/tcp open ipp
902/tcp open iss-realsecure-sensor
977/tcp open unknown
2049/tcp open nfs
3306/tcp open mysql
5432/tcp open postgres

Nmap finished: 1 IP address (1 host up) scanned in 0.247 seconds



Ya por mi parte es todo y nos vemos pronto.



Bahia desde el balcon...

Bahia desde el balcon...
Ixtapa Zihuatanejo