Monday, March 29, 2010

Parece que las bases de datos relacionales tienen competencia fuerte.

Una nueva ola de almacenar datos esta revolucionando la insdustria en los ultimos meses, y definitivamente nombres reconocidos en la industria estan reconociendo sus bondades, pero de que estamos hablando? Al parecer el tradicional LAMP, una combinacion ganadora en los ultimos anios y que ha hecho uso extensivo de bases de datos relacionales como MySQL y PostgreSQL, parece cosa del pasado, y es que la nueva modalidad de bases de datos no relacionales o (Non-SQL como tambien les llaman en Ingles) son la nueva panacea en el almacenamiento de informacion (o mejor dicho documentos).

Pero que esta motivando este cambio:

  • En muchos casos las bases de datos relacionales no son la respuesta a todos los requerimientos de almacenamiento de datos.
  • Las bases de datos relacionales presentan obstaculos para su uso en ambientes distribuidos.
  • La normalizacion usualmente impacta en el desempeno de la base de datos.
  • En muchas aplicaciones solamente es necesario hacer busquedas basadas en la llave primaria.


Los nuevos protagonistas en el almacenamiento de bases de datos difieren en algunas funcionalidades, pero en general todos se basan en un conjunto de caracteristicas de alto nivel, entre las cuales podemos mencionar:
  1. Son bases de datos no normalizadas, sin necesidad de esquemas y orientadas a documentos
  2. Bases de datos simples, basadas en el conjunto llave : valor; metodos de busqueda estan disponibles y se basan en busquedas de la llave primaria.
  3. Capacidad de uso distribuido, o crecimiento horizontal, donde replicas de la base de datos pueden ubicarse en diversos nodos de distribucion de contenidos para los usuarios.
  4. Replicacion es un punto importante como se menciono anteriormente, entonces esta debe ser una caracteristica importante de la base de datos.
  5. La base de datos se accede mediante llamadas usando el formato RESTful, o mediante API
  6. Soporte del metodo de programacion o framework Map Reduce introducido por Google para el analisis de datos.
  7. Consistencia.
Apache CouchDB

Hace unas semanas el grupo de usuarios de Python de Utah hizo una presentacion sobre esta base de datos y en ellas se demostro su funcionalidad en el desarrollo de una pequena aplicacion, tambien se hablo un poco de la historia de la base de datos y sus caracteristicas, las cuales pueden encontrarse en el sitio web oficial, pero basicamente CouchDB es una base de datos orientada a documentos.

A continuacion mostramos como instalar la aplicacion en Fedora 12, asi como la libreria de Python para acceder a la base de datos; luego un programa que accede a la base de datos y muestra como hacer ciertas operaciones con los datos de la misma.



[fdiaz@fedora12 ~]$ sudo yum install couchdb python-couchdb
[sudo] password for fdiaz:
Loaded plugins: presto, refresh-packagekit
adobe-linux-i386 | 951 B 00:00
rpmfusion-free-updates | 3.3 kB 00:00
rpmfusion-nonfree-updates | 3.3 kB 00:00
updates/metalink | 15 kB 00:00
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package couchdb.i686 0:0.10.0-2.fc12 set to be updated
--> Processing Dependency: libicu-devel for package: couchdb-0.10.0-2.fc12.i686
--> Processing Dependency: libjs.so.1 for package: couchdb-0.10.0-2.fc12.i686
--> Processing Dependency: erlang for package: couchdb-0.10.0-2.fc12.i686
---> Package python-couchdb.noarch 0:0.6.1-2.fc12 set to be updated
--> Running transaction check
---> Package erlang.i686 0:R13B-04.1.fc12 set to be updated
--> Processing Dependency: libodbc.so.2 for package: erlang-R13B-04.1.fc12.i686
---> Package js.i686 0:1.70-8.fc12 set to be updated
---> Package libicu-devel.i686 0:4.2.1-7.fc12 set to be updated
--> Running transaction check
---> Package unixODBC.i686 0:2.2.14-9.fc12 set to be updated
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
couchdb i686 0.10.0-2.fc12 fedora 513 k
python-couchdb noarch 0.6.1-2.fc12 updates 83 k
Installing for dependencies:
erlang i686 R13B-04.1.fc12 updates 33 M
js i686 1.70-8.fc12 fedora 346 k
libicu-devel i686 4.2.1-7.fc12 updates 616 k
unixODBC i686 2.2.14-9.fc12 updates 381 k

Transaction Summary
================================================================================
Install 6 Package(s)
Upgrade 0 Package(s)

Total download size: 35 M
Is this ok [y/N]: y
Downloading Packages:
Setting up and reading Presto delta metadata
updates/prestodelta | 689 kB 00:03
Processing delta metadata
Package(s) data still to download: 35 M
(1/6): couchdb-0.10.0-2.fc12.i686.rpm | 513 kB 00:02
(2/6): erlang-R13B-04.1.fc12.i686.rpm | 33 MB 03:16
(3/6): js-1.70-8.fc12.i686.rpm | 346 kB 00:02
(4/6): libicu-devel-4.2.1-7.fc12.i686.rpm | 616 kB 00:03
(5/6): python-couchdb-0.6.1-2.fc12.noarch.rpm | 83 kB 00:00
(6/6): unixODBC-2.2.14-9.fc12.i686.rpm | 381 kB 00:02
--------------------------------------------------------------------------------
Total 169 kB/s | 35 MB 03:30
Running rpm_check_debug
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
Installing : unixODBC-2.2.14-9.fc12.i686 1/6
Installing : erlang-R13B-04.1.fc12.i686 2/6
Installing : js-1.70-8.fc12.i686 3/6
Installing : libicu-devel-4.2.1-7.fc12.i686 4/6
Installing : couchdb-0.10.0-2.fc12.i686 5/6
Installing : python-couchdb-0.6.1-2.fc12.noarch 6/6

Installed:
couchdb.i686 0:0.10.0-2.fc12 python-couchdb.noarch 0:0.6.1-2.fc12

Dependency Installed:
erlang.i686 0:R13B-04.1.fc12 js.i686 0:1.70-8.fc12
libicu-devel.i686 0:4.2.1-7.fc12 unixODBC.i686 0:2.2.14-9.fc12

Complete!
You have new mail in /var/spool/mail/fdiaz
[fdiaz@fedora12 ~]$

Una vez la aplicacion se ha instalado, podemos iniciar el servicio mediante el siguiente comando:

[fdiaz@fedora12 ~]$ sudo /etc/init.d/couchdb start
Starting couchdb: Apache CouchDB has started, time to relax.
[ OK ]

Para acceder a la base de datos, hacemos uso de un explorador y nos dirigimos a la siguiente direccion:
http://localhost:5984/_utils/index.html

El servicio responde con una aplicacion web en la cual podremos acceder a la base de datos y hacer algunas operaciones, les invito a que le den un vistazo; pero programaticamente, podemos hacer uso del lenguaje de programacion Python para hacer las mismas operaciones como veremos a continuacion en el siguiente programa:

================ couchDBTest.py ===============

#! /usr/bin/python

import httplib, simplejson # Librerias para efectuar las llamadas HTTP y para acceder
# a los elementos json devueltos por la base de datos.

def Imprimir(s):
"""Imprimir muestra el resultado json de un objeto HTTP Respond"""
# Respuesta HTTP -> Diccionario de Python -> cadena
print simplejson.dumps(simplejson.loads(s.read()), sort_keys=True, indent=4)


class Couch:
"""Utilizaremos una clase para encapsular las acciones de la base de datos"""

def __init__(self, servidor, puerto=5984, options=None):
self.servidor = servidor
self.puerto = puerto

def conectar(self):
return httplib.HTTPConnection(self.servidor, self.puerto) # Hacemos una coneccion
# al servicio de la base de datos mediante metodos HTTP
# Operaciones basicas en la base de datos

def creaDB(self, nombreBD):
"""crea una nueva base de datos en el servidor"""

r = self.put(''.join(['/',nombreBD,'/']), "")
Imprimir(r)

def borraDB(self, nombreBD):
"""Elimina la base de datos del servidor"""
r = self.delete(''.join(['/',nombreBD,'/']))
Imprimir(r)

def listaDB(self):
"""Muestra un listado de las bases de datos disponibles en el servidor"""
Imprimir(self.get('/_all_dbs'))

def infoDb(self, nombreBD):
"""Muestra informacion sobre la base de datos"""
r = self.get(''.join(['/', nombreBD, '/']))
Imprimir(r)

# Operaciones sobre los documentos en la base de datos

def listDoc(self, nombreBD):
"""Muestra una lista de todos los documentos en la base de datos"""

r = self.get(''.join(['/', nombreBD, '/', '_all_docs']))
Imprimir(r)

def abreDoc(self, nombreBD, docId):
"""Abre un documento en una base de datos en particular"""
r = self.get(''.join(['/', nombreBD, '/', docId,]))
Imprimir(r)


def guardaDoc(self, nombreBD, contenido, docId=None):
"""Guarda o crea un documento en la base de datos especificada"""
if docId:
r = self.put(''.join(['/', nombreBD, '/', docId]), contenido)
else:
r = self.post(''.join(['/', nombreBD, '/']), contenido)
Imprimir(r)

def borraDoc(self, nombreBD, docId):
"""Elimina un documento, especificado por su docID, de la base de datos
segun la documentacion de CouchDB, la llamada debe identificar el documento
por medio de la revision del mismo, asi que primero hacemos un get segun el docid,
luego de-serializamos el objeto json, lo cual nos devuelve un diccionario y de el
extraemos el item '_rev' que es el parametro que pasamos para finalmente borrar
el documento.
"""
t = self.get(''.join(['/', nombreBD, '/', docId,]))
obj = simplejson.loads(t.read())
r = self.delete(''.join(['/', nombreBD, '/',docId,'?rev=',obj['_rev']]))
Imprimir(r)

# metodos HTTP

def get(self, uri):
c = self.conectar()
encabezado = {"Accept": "application/json"}
c.request("GET", uri, None, encabezado)
return c.getresponse()

def post(self, uri, contenido):
c = self.conectar()
encabezado = {"Content-type": "application/json"}
c.request('POST', uri, contenido, encabezado)
return c.getresponse()

def put(self, uri, contenido):
c = self.conectar()
if len(contenido) > 0:
encabezado = {"Content-type": "application/json"}
c.request("PUT", uri, contenido, encabezado)
else:
c.request("PUT", uri, contenido)
return c.getresponse()

def delete(self, uri):
c = self.conectar()
c.request("DELETE", uri)
return c.getresponse()

def main():
foo = Couch('localhost', '5984')
print "\nCreamos la base de datos 'bddoc':"
foo.creaDB('bddoc')

print "\nLista de bases de datos en el servidor:"
foo.listaDB()

print "\nCreamos un documento en la base de datos 'bddoc' con id: 00001:"
doc = """
{
"valor":
{
"Titulo":"Programacion Python y CouchDB",
"Autor":"FcoDiaz",
"Fecha":"2010-03-29T11:30:12-06:00",
"Tags":["couchDB", "python", "REST", "json"],
"Contenido":"Practicando un rato con CouchDB y Python.",
"Idioma":"Espanol"
}
}
"""
foo.guardaDoc('bddoc', doc,'00001')

doc2 = """
{
"valor":
{
"Titulo":"Programacion en C++",
"Autor":"FcoDiaz",
"Fecha":"2010-03-29T11:30:12-06:00",
"Tags":["C++", "Programacion"],
"Contenido":"Practicando un rato con C++.",
"Editorial":"FcoDiaz Publicaciones"
}
}
"""
print "\nCrea un segundo documento, el id sera seleccionado por la BD"
foo.guardaDoc('bddoc', doc2)

print "\nMuestra todos los documentos en la base de datos 'bddoc'"
foo.listDoc('bddoc')

print "\nRecuperamos el documento con codigo 00001 de la base de datos 'bddoc':"
foo.abreDoc('bddoc', '00001')
raw_input("Presiona ENTER para continuar")

print "\nBorramos el documento con codigo '00001' de la base de datos 'bddoc':"
foo.borraDoc('bddoc', '00001')


print "\nNuevamente mostramos los documentos en la base de datos 'bddoc'"
foo.listDoc('bddoc')
raw_input("Presiona ENTER para continuar")

print "\nInformacion de la base de datos 'bddoc':"
foo.infoDb('bddoc')

print "\nBorra la base de datos 'bddoc':"
foo.borraDB('bddoc')

print "\nLista de bases de datos en el servidor:"
foo.listaDB()

if __name__ == "__main__":
main()

==========
Para ejecutar el programa, lo guardamos y posteriormente podemos usar la siguiente linea de comandos para correr el programa, el resultado del programa puede verse a continuacion; he agregado algunas pausas para que mientras el programa espera por la tecla Return para continuar, se pueda observar el estado de la base de datos mediante el navegador, como se explico anteriormente y donde se podran observar la nueva base de datos y los documentos creados.

[fdiaz@fedora12 Python]$ python couchDBTest.py

Creamos la base de datos 'bddoc':
{
"ok": true
}

Lista de bases de datos en el servidor:
[
"bddoc"
]

Creamos un documento en la base de datos 'bddoc' con id: 00001:
{
"id": "00001",
"ok": true,
"rev": "1-7ddb0ea5569142bae2ce8879d56adb97"
}

Crea un documento, el codigo sera seleccionado por la BD
{
"id": "d5907db4ebd8fb240a8be36e24d56c43",
"ok": true,
"rev": "1-75ca9f69056f030781dfefd128c664c9"
}

Muestra todos los documentos en la base de datos 'bddoc'
{
"offset": 0,
"rows": [
{
"id": "00001",
"key": "00001",
"value": {
"rev": "1-7ddb0ea5569142bae2ce8879d56adb97"
}
},
{
"id": "d5907db4ebd8fb240a8be36e24d56c43",
"key": "d5907db4ebd8fb240a8be36e24d56c43",
"value": {
"rev": "1-75ca9f69056f030781dfefd128c664c9"
}
}
],
"total_rows": 2
}

Recuperamos el documento con codigo 00001 de la base de datos 'bddoc':
{
"_id": "00001",
"_rev": "1-7ddb0ea5569142bae2ce8879d56adb97",
"valor": {
"Autor": "FcoDiaz",
"Contenido": "Practicando un rato con CouchDB y Python.",
"Fecha": "2010-03-29T11:30:12-06:00",
"Idioma": "Espanol",
"Tags": [
"couchDB",
"python",
"REST",
"json"
],
"Titulo": "Programacion Python y CouchDB"
}
}
Presiona ENTER para continuar

Borramos el documento con codigo '00001' de la base de datos 'bddoc':
{
"id": "00001",
"ok": true,
"rev": "2-cf6064d2287d8c685d90eecebf58dfb6"
}

Nuevamente mostramos los documentos en la base de datos 'bddoc'
{
"offset": 0,
"rows": [
{
"id": "d5907db4ebd8fb240a8be36e24d56c43",
"key": "d5907db4ebd8fb240a8be36e24d56c43",
"value": {
"rev": "1-75ca9f69056f030781dfefd128c664c9"
}
}
],
"total_rows": 1
}
Presiona ENTER para continuar

Informacion de la base de datos 'bddoc':
{
"compact_running": false,
"db_name": "bddoc",
"disk_format_version": 4,
"disk_size": 4589,
"doc_count": 1,
"doc_del_count": 1,
"instance_start_time": "1269893376165905",
"purge_seq": 0,
"update_seq": 3
}

Borra la base de datos 'bddoc':
{
"ok": true
}

Lista de bases de datos en el servidor:
[]
[fdiaz@fedora12 Python]$



Algunas cosas que se observan de al menos los dos elementos de datos presentados es que la estructura de datos utilizada es un diccionario de Python y que los elementos de cada uno de los datos pueden variar, como es el caso del Idioma y Editorial, lo cual da una gran flexibilidad a los datos en caso que estos cambien constantemente de forma y estructura. Las bases de datos no relacionales han existido desde hace ya buen rato, de hecho yo trabaje en una llamada Minisis, alla por los 90's, y la base de datos era execelente para almacenar el catalogo de libros de una biblioteca; como podran ver la orientacion de estas bases de datos es hacia los documentos, donde estos forman un elemento primordial en sistemas recientes que ademas abarcan una gran cantidad de usuarios como lo por ejemplo las redes sociales. Tambien puede ser utilizada en sistemas no tan complejos que requiran bases de datos sencillas y donde la recuperacion de datos esta mas enfocada en busquedas sobre la llave primaria.

Finalmente algunas companias que estan haciendo el cambio a este tipo de bases de datos tenemos:

Facebook y Twitter: Cassandra
Google: Big Table
LinkedIn: Voldemort


Mas informacion sobre CouchDB: http://couchdb.apache.org/
O sobre la libreria de Python para CouchDB: http://wiki.apache.org/couchdb/Getting_started_with_Python

Hasta la proxima.

No comments: