Integración de Kumbia Enterprise con Apache Cassandra
Publish at Monday, April 12, 2010
By Core Developers - Louder
¿Qué es Cassandra?
Cassandra es una base de datos hibrida no-relacional liberada por Facebook y
actualmente administrada y mantenida por la fundación Apache en su incubadora.
Ofrece muchas más posibilidades que una simple base de datos clave-valor como
Redis, pero tiene menos facilidades de consulta que
MongoDB.
Cassandra es actualmente usada por
Digg,
Facebook,
Twitter,
Reddit,
Rackspace, Cisco
y muchas otras grandes empresas con altos volúmenes de datos y necesidades de escalabilidad.
Uno de los clusteres en producción más grandes tienen alrededor de 100TB sobre 150 maquinas.
Recientemente Cassandra empezó a ser adoptada por sitios web con gran tráfico como Twitter y Digg como
un reemplazo de MySQL a mediano plazo.
¿Qué no es Cassandra (y las NoSQL)?
Cassandra y las bases de datos NoSQL en general, no son un reemplazo para las bases de datos relacionales (RBDM).
Cualquier sistema en donde la prioridad sea la integridad de los datos no debe ser implementado con una base de
datos NoSQL, de ninguna manera. Cassandra en este caso seguramente es gran opción para sitios con alto tráfico y
necesidad de escalar en donde no de manejan datos no-criticos.
Características de Cassandra
Esquema Fléxible: siendo un document store, no es necesario definir que campos debe llevar
cada registro ó que tipos de datos deben manejar. Es posible agregar ó quitar campos en cualquier
momento lo que representa un avance muy importante en productividad.
Escalabilidad Real: Cassandra escala horizontalmente en todo el sentido de la palabra. Agregar
más poder a cassandra es solamente colocar otra maquina, sin reiniciar nada, copiar datos ó modificar
las aplicaciones.
Lectura/Escritura Distribuida: Es posible leer ó escribir en cualquier maquina con cassandra dentro
de un cluster y obtener el mismo resultado. Todos los datos son replicados en todas las maquinas
muy rápidamente.
Rendimiento: Cassandra es 10 veces más rápido para escribir y 9 veces más rápido que MySQL
al tratar con altos volúmenes de datos (> 50GB).
¿Qué diferencia a Cassandra de los demas bases de datos NoSQL?
Cassandra está diseñada bajo un principio que va más alla de almacenar datos mediante claves-valores.
Con esta base de datos es posible almacenar datos en esquemas multidimensionales (5-dimensiones)
ofreciendo mayor flexibilidad para organizar y consultar los datos. A diferencia los modelos relacionales
presentan tablas de 2 dimensiones, agregar otra dimensión requiere agregar otra tabla.
Integración con Kumbia Enterprise
Anteriormente ya habiamos integrado Kumbia Enterprise con
MongoDB,
por lo que se había agregado una nueva capa de acceso a bases de datos llamada nosql al
componente Db.
En el caso de Cassandra hemos trabajado en crear una integración más completa la cuál incluye:
un manejador de modelos (ODM?) para bases de datos de este tipo, un adaptador de
Session,
un componente de Traslate
(traducción), optimización para almacenar los
ACL (listas de control de acceso) y las credenciales de
autenticación con Auth.
Cassie, un ODM(?) para Cassandra
El componente Cassie es un (object-document-mapper) que facilita en gran medida al trabajo de "ver"
bases de datos Cassandra como las tradicionales relacionales. Cassie actua como una capa superior funcional
que implementa algunos comportamientos de las bases de datos relacionales.
Con Cassie es posible:
Definir modelos como los utilizados en ActiveRecord
Crear, actualizar, Consultar y Borrar registros
Definir relaciones (1-1, 1-n, n-1, n-m) entre modelos de Cassandra y modelos ActiveRecord
Cassie, también extiende las capacidades de Cassandra al permitir:
Definir llaves primarias para controlar la duplicidad. Las claves en Cassandra tienen un scope-global, con Cassie
es posible que 2 modelos diferentes tengan un mismo valor de llave primaria.
Definir columnas auto-numéricas en los modelos. Cassandra no tiene columnas auto-numéricas.
Definir indices a partir de las columnas de los modelos en cualquier momento.
Crear un Blog con Cassandra y Kumbia Enterprise
Ya hemos visto como Arin Sarkissian, el principal desarrollador de Digg ha enseñado como crear un esquema para almacenar los
datos de un blog usando Cassandra.
El objetivo del siguiente tutorial es ilustrar la integración de Cassie con Cassandra como un medio
transparente y familiar a como se ha venido trabajando con ActiveRecord.
Cassie almacena los datos de los modelos de una forma parecida pero a la vez un poco diferente con
el fin de ofrecer un nivel de flexibilidad superior. En este tutorial se van a utilizar 2 modelos: "Posts",
para almacenar los artículos y "Comments" para los comentarios.
Agregar un Keyspace a Cassandra
Los Keyspaces (espacios de nombres) se usan para agrupar familias de columnas en Cassandra. Un Keyspace podría
decirse que son las "bases de datos" como se conocen en las relacionales.
Un keyspace que vaya a ser usado con Cassie debe tener la siguiente estructura:
Esta configuración se agrega al archivo conf/storage-conf.xml de Cassandra. Después de agregar
la configuración se requiere reiniciar el servidor de cassandra.
A diferencia de otros ejemplos que se puedan encontrar en internet, este keyspace
define 3 familias de super columnas donde se almacenarán los datos, indices y meta-datos
de los modelos respectivamente.
Crear la Aplicación
Creamos el esqueleto de la aplicación usando el script. La aplicación se llamará "blog". También
se hubiera podido llamar "default" pero usaremos "blog" en este tutorial:
php scripts/create_application.php --name blog
Con el anterior comando se creó el esqueleto de la aplicación. Ahora se edita el archivo apps/blog/config/enviroment.ini
y se configura la conexión a cassandra en el entorno de desarrollo:
La configuración hace referencia al keyspace destinado a almacenar los datos del blog, junto con el nodo de cassandra
que utilizará la aplicación.
Cargar la libreria Thrift
Thrift es una libreria originalmente desarrollada por Facebook
y mantenida actualmente por Apache, que permite el
intercambio de datos entre diferentes lenguajes y que en nuestro caso proporciona el acceso de bajo nivel a Cassandra desde PHP.
Utilizamos el archivo apps/blog/config/boot.ini para cargar está libreria que se incluye con el framework
para cualquier petición a la aplicación:
[modules]
extensions = "Thrift.Thrift"
Definir los modelos
Como se mencionó se van a utilizar los modelos "Posts" y "Comments" en este blog, por lo que debemos crear los
modelos de la siguiente forma:
//apps/blog/models/posts.php
class Posts extends CassieRecord {
}
//apps/blog/models/comments.php
class Comments extends CassieRecord {
}
De momento los modelos no requieren ninguna inicialización en especial. Tampoco se requiere definir los atributos
que van a tener ya que estos pueden variar de un registro a otro.
Crear Posts
El Controlador PostsController nos va a permitir administrar los posts del blog:
<php
class PostsController extends ApplicationController {
//Acción por defecto del controlador
public function indexAction(){
//Si no hay "Posts" creados ir a la acción "new"
$numeroPostsCreados = $this->Posts->count();
if($numeroPostsCreados==0){
$this->routeTo("action: new");
} else {
//Si hay alguno ir a la acción "list"
$this->routeTo("action: list");
}
}
//Acción para crear "Posts"
public function newAction(){
$request = $this->getRequestInstance();
//Ingresaron el campo titulo?
if($request->isSetPostParam("titulo")){
$post = new Posts();
$post->titulo = $request->getParamPost("titulo");
$post->contenido = $request->getParamPost("contenido");
if($post->save()==false){
foreach($post->getMessages() as $message){
Flash::error($message->getMessage());
}
} else {
Flash::success("Se creó el post con éxito");
return $this->routeTo("action: list");
}
}
}
//Acción para listar los existentes
public function listAction(){
}
//Acción para editar "Posts" existentes
public function editAction($postId){
$postId = $this->filter($postId, "int");
if($postId>0){
$post = $this->Posts->findFirst($postId);
if($post!=false){
//Si la petición es por GET mostramos los campos actuales sino
//se actualizan según lo ingresado
$request = ControllerRequest::getInstance();
if($request->isGet()==true){
Tag::displayTo("titulo", $post->titulo);
Tag::displayTo("contenido", $post->contenido);
} else {
$post->titulo = $request->getParamPost("titulo");
$post->contenido = $request->getParamPost("contenido");
if($post->save()==false){
foreach($post->getMessages() as $message){
Flash::error($message->getMessage());
}
} else {
Flash::success("Se actualizó el post con éxito");
return $this->routeTo("action: list");
}
}
} else {
$this->routeTo("action: index");
}
} else {
$this->routeTo("action: index");
}
}
//Acción para eliminar "Posts" existentes
public function deleteAction($postId){
$postId = $this->filter($postId, "int");
if($postId>0){
$post = $this->Posts->findFirst($postId);
if($post!=false){
if($post->delete()==false){
foreach($post->getMessages() as $message){
Flash::error($message->getMessage());
}
} else {
Flash::success("Se eliminó el post con éxito");
return $this->routeTo("action: list");
}
} else {
$this->routeTo("action: index");
}
} else {
$this->routeTo("action: index");
}
}
}