lunes, 24 de enero de 2011

Amazon S3

Amazon tiene espacio de almacenamiento en Internet: Amazon Simple Storage Service (Amazon S3). Proporciona una interface que puede utilizarse para almacenar y recuperar datos, en cualquier momento y en cualquier parte de la web.

La funcionalidad y datos prácticos los podemos encontrar en el siguiente link: http://aws.amazon.com/es/s3/#functionality

Estos son los principales pasos a seguir en el almacenamiento de un objeto:

(http://docs.amazonwebservices.com/AmazonS3/latest/gsg/)

En la web en la que trabajo hemos subido los videos y las imágenes a Amazon, por lo que tuve que hacer un pequeño proyecto para hacer el upload desde la web y el backoffice.

Lo primero que hice fue instalar el Plugin de AWS para Eclipse (http://aws.amazon.com/es/eclipse/), y lo que aconsejan en su web, crear un “sample code”. Es una forma sencilla de empezar a probar la API.

Una vez familiarizada con las librerías, creé un Project para el upload de los videos y las imágenes. Este se compone de dos ficheros: una clase que he llamado s3Access y un fichero de properties.

El fichero de properties contiene la Secret Key y la Access Key de Amazon. De esta forma, si por alguna razón cambian no es necesario modificar el proyecto.

La clase la explico un poco, quitando código superfluo, así no alargo demasiado el post.

A partir de este proyecto se generará un JAR que incluiremos en el path de la web, de forma que crearemos un objeto en la web de la clase s3Access, y llamaremos a sus métodos según convenga.

La clase se compone de un constructor que al ser llamado creará el objeto S3 y cargará los buckets (espacios propios en Amazon):

public S3Access() {

this.s3 = new AmazonS3Client(new PropertiesCredentials(

S3Access.class.getResourceAsStream(awsCredentialsProperties)));

loadMXMBuckets();

}

private void loadMXMBuckets() {

for (Bucket bucket : this.s3.listBuckets()) {

__for (int i=0; i< listObjectType.length; i++) {

____if (bucket.getName().indexOf(listObjectType[i]) != -1) {

______ listMXMBuckets.put(listObjectType[i], bucket.getName());

____ }

__}

}

}

El método uploadFile hace el upload de un fichero en una carpeta en un bucket determinado, y devuelve la url de acceso a ese fichero:

public String uploadFile (String filePath, String objectType, String folderName) {

__String urlFile = null;

__String bucketName = this.listMXMBuckets.get(objectType);

__String key = extractNameFileFromPath(filePath);

__String pathBucket = bucketName + "/" + folderName;

__try {

____File file = new File(filePath);

____s3.putObject(new PutObjectRequest(pathBucket, key, file));

____s3.setObjectAcl(pathBucket, key,

CannedAccessControlList.PublicRead);

____urlFile = urlAmazonAws + pathBucket + "/" + key;

__} catch (AmazonServiceException ase) {

__} catch (AmazonClientException ace) {

__} catch (Exception e) {

__}

return urlFile;

}

Un método interesante para el backoffice sería el de borrar un objeto:

public boolean deleteObject (String filePath, String objectType, String folderName) {

__String bucketName = this.listMXMBuckets.get(objectType);

__String pathBucket = bucketName + "/" + folderName;

__boolean objectDeleted = true;

__try {

____this.s3.deleteObject(pathBucket, filePath);

__} catch (AmazonClientException e) {

__objectDeleted = false;

__}

__return objectDeleted;

}

Recomiendo ver el video: http://media.amazonwebservices.com/videos/eclipse-java-sdk-video.html

domingo, 23 de enero de 2011

NoSQL: El fin de las Bases de Datos relacionales?

El primer día del curso de J2EE el profe nos dijo que a las Bases de Datos le quedaban 10 años de vida y la idea me entusiasmó.

Y cuanto más pienso en ello, más sentido le encuentro.

Para extraer datos de la BD tenemos que hacer un acceso a un repositorio externo, algo ajeno a nuestra aplicación.

Además tienen un lenguaje propio, el SQL, que nos obliga a tratar nuestros objetos de una manera poco lógica e intuitiva. Se pierde mucho tiempo en lograr queries de bajo coste, a veces sacrificando un código limpio y sencillo.

Cuánto podríamos mejorar el rendimiento de nuestra aplicación si tuviéramos los datos más accesibles?

He buscado información al respecto y encontré lo que llaman NoSQL.

Amazon creó su propia base de datos NoSQL, de código cerrado, y de uso exclusivo: Amazon Dynamo. Fue en octubre de 2007 cuando se publicó un paper con el diseño y la especificación a grandes rasgos de Dynamo, rompiendo con conceptos como la consistencia o modelo relacional. Su objetivo se definió claramente: escalabilidad y disponibilidad.

Garantizan en el 99.9% de los accesos, un tiempo de respuesta menor de 300ms, aunque el servicio esté caído.

http://www.nosql.es/blog/nosql/amazon-dynamo.html

Google empezó el desarrollo de BigTable en 2004, un almacen de datos masivo, para proveer a sus propias aplicaciones. Cuenta con una API de uso público, por lo que cualquier desarrollador puede trabajar con él.

Uno de los clusters más grande de BigTable gestiona 6 petabytes de datos sobre miles de servidores.

http://labs.google.com/papers/bigtable.html

El proyecto Cassandra de Apache desarrolla una base de datos distribuida de segunda generación altamente escalable.

Está en uso en Digg, Facebook, Twitter, Reddit, Rackspace, Cloudkick, Cisco, SimpleGEO, Ooyala, OpenX, etc.

Según escribe Avinash Lakshaman, ingeniero de Facebook, Cassandra puede escribir hasta 50GB de datos en disco en tan solo 0.12ms, más de 2500 veces más rápido que MySQL.

http://cassandra.apache.org/

Adobe utiliza Hbase, porque según ellos necesitan un sistema de almacenamiento de datos estructurado, genérico, y a tiempo real, que pueda manejar cualquier volumen de datos, con tiempos de acceso menores a 50ms, sin pérdida de datos.


http://hstack.org/why-were-using-hbase-part-1/

http://hstack.org/why-were-using-hbase-part-2/

Hay más proyectos en marcha: Hadoop, Voldemort, Dynomite, etc.

Dejo unos cuantos links que me han parecido interesantes.


NoSQL: el movimiento en contra de las bases de datos
1 billion reasons why adobe chose HBase
Google BigTable blog NoSql
The Database tea party: The NoSQL Movement
NoSQL debrief

miércoles, 19 de enero de 2011

Composición JS y CSS

En una web una de las cosas más importantes es la velocidad de carga de cada una de las páginas que la componen, por lo que tenemos que limitar al mínimo las llamadas a otras url dentro de una página.

Propongo e siguiente escenario en desarrollo: tenemos diferentes archivos JS en el proyecto estructurados por funcionalidad, también tendremos ficheros con funciones comunes, y por último ficheros con funciones que se usan en más de una página.

Si los dividimos de ese modo, es más fácil saber donde tenemos cada función, evitamos duplicarlas, y solo cargamos las necesarias para la página solicitada.

La opción de tener todo el código JS en un solo fichero no es la más eficiente porque tendríamos que cargar un fichero mucho más pesado, con funciones que en realidad no vamos a usar en todas las páginas.

Con un ejemplo se verá claro:

El fichero validacionAlta.js contendrá las funciones JS necesarias para el alta en la web, y solo se cargará en las páginas de alta. Por otro lado los ficheros validacionBuscador.js y validacionLogin.js contienen las funciones necesarias para el buscador y el login, y estas funcionalidades están en todas las páginas de la web, por lo que son necesarios en todas ellas. El fichero validacionForm.js tiene funciones varias que servirán para todos los formularios de la web.

En desarrollo se cargarán por separado, pero en la web del alta los agruparemos de forma que haremos una sola llamada al fichero que podríamos llamar composed_alta_20110101.js.

La fecha actual servirá para tener diferentes versiones del fichero cada vez que lo generemos. De ese modo cuando hayan cambios y tengamos que subirlos a la web, el nuevo fichero se cargará con todos los cambios (pensemos que un fichero JS se guarda en la caché, y no se recarga a menos que se haga expresamente. Está claro que los usuarios al no conocer dichos cambios no harán el ctrol+F5).

Lo primero que hice fue reestructurar los JS que teníamos en el proyecto en ese momento, de forma que quedaran bien separados por funcionalidades y no hubiera duplicados.

Lo siguiente era revisar página a página los archivos JS que se estaban cargando, y pensar en las posibles agrupaciones.

En las páginas se elegirán los JS a incluir en función de un booleano, en pseudo-código (en desarrollo) sería:

// Si estamos en una de las páginas del alta

if (bJSAlta) {

include ‘validacionAlta.js’

include ‘validacionBuscador.js’

include ‘validacionForms.js’

include ‘validacionLogin.js’

// En cualquier otra página de la web

else {

include ‘validacionBuscador.js’

include ‘validacionLogin.js’

}

Y en producción sería:

if (bJSAlta) {

include ‘composed_alta_.js’

else {

include ‘composed_all_.js’

}

Para diferenciar entre desarrollo y producción podríamos tener una variable Application, bTest, que podríamos consultar para incluir un código u otro.

A partir de aquí empezamos con la composición propiamente dicha. Lo he automatizado de con un proceso hecho en Java, para no tener cada vez que revisar los cambios hechos por cada uno de los componentes del equipo técnico.

Ese proceso tiene programado lo siguiente:

Parseo del fichero de desarrollo que contiene los includes de los JS por funcionalidad. Cada vez que encontremos el if guardamos los includes y como key el boleano.

Merge de los archivos agrupados por la key de funcionalidad.

Compresion de los ficheros agrupados.

Modificamos el fichero que contiene los includes de los Compuestos JS, con la nueva fecha.

De esta forma cuando hayan cambios en algún JS se habrá modificado el fichero que contiene los includes, y por lo tanto al parsearlo trabajaremos siempre con los últimos cambios.

A continuación concatenamos los archivos por cada funcionalidad, y los comprimimos.

La modificicación del fichero que contiene los includes de producción nos ahorra la tarea de cambiar la versión una vez generado el nuevo nombre del fichero compuesto.

El compresor que he utilizado es YUI Compressor, que sirve tanto para JS como para CSS, y es gratis.

lunes, 17 de enero de 2011

Fechas en Java (Date, Calendar)

Me parece que no soy la única persona que se queja de como está montado todo el tema de fechas en Java. Para obtener algunas fechas hay que hacer algunas operaciones extra que "ensucian" el código.

Un día creé una clase propia Fecha para no tener que repetir el mismo código en diferentes clases. La verdad es que lo he ido montando en función de lo que iba necesitando, así que hay un poco de todo.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Fecha {

__private static String formatoDefecto = "yyyy/MM/dd HH:mm:ss";

__public static Date getDate(String sFecha) {
__ __SimpleDateFormat sdf = new SimpleDateFormat(formatoDefecto);
__ __Date fecha;
__ __try {
__ __ __fecha = sdf.parse(sFecha);
__ __} catch (ParseException e) {
__ __ __fecha = null;
__ __}

__ __return fecha;
__}

__public static Date getDate(String sFecha, String formato) {
__ __SimpleDateFormat sdf = new SimpleDateFormat(formato);
__ __Date fecha;
__ __try {
__ __ __fecha = sdf.parse(sFecha);
__ __} catch (ParseException e) {
__ __fecha = null;
__ __}
__ __return fecha;
__}

__public static String getDateStringNDaysAgo(int nDaysAgo, String formato) {
__ __SimpleDateFormat sdf = new SimpleDateFormat(formato);

__ __Calendar dateEnd = Calendar.getInstance();
__ __dateEnd.add(Calendar.DATE, nDaysAgo * (-1));

__ __return sdf.format(dateEnd.getTimeInMillis());
__}

__public static Calendar getCalendarNDaysAgo(int nDaysAgo) {
__ __Calendar dateEnd = Calendar.getInstance();
__ __dateEnd.add(Calendar.DATE, nDaysAgo * (-1));

__ __return dateEnd;
__}

__public static Calendar getCalendar(String sFecha, String formato) {
__ __Calendar calendar = Calendar.getInstance();
__ __Date dateStart = getDate(sFecha,formato);
__ __calendar.setTime(dateStart);

__ __return calendar;
__}

__public static String getToday() {
__ __Date date = Calendar.getInstance().getTime();
__ __SimpleDateFormat sdf = new SimpleDateFormat(formatoDefecto);

__ __return sdf.format(date);
__}

__public static String getToday(String formato) {
__ __ Date date = Calendar.getInstance().getTime();
__ __SimpleDateFormat sdf = new SimpleDateFormat(formato);

__ __return sdf.format(date);
__}

__public static String getDateString (Calendar cFecha) {
__ __SimpleDateFormat sdf = new SimpleDateFormat(formatoDefecto);

__ __return sdf.format(cFecha.getTimeInMillis());
__}

__public static String getDateString (Calendar cFecha, String formato) {
__ __SimpleDateFormat sdf = new SimpleDateFormat(formato);

__ __return sdf.format(cFecha.getTimeInMillis());
__}

__public static String getDateString (Date dFecha, String formato) {
__ __SimpleDateFormat sdf = new SimpleDateFormat(formato);

__ __return sdf.format(dFecha);
__}

__public static Calendar getCalendar(Date dFecha) {
__ __Calendar calendar = Calendar.getInstance();
__ __calendar.setTime(dFecha);

__ __return calendar;
__}
}

Las clases que utilzo son java.util.Date y java.util.Calendar, y para el formato java.text.SimpleDateFormat.

Date getDate(String fecha)
A partir de un String devolvemos un Date con formato. El método está overide dependiendo de si le explicito el formato o no.

Para esto utilizamos la clase SimpleDateFormat, y transformamos a Date con el método parse() de la misma.

Calendar getCalendar(Date fecha)
A partir de una fecha tipo Date devolvemos una fecha tipo Calendar. Este método también está overide dependiendo de si le explicito el formato o no.

Calendar getCalendarNDaysAgo(int ndaysAgo)
En este método obtenemos la fecha y le restamos los días especificados, y devolvemos el Calendar.

String getDateStringNDaysAgo(int ndaysAgo, format)

En este caso devolvemos un String con una fecha anterior a la actual. Así que con SimpleDateFormat obtenemos la fecha, y devolvemos la fecha en String con el formato deseado.

String getToday()
Devuelve un String con la fecha actual. El método está overide dependiendo de si le explicito el formato o no.

String getDateString (Calendar cFecha)
Devuelve un String partiendo de una fecha Calendar. Este método también está overide para explicitar el formato, o si le pasamos la fecha Date.

martes, 11 de enero de 2011

SQL: row_number over partition

Hoy voy a hacer una entrada sobre una consulta en Oracle que me parece muy interesante, utilizando la funcion row_number over partition.

Se utiliza para ordenar registros en grupos.

Lo más sencillo es que lo explique con un ejemplo: Necesitamos el primer curso publicado por un autor.

Nos ponemos en situación... un autor tiene una serie de cursos publicados, tenemos que hacer una consulta de forma que tengamos los cursos de cada autor, y sacar de alguna forma cual fue el primero que publicó.

Podríamos hacer la consulta completa de todos los autores y sus cursos, y por programa sacar el más reciente de cada uno. Pero esa consulta tiene un coste muy alto, y hay que añadirle además el coste de recorrer todo el resultset.

La mejor opción sería utilizar el row_number over partition:

row_number() over (order by col_1 [, col_2...])

row_number() over (partition by col_n [, col_m...] order by col_1 [, col_2...])


En el ejemplo la consulta sería:

select id_autor, nombre_autor, id_curso, titulo_curso
from (
__select cursos.id_autor, nombre_autor, id_curso,
____titulo_curso, fecha_publi
____row_number() over
______(partition by cursos.id_autor order by fecha_publi) ncurso
__from autores
__left join cursos
__on autores.id_autor=cursos.id_autor) cursos_autor
where ncurso=1;

Vemos que hacemos la join entre cursos y autores para relacionarlos, y en un campo asginamos un row_number particionado por el id_autor, y ordenado por la fecha_publicacion. La subconsulta con el row_number devuelve lo siguiente:

Vemos que el campo ncurso es un número correlativo por autor, y se asigna por el orden de la fecha_publicacion.

En la consulta completa vemos que hemos seleccionado solo los cursos que tienen ncurso igual a 1, es decir, el primero que son los que tienen la fecha más antigua.