viernes, 30 de noviembre de 2012

Enlaces sobre herramientas de desarrollo


Branching con Git muy bien explicado

Flujos de Git

Uno de mis vicios. Los juegos

En los siguientes lugares encontrarás miles de horas de diversión.

Humble Bundle
Elige lo que quieres pagar por los juegos, cada cierto tiempo ofrecen packs muy interesantes. Puedes suscribirte para que te avisen.


GOG
Juegos que nunca mueren muy muy buenos. Podrás jugar en los modernos ordenadores juegos clasicos.

Steam
El monopolio de las descargas de juego. Todo tipo de juegos antiguos, nuevos, indies. Suele haber ofertas por temporada y algunos días.

lunes, 26 de noviembre de 2012

Deserializacion de JSON con la librería de Java Jackson

Después de probar varias librerías para Java que me ayudasen a serializar/deserializar JSON, me decanté por Jackson por ser la más completa y que además me permitía el acceso a bajo nivel.

El motivo de esta entrada es enseñar la deserialización usando el acceso a bajo nivel (Streaming) y combinarlo con el acceso de más alto nivel (Data Object binding)

Nuestro objeto JSON se compone de un Objeto con varias propiedades, una de las cuales es un array de otro tipo de objecto:

{"usuarioId":1,"color":4,
"lista":[{"tipo":1,"id":1005,"cantidad":3},
{"tipo":1,"id":1006,"cantidad":4},
{"tipo":1,"id":1007,"cantidad":2}]}

En el siguiente trozo de código vamos a parsear el JSON recibido en un parámetro "data" de la llamada de un webservice:

public void webservice( HttpServletRequest request, HttpServletResponse response ) {
int error = 0;
    
 
JsonParser jp = null;

try {
String datos = request.getParameter("data");
JsonFactory f = new MappingJsonFactory();
jp = f.createJsonParser(datos);

JsonToken current;

current = jp.nextToken();
if (current != JsonToken.START_OBJECT) {
System.out.println("Error: root should be object: quiting.");
return;
}

while (jp.nextToken() != JsonToken.END_OBJECT) {
String fieldName = jp.getCurrentName();
// move from field name to field value
current = jp.nextToken();

if ( fieldName=="usuarioId" ) {
System.out.println("Procesando: " + fieldName + " valor: " + jp.getValueAsString());
jp.skipChildren();
} else if (fieldName=="color") {
System.out.println("Procesando: " + fieldName + " valor: " + jp.getValueAsString());
jp.skipChildren();
} else if (fieldName.equals("lista")) {
if (current == JsonToken.START_ARRAY) {
// For each of the records in the array
while (jp.nextToken() != JsonToken.END_ARRAY) {
// read the record into a tree model,
// this moves the parsing position to the end of it

//JsonNode node = jp.readValueAsTree();
// And now we have random access to everything in the object
//System.out.println("tipo: " + node.get("tipo").asText());
//System.out.println("id: " + node.get("id").asText());

// prueba sobre un objeto
Item item=jp.readValueAs(Item.class);
log.error(" probando " + item.toString() );

}
} else {
System.out.println("Error: records should be an array: skipping.");
jp.skipChildren();
}
} else {
System.out.println("Unprocessed property: " + fieldName);
jp.skipChildren();
}
}
jp.close(); // ensure resources get cleaned up timely and properly  
....

La clase Item sería:

public class Item implements Serializable {
private Long id;
private Long tipo;
private Long cantidad;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Long getTipo() {
return tipo;
}

public void setTipo(Long valor) {
this.tipo = valor;
}

public Long getCantidad() {
return cantidad;
}

public void setCantidad(Long valor) {
this.cantidad = valor;
}

@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
hash += (tipo != null ? tipo.hashCode() : 0);
return hash;
}

@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
  if (!(object instanceof Item)) {
return false;
}
  Item other = (Item) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}

@Override
public String toString() {
  return "com.jpa.Item[ id=" + id + " ]";
}

}


Referencias:

http://outerthought.org/blog/415-ot.html

viernes, 28 de septiembre de 2012

curl script

Si necesitas hacer repetidas llamadas curl via script Linux:

curl -S -w "%{url_effective}\n" -L "http://dominio.com:7080/api/pepe.php?apikey=56de&parametro1=390&parametro2=elquesea"

miércoles, 19 de septiembre de 2012

Componer fechas

Como componer en postgres una fecha con partes de otras fechas:

select date (
date_part('year' ,current_timestamp) || '-' ||
date_part('month',current_timestamp) || '-' ||
date_part('day' ,current_timestamp));


jueves, 6 de septiembre de 2012

Redes Servidores y UDP confiable

Pregunta sobre Reliable UDP con interesantes comentarios:
http://stackoverflow.com/questions/2940106/simple-reliable-udp-c-libraries

Boost.Asio

http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio.html

Librería muy utilizada encuadrada dentro del framework Boost con múltiples utilidades. Tiene sockets asincronos, ejemplo de un servidor de chat,

UDP - UDP reliable protocol

http://udt.sourceforge.net/index.html

Se parece mucho a las funciones normales de sockets, minima barrera de entrada. Usado por Raknet y otros frameworks. El mejor protocolo para envío de mucha información en redes de amplio ancho de banda.

NOTA: en documentos de paco hay un ejemplo de implementación de EPOLL.

lunes, 3 de septiembre de 2012

Estadísticas de acierto de la cache

Es muy interesante saber en nuestra base de datos cuantas veces encontramos en la caché el resultado de las consultas. Si tenemos valores altos, seguro que no accedemos al disco a buscar los resultados y por lo tanto nuestra base de datos está funcionando bien.

Vamos a ver el porcentaje de acierto a nivel de base de datos:

SELECT d.datname, pg_database_size(d.datname), SUM(pg_stat_get_db_blocks_hit(d.oid)) / SUM(pg_stat_get_db_blocks_fetched(d.oid)) AS hit_rate
FROM pg_database d
GROUP BY d.datname
HAVING SUM(pg_stat_get_db_blocks_fetched(d.oid)) > 0

Pero aún más interesante es ver el resultado a nivel de tabla:

select t.schemaname, t.relname, heap_blks_hit*100/(heap_blks_hit+heap_blks_read) as BCHR
from pg_statio_user_tables t
where t.heap_blks_read > 0

Si tus tablas más accedidas tienen valores superiores a 95% todo va bien.


Sino revisa tus valores de "shared_buffers" y "effective_cache_size", si son correctos quizá tengas que revisar tus sentencias o aumentar tu memoria.

Referencias:
http://blog.kimiensoftware.com/2011/05/postgresql-vs-oracle-differences-4-shared-memory-usage-257

jueves, 30 de agosto de 2012

Indices OnlyScan

A partir de Postgres 9.2 tenemos índices que si contienen los campos necesarios de una consulta, no acuden a la tabla a recuperar la información, sino que la recuperan directamente del índice. Para disfrutar de esta mejora tenemos que activar la consulta de este modo:
 
SET enable_indexonlyscan TO true;

Podeis ver ejemplos de uso en: 
http://michael.otacoo.com/postgresql-2/postgresql-9-2-highlight-index-only-scans/

Tened cuidado al utilizarlo para no crear índices sobre campos que se actualicen muy a menudo, ya que perderemos rendimiento durante las actualizaciones e inserciones y no compensará la ganancia sobre las consultas.

Enlaces útiles de Postgres

Analizador de logs

http://michael.otacoo.com/postgresql-2/postgres-pgbadger-sneaking-in-log-files-for-you/#comment-10881

miércoles, 29 de agosto de 2012

Estadisticas Postgres

Es interesante revisar las estadísticas de acceso a cada tabla de tu base de datos:

select * from pg_stat_database;
select * from pg_stat_user_tables;
select * from pg_stat_user_indexes;

Guarda esta información, antes de hacer optimizaciones, y luego reseteala y posteriormente compara los resultados:

select * from pg_stat_reset();

Para ver cuantas conexionest tienes establecidas y que están ejecutando:

select * from pg_stat_activity;

Y los bloqueos con:

 select * from pg_locks;

Si haces uso intensivo de procedimientos almacenados, también puedes consultar su rendimiento:


select * from pg_stat_user_functions


Si no tienes estadísticas calculadas, será porque no tienes activada la recopilación de las mismas,



Referencias:
http://www.postgresql.org/docs/9.1/static/monitoring-stats.html#MONITORING-STATS-SETUP

martes, 28 de agosto de 2012

Reemplazar cadena en ficheros recursivamente

Busca a partir del directorio, recursivamente en todos los ficheros y cambia "lo_que_busco" por "lo_cambio_por_esto":

for i in `grep -l -R "lo_que_busco" ./directorio`; do sed 's/lo_que_busco/lo_cambio_por_esto/g' -i $i; done;

Trucos ssh

Conexión sin contraseña a un servidor ssh:


En el servidor origen de la conexión:


$ ssh-keygen -t rsa


El contenido del fichero resultante ~/.ssh/id_rsa.pub, hay que añadirlo al final del fichero ~/.ssh/authorized_keys, del usuario con el que quiero conectarme en el servidor destino donde quiero conectarme.

Copiar la llave id_rsa.pub al servidor remoto a traves de: ssh_copy_id id_rsa.pub usuario_remoto@servidor_remoto


Nota: en el servidor destino tiene que estar activados los siguientes parámetros del /etc/ssh/sshd_config


RSAAuthentication yes
PubkeyAuthentication yes




Copiar directorios


Copiar un directorio remoto vía ssh, con compresión y recursividad

scp -rC usuario@servidor.com:/origen ./destino

miércoles, 22 de agosto de 2012

Creación de índices

Cuando en producción nos encontramos que tenemos que optimizar las consultas, nos encontraremos a veces que no podemos parar los servidores.

La optimización muchas veces pasa por crear un índice para alguna tabla, pero en Postgres esta operación bloquea la tabla.

Esto puede hacer caer el servidor de aplicaciones en el caso de aplicaciones web dinámicas, si el índice es muy grande.

Por fortuna Postgres nos facilita hacer esta operación de forma concurrente sin necesidad de bloquear la tabla, la operación será más lenta pero podremos hacerla en caliente:

CREATE INDEX CONCURRENTLY idx_salary ON employees(last_name, salary);

martes, 26 de junio de 2012

Ranking compuesto

Tenemos una tabla con resultados parciales:
  
Tabla Parcial
id
campo1
campo2
...
campo N
Tenemos una tabla con el ranking actual:
 
Tabla Ranking
id
posicion
posicion_anterior
Vamos a actualizar el ranking con la suma de los resultados parciales, guardando además la posición anterior a la actualización para saber cuanto se ha avanzado o retrocedido desde el último recalculo del ranking.

begin;
-- Insertamos los nuevos registros en el ranking
insert into ranking (id,posicion,ant_posicion) select id,999999,999999
from parcial where id not in (select id from ranking);
-- Actualizamos la posición actual y guardamos la anterior
update ranking set posicion_anterior=ranking.posicion,posicion=temporal.posicion
from  (
select rank() over ( order by sum (campo1+
campo2...campoN) desc) as posicion,
sum (
campo1+campo2) as puntos,id
from parcial
group by id) as temporal
where ranking.id = temporal.id;
 
-- Borramos del ranking quien no tenga puntos parciales
delete from ranking where id not in (select id from parcial);
delete from ranking where id in
(select id from rankings.acumulado where
campo1+campo2...campoN=0 );


end;


Tenemos una tabla con los puntos acumulados por equipos de diferentes regiones y divisiones.

 region_id
division
equipo
puntos

Como hacer un ranking de un plumazo
 select region_id,division,rank() over ( partition by region_id,division order by puntos desc) as posicion,id from
(select
region_id,division,id,sum(puntos) as puntos
from rankings.diario group by
region_id,division,id) as temporal;

Enlaces de Interés:

http://www.postgresql.org.es/node/376 (Funciones de ventana)

martes, 12 de junio de 2012

Corregir nicks de usuarios con caracteres no válidos

Aquí podeis observar como arreglar la típica tabla de usuarios en la que se se han introducidos nicks con caracteres extraños que luego dan problemas al hacer login. La tabla principal que almacena los usuarios es USUARIO La tabla aux_usuario almacena los usuarios con caracteres no admitidos La tabla aux_usuario_bueno almacena los usuarios que realizada la traducción de un caracter incorrecto a otro, no supone nick duplicado en la tabla principal
-- Borramos la tabla de apoyo si existe
drop table if exists aux_usuario;
-- Rellenamos la tabla con los usuarios problemáticos
create table aux_usuario as select * from usuario where nick <> translate(nick, 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_áéíóúÁÉÍÓÚçÇñÑüÜ;,.-{}[]¡!ªº"@#$%&/()=¿?^*+€´:àèìòùÀÈÌÒÙâêîôûÂÊÎÔÛ¨¬', 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiouAEIOUcCnNuU____________________________________________________'); 
-- Actualizamos el nick substituyendo los caracteres problematicos  
update aux_usuario set nick=translate(nick,'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_áéíóúÁÉÍÓÚçÇñÑüÜ;,.-{}[]¡!ªº"@#$%&/()=¿?^*+€´:àèìòùÀÈÌÒÙâêîôûÂÊÎÔÛ¨¬', 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiouAEIOUcCnNuU____________________________________________________') where nick <> translate(nick, 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_áéíóúÁÉÍÓÚçÇñÑüÜ;,.-{}[]¡!ªº"@#$%&/()=¿?^*+€´:àèìòùÀÈÌÒÙâêîôûÂÊÎÔÛ¨¬', 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiouAEIOUcCnNuU____________________________________________________') ;
-- Creamos una tabla excluyendo los duplicados
drop table if exists aux_usuario_bueno;
create table aux_usuario_bueno as select * from aux_usuario aux where aux.nick not in ( select nick from usuario) and aux.nick not in ( select nick from (

-- Actualizamos la tabla principal
update usuario set nick=aux_usuario_bueno.nick from aux_usuario_bueno where aux_usuario_bueno.id=usuario.id ; ===================================================================== --- PARA LOS DUPLICADOS
--- Los usuarios que no al ser traducido el nick colisiona con otro
--- existente le añadimos el identificador para hacerlo único.
--- En el caso de nombres mayores de 10 caracteres añadimos
--- el identificador por segunda vez. ===================================================================== -- Borramos la tabla de apoyo si existe  
drop table if exists aux_usuario2;
-- Rellenamos la tabla con los usuarios problemáticos create table aux_usuario2 as select * from usuario where nick <> translate(nick, 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_áéíóúÁÉÍÓÚçÇñÑüÜ;,.-{}[]¡!ªº"@#$%&/()=¿?^*+€´:àèìòùÀÈÌÒÙâêîôûÂÊÎÔÛ¨¬\\''', 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiouAEIOUcCnNuU_______________________________________________________');
update aux_usuario2 set nick=translate(nick, 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_áéíóúÁÉÍÓÚçÇñÑüÜ;,.-{}[]¡!ªº"@#$%&/()=¿?^*+€´:àèìòùÀÈÌÒÙâêîôûÂÊÎÔÛ¨¬\\''', 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiouAEIOUcCnNuU_______________________________________________________') where nick <> translate(nick, 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_áéíóúÁÉÍÓÚçÇñÑüÜ;,.-{}[]¡!ªº"@#$%&/()=¿?^*+€´:àèìòùÀÈÌÒÙâêîôûÂÊÎÔÛ¨¬\\''', 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiouAEIOUcCnNuU_______________________________________________________');

update aux_usuario2 set nick=id::varchar where length(nick) >=10; update aux_usuario2 set nick=nick||id::varchar where length(nick) <10;
update usuario set nick=aux_usuario2.nick from aux_usuario2 where aux_usuario2.id=usuario.id ;

miércoles, 2 de mayo de 2012

selects favoritas con variables en psql

Puedes tener tus scripts de ayuda preparados, con esas consultas que sueles usar continuamente, y que te pregunten los parametros para después ejecutarlos.

Por ejemplo me voy a crear un script en /home/postgres que se llama nick.sql, que me va a servir para buscar a un usuario por su nick:
\set nick 'pepe'
\prompt NICK nick \set nick_comillas '\'' :nick '\'' \echo select * from usuario where nick= :nick_comillas;
\prompt seguro seguro
select * from usuario where nick= :nick_comillas;

Para ejecutarlo dentro de psql \i nick.sql y nos pedirá el parametro del nick y ejecutará la sentencia. Aquí se muestra como poner una variable de tipo cadena ya que las comillas son complicadas de formatear.

jueves, 9 de febrero de 2012

Crear una instancia de prueba de Amazon

Tutorial muy completo para crear una instancia gratuita de prueba en Amazon. Muy bien explicado: http://alanta.info/como-crear-un-servidor-gratuito-en-amazon.html

miércoles, 8 de febrero de 2012

Monitorización Linux

Quien está conectado y que está haciendo
w [user_name]
17:24:24 up 88 days, 17:12, 5 users, load average: 0,09, 0,07, 0,08
USER TTY   FROM         LOGIN@ IDLE   JCPU  PCPU  WHAT
gme  pts/0 paco.bit.mad 09:35  0.00s  0.61s 0.00s w
wdev pts/1 fe.bit.mad   Fri10  5days  0.01s 0.01s -bash
gme  pts/2 fe.bit.mad   Fri11  27:06m 6.31s 6.28s ./gmeser
wdev pts/5 paco.bit.mad 09:35  7:48m  0.01s 0.01s -bash
gme  pts/7 fe.bit.mad   12:23  4:58m  0.04s 0.04s -bash

Otras herramientas de monitorización de actividad, memoria, uso de disco, cpu... http://www.cyberciti.biz/tips/top-linux-monitoring-tools.html

Llamadas repetitivas a Web Services con wget

Necesitaba hacer multiples llamadas a webservices, y luego verificar los resultados devueltos. En lugar de hacerlo enlace a enlace, preparé un fichero llamadas.url donde almacene las llamadas a realizar en forma de proceso por lotes:
wget -S -i llamadas.url -O resultados_http.txt -a info_llamadas.txt
Los resultados de las llamadas se guardarán en resultados_http.txt La información de errores y tiempos se guardará en: info_llamadas.txt

martes, 7 de febrero de 2012

Vacuum de tablas con muchas actualizaciones

Si revisando los logs de tu Base de datos postgres, te encuentras a menudo con la siguiente entrada:
2012-02-06 18:44:20 CETLOG: vacuum automático de la tabla «esquema.tabla»: recorridos de índice: 1
Esto se debe a que esa tabla tiene muchas inserciones o updates y constantemente tiene que aprovisionar nuevas páginas, el proceso de vacuum autoático se lanza para liberar las páginas inservibles, etc. Para solucionar este problema lo mejor es indicar un Factor de relleno (Fillfactor) para la tabla. Este factor representa cuanto espacio se deja en una página para el alojamiento de nuevas inserciones o actualizaciones de tuplas de esa página. Por defecto este valor es 100, por lo que todas la actualizaciones/inserciones irán a páginas nuevas y no se modificará la página actual. Para empezar un buen valor es 80, pero si la tabla tiene un ínidice muy alto de actualización tendrás que probar con valores por debajo de 50. Despues de hacer la modificación, no tendrá efecto hasta que hagas un Vacuum full (Ojo esta operación bloqueará la tabla, por lo que es mejor que no haya conexiones a la base de datos):
ALTER TABLE rankings.ranking SET ( fillfactor = 80 ); VACUUM FULL rankings.ranking;
Este factor también afecta a los índices que sean modificados con mucha frecuencia, la sintaxis para modificarlos es:
ALTER INDEX "tabla_idx" ON "esquema"."tabla" USING btree ("campo_a_indexar"); DROP INDEX.tabla_idx; CREATE INDEX "tabla_idx" ON "esquema"."tabla" USING btree ("campo_a_indexar") WITH (fillfactor = 80);
Referencias: http://www.postgresql.org/docs/9.0/static/sql-createtable.html

lunes, 6 de febrero de 2012

Update basado en select

Si un día necesitas guardar algunos campos de una tabla antes de que la tabla sea machacada por un proceso, puedes guardar los datos en una tabla auxiliar:
drop table if exists aux_coleccion; create table aux_coleccion as select * from coleccion;
Despues de que hayamos guardado una copia de la tabla podemos machacar su contenido. Ahora podemos recuperar el contenido original de los campos que queramos:
update coleccion set precio=a.precio,nivel=a.nivel,puntos=a.puntos,estado=a.estado, nombre=a.nombre,descripcion=a.descripcion, from aux_coleccion a where a.id=coleccion.id;

Extraer permisos de un usuario

Si tienes una base de datos con un usuario y quieres copiar sus permisos en otro lugar, puedes utilizar la siguiente consulta, el resultado puedes ejecutarlo en otro lugar:

select 'grant ' || privilege_type || ' on table ' || table_schema || '.' || table_name || ' to ' || grantee || ';' from information_schema.role_table_grants where grantee='';