Posts de ‘Tiago Motta’

[Tiago Motta] Dados abertos do Web Democracia

Saturday, November 28th, 2015

Nas minhas conversas com os participantes do RecSys de 2015 em Viena foi possível notar o esgotamento deles em relação aos dados existentes para análise. Os acadêmicos longe de grandes empresas como Linkedin, Google, Facebook e Netflix, estavam sempre em busca de mais dados para tunar e implementar novos algoritmos. Em resumo, ninguém aguenta mais o MovieLens.

Por essa razão resolvi disponibilizar todos os dados de avaliações de políticos no Web Democracia publicamente.

Para garantir a privacidade dos usuários utilizei a total aleatorização dos IDs de forma a evitar qualquer engenharia reversa, deixando então as avaliações anônimas.

A descrição completa dos dados de avaliações de políticos do Web Democracia você encontra aqui.

Um exemplo simples de análise desses dados com pandas pode ser visto aqui.

Agora então os quase meio milhão de avaliações de políticos do Web Democracia é mais uma fonte de informação e de um domínio bem diferente que os tradicionais livros e filmes.

[Tiago Motta] Meu primeiro aplicativo Android

Monday, October 14th, 2013

Publiquei essa semana no Google Play meu primeiro aplicativo Android. O 9Gag Offline trata-se de um aplicativo que permite que você baixe os gags do site 9Gag para momentos em que não haja conexão com internet. Por exemplo se você for viajar, no avião você ficará isolado do mundo mas poderá rir bastante com os gags que estarão guardados no seu celular.

Já fazia mais ou menos uns seis meses que estava estudando esporadicamente o sistema operacional da Google e fazendo diversos testes. A um mês atrás resolvi fechar de vez o aplicativo e colocá-lo ao público. Deu pra aprender bastante sobre o framework e reviver os problemas de sincronia e concorrência que haviam na época em que programava para desktop com Delphi, Kylix e Java Swing.

Espero que gostem e se acharem qualquer problema ou tiverem alguma sugestão é só avisar. Para baixar o 9Gag Offline basta clicar aqui e ir ao Google Play.

[Tiago Motta] Plugin de Fonética Portuguesa para ElasticSearch

Thursday, July 25th, 2013

O ElasticSearch possui um plugin fonético que abrange diversas regras e padrões. No entanto nenhuma dessas regras é boa o bastante para as peculiaridades da lingua portuguesa.

Pensando nisso e de posse de uma gramática portuguesa, forkei o repositório e criei um encoder com as regras de nosso estimado idioma lusitano. Como alguns meses depois ainda não obtive resposta alguma da equipe do plugin original, promovi então este encoder a um novo plugin: Portuguese Phonetic Plugin.

Para utilizá-lo você precisa clonar o projeto no github e instalá-lo:

git clone https://github.com/timotta/elasticsearch-fonetica-portuguesa.gitcd elasticsearch-fonetica-portuguesa./script/install.sh ~/Programas/elasticsearch-0.20.5

Depois é preciso configurar um analyser em config/elasticsearch.yml:

index :    analysis :        analyzer :            fonetico :                type : custom                tokenizer : standard                filter : [ standard, lowercase, foneticaportuguesa_filter, asciifolding ]        filter :            foneticaportuguesa_filter:                type : foneticaportuguesa                replace : false

Com tudo configurado, você pode criar um novo indice usando este analyser, como é mostrado abaixo:

$ curl localhost:9200/comfonetica -XPUT -d '{  "mappings" :{     "document" : {        "analyzer" : "fonetico"     }   }    }'

Com o indice criado e configurado é possivel verificar as transformações de texto da seguinte forma:

$ curl -XGET 'localhost:9200/comfonetica/_analyze?analyzer=fonetico&pretty=true' -d chiclete{  "tokens" : [ {    "token" : "XICLETE",    "start_offset" : 0,    "end_offset" : 8,    "type" : "",    "position" : 1  }, {    "token" : "chiclete",    "start_offset" : 0,    "end_offset" : 8,    "type" : "",    "position" : 1  } ]

Repare que a palavra se transformou em duas. Isso acontece porque a configuração replace do filter está como false.

Atualmente só testei o plugin com a versão 0.20.5 do elasticsearch, se testarem em outras versões peço que reporte no github. Além disso, nem todas as regras fonéticas foram implementadas, então se você precisar de alguma que esteja faltando, colabore ou solicite lá também.

[Tiago Motta] Complexidade Ciclomática com Pygenie recursivo

Tuesday, July 16th, 2013

Pygenie é uma biblioteca python bem simples para calcular a complexidade ciclomática de seus método. É tão simples, mas tão simples que para projetos mais complexos é preciso scriptar um pouco para que todos os diretórios sejam analisados.

Para facilitar então a nossa vida aqui na empresa, fiz uma contribuição (com pouca esperança de ser aceita pois o projeto está parado a dois anos) para que seja possível obter os resultado de forma recursiva. Com esta contribuição é possivel informar o parâmetro -r que analisará recursivamente todos os arquivos .py dentro de um diretório.

$ pygenie complexity mylib -r

Se dentro de seu projeto houver algum arquivo ou diretório que você não deseja que seja analisado você pode utilizar um outro parâmetro adicionado, o -e, onde você informar um pattern para ser excluído. Por exemplo se houver um diretório de testes:

$ pygenie complexity mylib -r -e tests

Caso você deseje utilizar desde já pode instalar o pygenie através do meu fork e neste meio tempo tentar incentivar nosso amigo Matthew Von Rocketstein a aceitar o pull request.

[Tiago Motta] SlimIt, Head.js e django compressor

Tuesday, July 16th, 2013

Ao configurar o django compressor com o filtro do SlimIt, o arquivo do head.js comprimido pela templatetag compress era gerado com um caracter  ï ao ínicio. Utilizando o SlimIt na mão eu obtinha o seguinte erro:

$ slimit head.jsIllegal character '\xbb' at 1:1 after LexToken(ID,'\xef',1,0)Illegal character '\xbf' at 1:2 after LexToken(ID,'\xef',1,0)

Criei uma issue no repositório do SlimIt acreditando ser um problema desta lib. E determinado a corrigir tal problema acabei descobrindo que a raíz deste era o Head.js. No arquivo fonte desta biblioteca javascript havia realmente um caracter estranho, que nenhum editor exibia, nem mesmo o Vi. No entanto, se abríssemos o arquivo via python com um simples:

open("head.js").read()

Víamos claramente o caracter escondido. Espantosamente o único editor que testei e que exibiu o caracter estranho foi o editor online do github.

Forked, pull resqueted e merged: Felizes para sempre.

[Tiago Motta] Liberando o GIL do python para paralelizar seu código com threads

Wednesday, November 21st, 2012

No Python o Global Interpreter Locker impede que duas threads executem ao mesmo tempo. Uma thread só é executada quando nenhuma outra estiver executando. A solução mais comum para aproveitar todos os cores de uma máquina em python é abandonar threads e utilizar vários processos. No entanto há uma maneira de aproveitar todos os cores com threads no python. Para isso vamos precisar criar uma extensão em C.

Primeiro vamos criar uma extensão que não libere o GIL para podermos comparar e conferir a melhoria de performance depois. O pivô dessa extensão é a seguinte função:

static int reduce_com_gil(int max, int (*f)(int x, int y)) {
   int retorno = 0;
   int i;
   for(i=0; i
       retorno = (*f)(retorno, i);
   }
   return retorno;
}

Essa função faz algo similar ao que um reduce faria, mas indo de 0 ao valor indicado em max. Para cada iteração a função enviada como segundo parâmetro é executada. A idéia é causar um grande processamento para que meu core fique travado. O uso dela está descrito no código abaixo:

static PyObject *antigil_calcular_com_gil(PyObject *self) {
   int valor = reduce_com_gil(100*1000, *antigil_calculos);
   char numero [5000];
   sprintf(numero, “%d”, valor );
   return Py_BuildValue(”s”, numero);
}

Repare que eu passo para a função reduce_com_gil o ponteiro da função antigil_calculos. Essa outra função faz diversos cálculos a cada iteração do reduce. O nome antigil é o nome da extensão de exemplo. O código completo da extensão pode ser visto aqui.

Instalada a extensão, podemos testar a performance da lib com o seguinte código:

import antigil
antigil.calcular_com_gil()

E o seguinte comando:

$ time python teste.py
real 0m2.702s
user 0m2.692s

Mas o que a gente quer é saber como se comportam as threads. No caso o seguinte script abre 4 threads para aproveitar os 4 cores da minha máquina:

import antigil
from threading import Thread

threads = []
for i in xrange(4):
   t = Thread(target=antigil.calcular_com_gil)
   t.start()
   threads.append(t)

for t in threads:
   t.join()

No entando, acaba não aproveitando. Repare que ao executar 4 threads, o GIL age e impede que duas sejam executadas ao mesmo tempo. Dessa forma só um core da máquina é aproveitado. Isso pode ser observado pelo tempo total que é aproximadamente quatro vezes o tempo de execução de uma:

$ time python teste.py
real 0m10.910s
user 0m10.881s

Bom, vamos então à extensão não bloqueante. A chave do sucesso nesse caso são as macros Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS que respectivamente liberam o GIL e obtem o GIL de volta. Essas macros estão definidas em Python.h. O código da função reduce ficaria assim:

static int reduce_sem_gil(int max, int (*f)(int x, int y)) {
   int retorno = 0;
   Py_BEGIN_ALLOW_THREADS
   int i;
   for(i=0; i       retorno = (*f)(retorno, i);
   }
   Py_END_ALLOW_THREADS
   return retorno;
}

Pronto, basta criar a função antigil_calcular_sem_gil similar à antigil_calcular_com_gil, utilizando a função reduce_sem_gil e então podemos repetir o teste. No caso parametrizei o script de teste para que você possa escolher qual função deseja executar:

import antigil
from threading import Thread
import sys

if ‘com-gil’ in sys.argv:
    calcular = antigil.calcular_com_gil
elif ’sem-gil’ in sys.argv:
    calcular = antigil.calcular_sem_gil
else:
    print “Informar com-gil ou sem-gil”
    exit(0)

threads = []
for i in xrange(4):
   t = Thread(target=calcular)
   t.start()
   threads.append(t)

for t in threads:
   t.join()

O resultado é bem animador e mostra bem que o código rodou em paralelo:

$ time python teste.py sem-gil
real 0m3.414s
user 0m13.413s

Como o código utiliza apenas CPU, sem IO algum, ao aumentar para 8 threads, mesmo a versão que libera o GIL dobra de tempo pois só tenho disponível 4 cores na minha máquina. No entanto acredito que se fizer alguma operação de IO entre as macros Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS outras threads poderão ser executadas no caminho. Isso eu ainda preciso validar.

Só é preciso ter muito cuidado pois código entre essas macros está em território perigoso. A alteração de váriaveis globais ou ponteiros que podem ser compartilhados entre outras threads podem causar erros inesperados a qualquer momento. Portanto, é importante utilizar somente váriaveis locais e dados copiados.

O código completo da extensão em C antigil pode ser visto aqui.

[Tiago Motta] Ganhe convites de graça para a SEMCOMP

Thursday, August 9th, 2012

Este ano o maior evento de tecnologia da Bahia, a Semana da Computação da UFBA, será fantástica. A lista de palestrantes já confirmados é um dos pontos que chama a atenção, com presença de Nívio Ziviani, Osvaldo Matos, Fábio Akita, Sérgio Cavalcante entre outros. Você pode ver a lista completa dos palestrantes aqui: http://infojr.com.br/semcomp/palestrantes.

Os dois primeiros lotes de entradas ao evento se esgotaram logo nos primeiros dias de lançamento, e o terceiro pode estar em vias de acabar. Como a Globo.com está patrocinando o evento, ela me deu cinco convites para que eu distribuisse por conta própria. Resolvi então fazer de uma maneira divertida.

Para garantir o seu convite grátis para a SEMCOMP, basta fazer três coisas:

  1. Curtir o Música.com.br (novo site de música da Globo.com) no Facebook.
  2. Criar uma playlist no Música.com.br.
  3. Enviar a url dela para timotta@gmail.com indicando em quais quesitos ela se encaixa, lembrando de colocar no assunto do e-mail: Playlist SEMCOMP.

O criador da melhor playlist em cada um dos seguintes quesitos abaixo leva um convite para o evento.

  • Melhor playlist para programar em par
  • Melhor playlist para resolver bugs
  • Melhor playlist para configurar servidor
  • Melhor playlist para ajeitar layout web
  • Melhor playlist para programar sozinho

Você pode criar quantas playlists quiser. O próprio time de desenvolvimento do Música.com.br escolherá, em um método científico de experimentação, ou seja programando usando as playlists enviadas. Os vencedores serão anunciados apartir de 10 de Setembro de 2012 na página do facebook da SEMCOMP.

Mas veja que são apenas os convites. Não está incluído aí passagem ou hospedagem. Ou seja, caso você more em outro estado ou cidade, você irá arcar com esses custos. Será oferecido apenas o convite.

Para servir de inspiração, segue algumas das playlists que eu criei:

Que a diversão comece!

[Tiago Motta] Divagações sobre GIL, threads e IO em python e ruby

Sunday, July 22nd, 2012

Uma coisa que eu sempre me confundi sobre ruby e python é a questão do Global Interpreter Locker (GIL). Na verdade, a dúvida maior é se operações de IO realmente bloqueiam os processos, impedindo a execução de outras threads.

Recentemente li um pouco mais sobre a versão 1.9 do Ruby e como ela passou a utilizar threads do sistema operacional, ao contrário das chamadas Green Threads da versão 1.8. No entanto, o GIL do ruby continua impedindo que duas threads executem ao mesmo tempo. A menos que uma esteja parada executando alguma operação de IO não bloqueante.

No caso do Python, o pouco de informação que tenho me leva a crer que a linguagem utiliza Green Threads. Fica então a minha dúvida se mesmo assim é possivel que o processo execute uma outra thread enquanto aguarda um retorno de IO.

Para começar fiz um pequeno script python para simular uma query pesada do MySql sendo executada 4 vezes. Se o script demorar por volta de dois segundo significa que durante uma operação de IO, o python prosseguiu executando as outras threads:

import _mysql
from threading import Thread

def executa():
    db = _mysql.connect(host=”localhost”,

                        user="root",
                        passwd=”",

                        db="teste")
    db.query(”select sleep(2)”)
    r=db.use_result()
    r.fetch_row()

threads = []
for i in range(4):          
    t = Thread(target=executa, args=())
    t.start()
    threads.append(t)

for t in threads:
    t.join()
 
O resultado da execução pode ser visto abaixo, mostrando que o IO não bloqueou o programa:

> time python teste.py
real 0m2.030s
user 0m0.016s
sys 0m0.012s

Executei o mesmo teste com ruby, com o script similar abaixo:

require 'mysql2'

threads = []
4.times do |i|
    thread = Thread.new do
        my = Mysql2::Client.new(host: “127.0.0.1″, 

                                username: "root", 
                                database: "teste")
        my.query("select sleep(2)").collect{|i|i}
    end
    thread.run
    threads << thread
end

threads.each do |thread|  
    thread.join
end

E o resultado também foi satisfatório:

> time ruby teste.rb
real 0m2.178s
user 0m0.076s
sys 0m0.008s

Ou seja, tanto python como ruby estão lidando bem com execução paralela. Mesmo se não utilizar todos os cores disponiveis, no mínimo o IO para o MySql não está bloqueando.

Com esse bom resultado, resolvi então subir um pouco mais de nível e verificar se colocando o Django na equação poderíamos aproveitar esse bom desempenho. Criei essa pequena view para simular uma query lenta, assim como nos scripts acima, e iniciei o Django (versão 1.3) com o gunicorn.

from django.db import connection
def debug(request):
    cursor = connection.cursor()
    cursor.execute(”select sleep(2)”)
    return HttpResponse(’ok’)

E o resultado, como pode ser visto abaixo, foi ruim:

> ab -n 4 -c 4 http://127.0.0.1:8000/debug/
Time taken for tests:   8.161 seconds

O mesmo teste executado para a dupla ruby on rails tem resultado parecido:

> ab -n 4 -c 4 http://localhost:3000/politicos/
Time taken for tests:   8.043 seconds

Conclusão:

Embora python e ruby permitam IO não bloqueante, os frameworks Django e Rails ainda são bloqueantes. Um dos motivos daqueles memes de “Rails não escala” e “Django não escala”. Felizmente sempre há alternativas.

É possivel escalar via processos como explicado em Solucionando IO bloqueante do mysql para Rails, e utilizando vários workers do gunicorn para Django. Caso seja necessário uma escalabilidade maior ainda, o ideal então é partir para soluções como Event Machine do ruby, GEvent para Python, ou até mesmo NodeJs. Combinando essas soluções com múltiplos processos.

[Tiago Motta] Estratégias de persitência do Redis

Monday, July 9th, 2012

Nestes ultimos meses eu ministrei algumas palestras sobre o uso de redis na prática, usando como exemplo algumas funcionalidades do Musica.com.br. Uma das dúvidas mais constantes nesses eventos é a questão das opções de persistência deste banco. Confesso que não havia estudado o suficiente sobre o assunto. Eis que com o crescimento do site, surgiu a necessidade de uma estratégia melhor sobre a manutenção desses dados. Então aproveitei minhas horas em aeroportos para realizar alguns testes e responder algumas dúvidas.

Pra começar o Redis possui três formas de persitência de dados: não persistir, dump no disco e append only. O dump no disco, basicamente consiste em gravar uma cópia da memória em disco de tempos em tempos. O append only grava todos os comandos em um log, de forma que para recuperar em caso de restarte ele refaz todo o caminho percorrido.

Vamos então às perguntas que eu me fazia, e as respostas que obtive com meus testes:

1) É possível converter o rdb (dump) para um aof (appendonly)?

Sim para isso é preciso executar o comando BGREWRITEAOF estopar o redis server e estartar de novo com a nova configuração habilitada para appendonly.

2) O tempo para gravar e carregar o rdb (dump) é grande?

Para os padrões do Redis gravar é sim um tempo grande. Mas talvez não seja algo que possa atrapalhar. Depende muito da quantidade de dados que sua base terá. Fiz um teste adicionando três vezes 1 milhão de registros e comparando o tempo que demorava para gerar o dump em disco. Essa tabela pode servir de guia para saber quando é o momento de colocar o seu redis em uma máquina separada, e quando é hora de trocar de estratégia de gravação.

Registros Memória Disco Tempo para salvar Gravação de Memória/segundo
1.000.000 99.94M 24M 0.56s 178.46 M/s
2.000.000 191.50M 42M 0.93s 205.37 M/s
3.000.000 283.03M 62M 1.32s 214.41 M/s

No entanto, o load do dump com 3 milhões de registros foi irrisório.

3) O tempo para carregar o aof (appendonly) é grande?

Sim, muito grande. Fiz um teste similar ao do dump, com 3 milhões de registros colocando 313.57M em memória e gerando um arquivo aof de 192M. Ao restartar o servidor ele demorou 4 segundos para carregar o arquivo. Um resultado que achei péssimo. No entanto, se pensarmos que restartar servidor é algo que não faremos com tanta constância, pode não ser algo tão ruim. Só deve-se ficar em mente que manutenções desse tipo em uma base grande devem ser em horários com pouco ou nenhum uso do seu servidor master.

4) É possivel compactar o aof (appendonly) com BGREWRITEAOF?

Um dos problemas do aof é que se um registro for incluido e removido, as duas instruções serão gravadas, de forma que não há uma razão clara entre memória do servidor e tamanho do arquivo em disco gerado. Minha dúvida era se eu poderia limpar o aof reescrevendo somente aquilo que estava na memória. E sim, isso é possível com o comando BGREWRITEAOF.

5) Os comandos ficam mais lentos com aof (appendonly) ligado?

Sim. Fiz um teste com concorrência. Três processos inserindo 1 milão de itens em listas diferentes. Com rdb (dump) habilitado a média de tempo foi de 0.33s. Com aof (appendonly) habilitado e rdb (dump) desabilitado a média de tempo foi de 0.43s. Essa proporção de 30% mais demorado para aof foi constante nos três testes seguintes que fiz como tira-teima.

6) Se o redis slave ficar down por um tempo ele recebe os dados do master?

Sim, fiz uma série de testes e percebi que o redis slave ignora os arquivos salvos por ele mesmo. Sempre que se inicia ele recebe todos os dados novamente do master, seja ele configurado como rdb ou como aof. Portanto, se estiver utilizando master slave, uma boa é configurar o seu slave para não gravar nada.

7) Gravar o rdb (dump) afeta a performance na resposta a outros comandos?

Sim, fiz um teste gravando três vezes uma memória de 2GB enquanto estava inserindo 1 milhão de novos registros. A insersão deles demorou 25% mais que inserir a mesma quantidade de registros sem salvar nenhuma vez o rdb. Só para se ter uma idéia, o tempo de gravação deste dump foi de 9 segundos e o tempo de load foi de 4 segundos.

Conclusão:

- Manter seus dados com rdb deixa o redis mais rápido, mas pode exigir muito de IO nos intervalos de persistência. Se a memória estiver muito grande, vale a pena colocar o servidor master em uma máquina dedicada para que não atrapalhe outros serviços.

- Manter seus dados com aof deixa o redis mais lento, mas garante que nenhuma transação será perdida. É preciso ficar atento para o arquivo gerado e periodicamente compactar ele com BGREWRITEAOF. Também tomar cuidado nos restarts que podem demorar.

[Tiago Motta] Bate-papo sobre o Redis no 3o Dev in Santos

Wednesday, May 2nd, 2012

Neste sábado dia 5 de Maio participarei do maior encontro de desenvolvedores da baixada santista, falando um pouco sobre o Redis, e como o utilizamos pra tornar o desenvolvimento do Musica.com.br mais fácil, estável e seguro.

O programa completo do Dev in Santos com todas as apresentações você pode ver aqui: http://www.mktvirtual.com.br/mailing/2012/dev-in-santos/. Repare que teremos ótimas apresentações sobre uma grande variedade de assuntos como NodeJS, Python, IOS, Unity3D e é claro, Redis.

Se você estiver pela baixada santista, ou até mesmo em São Paulo neste fim de semana, será uma bela oportunidade para bater um papo sobre desenvolvimento e outras nerdices. Não deixe de se inscrever enquanto ainda há vagas.

Vejo vocês lá.