26 jul 2012

Optimización de MySQL (Parte 1)

Las bases de datos crecen y crecen a lo largo del tiempo, cada día almacenan más y más información. Por lo que es necesario asegurarse que los resultados solicitados sean entregados en el menor tiempo posible.

Medio segundo puede significar la perdida de un 20% del tráfico del sitio, así que es muy importante mejorar el rendimiento de las bases de datos.

Se pueden usar índices y particiones para limitar la cantidad de registros que MySQL tiene que procesar para entregar un resultado.

Para una consulta frecuente, la creación de un índice es la mejor manera de acelerar las cosas. Por ejemplo, si tenemos una tabla con 40 columnas, de las cuales una columna es accesada frecuentemente, se puede crear un índice para dicha columna y acelerar el proceso de búsqueda. Si no existen índices, MySQL hará un "escaneo" completo para obtener los datos, es decir, se examinan todos los registros en la tabla, uno a uno, hasta el último registro o se satisfaga la consulta.

En otros términos, podemos pensar en el índice de un libro. Maravilloso, no?

Cuando creamos un índice para un campo, MySQL registra toda la información de la columna en particular, la ordena y luego la almacena en un objeto único o archivo, separado de la tabla, junto con referencias a la tabla original con los datos sin ordenar.

El mantenimiento de los índices no requiere cosas adicionales. Si creamos un índice para tabla que es actualizada con frecuencia, todos los DELETEINSERT o UPDATE también serán actualizados en el índice.

Cuidado, el manejo incorrecto de los índices ocasionará una carga extra al servidor y puede generar lentitud en otros procesos, por lo que no deben crear índices para todos los campos. Sólo creen índices para las consultas mas frecuentes o para tablas muy grandes (con miles y miles de registros)

Ahora, la forma de indexar va a depender del engine (motor de almacenamiento) que hayan usando al momento de crear las tablas de la base de datos. Anteriomente, MyISAM era el engine por default en mysql server, pero a partir de la versión 5.5 InnoDB a tomado su lugar. Pero no se preocupen, esto es algo que se puede manipular en el archivo de configuración (my.cnf) o se puede especificar mientras se crean las tablas.

Hay varios tipos de índices que se pueden utilizar, tales como B-Tree o Hash . La elección del índice depende del motor de almacenamiento que esté en uso.

UTILIZAR INDICES

Se puede crear un índice para una tabla (mientras se crea la tabla), o bien crearlo para las tablas existentes. Por ejemplo:

CREATE TABLE records (
     name VARCHAR(50),
     age INT,
     id_num INT, INDEX (id)
         );

Esto crea un índice llamada id para la columna id_num. Para una tabla existente el comando podría ser:

CREATE INDEX id ON records(id_num);

Para crear múltiples índices para una tabla:

ALTER TABLE records ADD INDEX id(id_num), ADD INDEX name(name);

Como MySQL desconoce la naturaleza exacta de los datos almacenados en un campo, se creará un índice con toda la longitud de los datos. Por lo tanto, si tenemos una tabla de artículos, y crear un índice en el campo de título de 50 caracteres, MySQL almacena la longitud total del nombre de cada artículo en la base de datos. En un caso como éste, es sabio almacenar sólo los primeros 20 caracteres en el índice, en lugar de la longitud total. Para grandes bases de datos con columnas muy largas, sería un desperdicio de espacio almacenar todo el nombre en el índice.

Puede limitar la longitud de los datos a 20 caracteres especificandolo junto con el nombre de la columna, de este modo:

ALTER TABLE records ADD INDEX id(id_num), ADD INDEX name(name(20));

Recuerden que un índice mal creado puede ocasionar un consumo excesivo de los recursos del servidor, sobre todo si se usa InnoDB.

En el próximo post hablaremos de las particiones.

12 jun 2012

Particionar y Formatear un nuevo disco duro con ext3

Recientemente me informaron acerca de la configuración de discos duros de los nuevos servidores, la necesidad es crear una partición primaria con ext3 y montarla en el directorio /data02.

Manos a la obra:

Lo primero que necesitamos es consultar la tabla de particiones y ubicar el disco que vamos a ocupar, aquí mucho cuidado, cualquier error nos puede arruinar el día. Por lo tanto ejecutamos fdisk de la siguiente forma:
# /sbin/fdisk -l
Disk /dev/cciss/c0d0: 299.9 GB, 299966445568 bytes
255 heads, 63 sectors/track, 36468 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

Device Boot Start End Blocks Id System
/dev/cciss/c0d0p1 * 1 16 128488+ 83 Linux
/dev/cciss/c0d0p2 17 36468 292800690 8e Linux LVM

Disk /dev/cciss/c0d1: 599.9 GB, 599932844032 bytes
255 heads, 32 sectors/track, 143596 cylinders
Units = cylinders of 8160 * 512 = 4177920 bytes


Disk /dev/cciss/c0d1 doesn't contain a valid partition table
El disco a trabajar es /dev/cciss/c0d1, y tal como lo dice el mensaje: No contiene una tabla de partición válida. Para comenzar, usamos nuevamente el comando fdisk de la siguiente forma (Se resaltan las opciones en negritas):
# /sbin/fdisk /dev/cciss/c0d1
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.

The number of cylinders for this disk is set to 143596.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
(e.g., DOS FDISK, OS/2 FDISK)
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help):n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-143596, default 1): 1
Last cylinder or +size or +sizeM or +sizeK (1-143596, default143596 ): 143596

Command (m for help): t
Partition number (1-4): 1
Hex code (type L to list codes): 83

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.
No olviden la "w", de lo contrario los cambios no se escribirán en la tabla de particiones.

Ahora vamos a crear el sistema de archivos ext3 (o formatear, como normalmente se dice)
# /sbin/mkfs.ext3 -b 4096 /dev/cciss/c0d1
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
73236480 inodes, 146467979 blocks
7323398 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=4294967296
4470 block groups
32768 blocks per group, 32768 fragments per group
16384 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
102400000


Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done


This filesystem will be automatically checked every 33 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
Ahora que el disco está particionado y formateado, el último paso es "montarlo", recordemos que el directorio donde deseo montar mi nuevo disco es /data02. Primero creamos el directorio /data02.

# mkdir /data02
Y luego editamos el archivo /etc/fstab para agregar la entrada (en azul):
/dev/vg_root/lv_root   /        ext3   defaults       1 1
/dev/vg_root/lv_tmp    /tmp     ext3   defaults       1 2
/dev/vg_root/lv_data01 /data01  ext3   defaults       1 2
/dev/vg_root/lv_usr    /usr     ext3   defaults       1 2
/dev/vg_root/lv_var    /var     ext3   defaults       1 2
LABEL=/boot            /boot    ext3   defaults       1 2
tmpfs                  /dev/shm tmpfs  defaults       0 0
devpts                 /dev/pts devpts gid=5,mode=620 0 0
sysfs                  /sys     sysfs  defaults       0 0
proc                   /proc    proc   defaults       0 0
/dev/vg_root/lv_swap            swap   swap defaults  0 0
/dev/cciss/c0d1        /data02  ext3   defaults       1 1
Después de crear la entrada, lo que resta es montar el disco:
# mount /data02
Verificamos que todo quedó bien:
# /bin/df -h
Filesystem                     Size Used Avail Use% Mounted on
/dev/mapper/vg_root-lv_root    2.0G 399M 1.5G   22% /
/dev/mapper/vg_root-lv_tmp     3.9G 137M 3.6G    4% /tmp
/dev/mapper/vg_root-lv_data01  250G 188M 237G    1% /data01
/dev/mapper/vg_root-lv_usr     5.9G 1.1G 4.5G   20% /usr
/dev/mapper/vg_root-lv_var     5.9G 292M 5.3G    6% /var
/dev/cciss/c0d0p1              122M  13M 103M   11% /boot
tmpfs                           16G     0 16G    0% /dev/shm
/dev/cciss/c0d1                550G 198M 522G    1% /data02

15 may 2012

Concatenar comandos en linux

Concatenar comandos:

con | hace que la salida del primero se convierta en la entrada del segundo.

[root@server ~]# cmd1 | cmd2

Con & hará que los dos (o más) comandos se ejecuten de manera simultanea.

[root@server ~]# cmd1 & cmd2

Con || El segundo comando se ejecutará si el primero termina sin éxito.

[root@server ~]# cmd1 || cmd2

Con && El segundo comando se ejecutará solo si el primero termina con éxito.

[root@server ~]# cmd1 && cmd2

Con ; El segundo comando se ejecutará sin importar el resultado del primero.

[root@server ~]# cmd1 ; cmd2

13 feb 2012

PHP vs. PYTHON vs. RUBY

16 ene 2012

httpd.conf VirtualHost Splitter

Sin duda, ser ordenados nos lleva a tener una mejor administración de los
recursos. Y más, si estamos hablando de una gran infraestructura de servidores Apache que alojan cientos de hosts virtuales.

Por esta razón he creado un script python el cual ayudará en ésta tarea. La filosofía fue la siguiente:


  1. Generar un archivo de configuración por cada VirtualHost, usando la directiva de Apache "ServerName" por ejemplo: "ServerName".conf 
  2. Guardar cada archivo creado en en nuevo directorio: /etc/httpd/vhosts/ 
  3. Incluir el nuevo directorio en la configuración principal de Apache: Include vhost/*.conf
  4. Finalmente, comentar todos los VirtualHosts del archivo de configuración principal. 
  5. Refrescar la configuración de Apache.
El script ejecuta algunos comandos de sistema, el primero respalda el
httpd.conf original por cualquiero cosa (no está de más). Y otros comandos cambian permisos de carpetas y archivos para poder ser usados con otros propósitos (Una interfaz Web)

Bien, aquí les dejo el script, espero sea de su utilidad.
#!/usr/bin/python
# AUTHORS:     ANGEL CABRERA, ALEJANDRO GUADARRAMA
# DATE:      11/01/2012
# DESCR:      HTTPD.CONF SPLITTER


import re, sys, os

vhost_dir = "/etc/httpd/vhost/"
os.system('cp -rp %s %s.bak' % (sys.argv[1], sys.argv[1]))
print "\nRespaldando configuracion actual..."

if os.path.exists(vhost_dir):
    print "\nYa existe el directorio ", vhost_dir, "\n"
else:
    print "\nCreando el directorio ", vhost_dir, "\n"
    os.makedirs(vhost_dir)
    os.chmod(vhost_dir,0775)

if len(sys.argv) == 1:
  print "Uso: split.py <httpd.conf>"
  sys.exit(1)

input = open(sys.argv[1], "rb")
p_include = re.compile(r"[I,i]nclude /etc/httpd/conf.d/vhosts/\*.conf")
p_comment = re.compile(r"[\s\t]*#.*")
p_begin = re.compile(r"<VirtualHost[^>]+>")
p_end = re.compile(r"</VirtualHost>")
p_server = re.compile(r"[S,s]erver[N,n]ame[\s\t]*(.*)")
file_name = "default"
found = False
commented = False
servers = {}
new_lines = []
archivo_comentado = []
n_total = 0
n_commented = 0
num_servers = 0
n_include = []
total_lineas = 0
iniciando = True
primer_host = 0

for line in input:
  total_lineas += 1
  if not found:
    i = p_include.search(line)
    if i:
      n_include.append(line)
    m = p_begin.search(line)
    if m:
      found = True
      if iniciando:
        primer_host = total_lineas
    iniciando  = False         
      file_name = "default"
      new_lines.append(line)
      m = p_comment.search(line)
      commented = False
      archivo_comentado.append("#"+line)
      if m:
        commented = True
  else:
    archivo_comentado.append("#"+line)
    new_lines.append(line)
    m = p_server.search(line)
    if m:
      file_name = m.group(1).strip()
    m = p_end.search(line)
    if m:
      found = False
      if num_servers == 0 and not commented:
    prefijo = "1_"
      else:
    prefijo = ""
      nombre_arch = vhost_dir+prefijo+file_name+".conf"
      if file_name in servers:
        servers[file_name] += 1
        file_name += "-%d" % servers[file_name]
      else:
        servers[file_name] = 0
      if commented:
        n_commented += 1
        print "Omitiendo virtual host... %s" % nombre_arch +"\n"
      else:
    num_servers += 1
        print "Creando virtual host... %s" % nombre_arch +"\n"
        output = open(nombre_arch, "wb")
        output.writelines(new_lines)
        output.close()
    os.chmod(nombre_arch,0664)
        new_lines = []
      n_total += 1

input.close()
os.system('chown -R root:reloadapache %s' % vhost_dir)
print "\n%d virtual hosts encontrados. %d comentados no creados| %d creados.\n" % (n_total, n_commented,num_servers)

arch = open(sys.argv[1], "rb")
contenido_original = arch.readlines()
arch.close()
del contenido_original[primer_host:(total_lineas+1)]

arch_modif = open(sys.argv[1], "w")
arch_modif.writelines(contenido_original)
arch_modif.writelines(archivo_comentado)
arch_modif.write("\nInclude vhost/*.conf\n")
arch_modif.writelines(n_include)
arch_modif.close()
os.system('/sbin/service httpd reload')
Nos vemos en la siguiente entrada!