Posts de July, 2012

[Francisco Souza] Using Juju to orchestrate CentOS-based cloud services

Saturday, July 28th, 2012

Earlier this week I had the opportunity to meet Kyle MacDonald, head of Ubuntu Cloud, during FISL, and he was surprised when we told him we are using Juju with CentOS at Globo.com. Then I decided to write this post explaining how we came up with a patched version of Juju that allows us to have CentOS clouds managed by Juju.

For those who doesn’t know Juju, it’s a service orchestration tool, focused on devops “development method”. It allows you to deploy services on clouds, local machine and even bare metal machines (using Canonical’s MAAS).

It’s based on charms and very straightforward to use. Here is a very basic set of commands with which you can deploy a Wordpress related to a MySQL service:

% juju bootstrap% juju deploy mysql% juju deploy wordpress% juju add-relation wordpress mysql% juju expose wordpress

These commands will boostrap the environment, setting up a bootstrap machine which will manage your services; deploy mysql and wordpress instances; add a relation between them; and expose the wordpress port. The voilà, we have a wordpress deployed, and ready to serve our posts. Amazing, huh?

But there is an issue: although you can install the juju command line tool in almost any OS (including Mac OS), right now you are able do deploy only Ubuntu-based services (you must use an Ubuntu instance or container).

To change this behavior, and enable Juju to spawn CentOS instances (and containers, if you have a CentOS lxc template), we need to develop and apply some changes to Juju and cloud-init. Juju uses cloud-init to spawn machines with proper dependencies set up, and it’s based on modules. All we need to do, is add a module able to install rpm packages using yum.

cloud-init modules are Python modules that starts with cc_ and implement a `handle` function (for example, a module called “yum_packages” would be written to a file called cc_yum_packages.py). So, here is the code for the module yum_packages:

import subprocessimport traceback

from cloudinit import CloudConfig, util

frequency = CloudConfig.per_instance

def yum_install(packages):    cmd = ["yum", "--quiet", "--assumeyes", "install"]    cmd.extend(packages)    subprocess.check_call(cmd)

def handle(_name, cfg, _cloud, log, args):    pkglist = util.get_cfg_option_list_or_str(cfg, "packages", [])

    if pkglist:        try:            yum_install(pkglist)        except subprocess.CalledProcessError:            log.warn("Failed to install yum packages: %s" % pkglist)            log.debug(traceback.format_exc())            raise

    return True

The module installs all packages listed in cloud-init yaml file. If we want to install `emacs-nox` package, we would write this yaml file and use it as user data in the instance:

#cloud-configmodules: - yum_packagespackages: [emacs-nox]

cloud-init already works on Fedora, with Python 2.7, but to work on CentOS 6, with Python 2.6, it needs a patch:

--- cloudinit/util.py 2012-05-22 12:18:21.000000000 -0300+++ cloudinit/util.py 2012-05-31 12:44:24.000000000 -0300@@ -227,7 +227,7 @@         stderr=subprocess.PIPE, stdin=subprocess.PIPE)     out, err = sp.communicate(input_)     if sp.returncode is not 0:-        raise subprocess.CalledProcessError(sp.returncode, args, (out, err))+        raise subprocess.CalledProcessError(sp.returncode, args)     return(out, err)

I’ve packet up this module and this patch in a RPM package that must be pre-installed in the lxc template and AMI images. Now, we need to change Juju in order to make it use the yum_packages module, and include all RPM packages that we need to install when the machine borns.

Is Juju, there is a class that is responsible for building and rendering the YAML file used by cloud-init. We can extend it and change only two methods: _collect_packages, that returns the list of packages that will be installed in the machine after it is spawned; and render that returns the file itself. Here is our CentOSCloudInit class (within the patch):

diff -u juju-0.5-bzr531.orig/juju/providers/common/cloudinit.py juju-0.5-bzr531/juju/providers/common/cloudinit.py--- juju-0.5-bzr531.orig/juju/providers/common/cloudinit.py 2012-05-31 15:42:17.480769486 -0300+++ juju-0.5-bzr531/juju/providers/common/cloudinit.py 2012-05-31 15:55:13.342884919 -0300@@ -324,3 +324,32 @@             "machine-id": self._machine_id,             "juju-provider-type": self._provider_type,             "juju-zookeeper-hosts": self._join_zookeeper_hosts()}+++class CentOSCloudInit(CloudInit):++    def _collect_packages(self):+        packages = [+            "bzr", "byobu", "tmux", "python-setuptools", "python-twisted",+            "python-txaws", "python-zookeeper", "python-devel", "juju"]+        if self._zookeeper:+            packages.extend([+                "zookeeper", "libzookeeper", "libzookeeper-devel"])+        return packages++    def render(self):+        """Get content for a cloud-init file with appropriate specifications.++        :rtype: str++        :raises: :exc:`juju.errors.CloudInitError` if there isn't enough+            information to create a useful cloud-init.+        """+        self._validate()+        return format_cloud_init(+            self._ssh_keys,+            packages=self._collect_packages(),+            repositories=self._collect_repositories(),+            scripts=self._collect_scripts(),+            data=self._collect_machine_data(),+            modules=["ssh", "yum_packages", "runcmd"])

The other change we need is in the format_cloud_init function, in order to make it recognize the modules parameter that we used above, and tell cloud-init to not run apt-get (update nor upgrade). Here is the patch:

diff -ur juju-0.5-bzr531.orig/juju/providers/common/utils.py juju-0.5-bzr531/juju/providers/common/utils.py--- juju-0.5-bzr531.orig/juju/providers/common/utils.py 2012-05-31 15:42:17.480769486 -0300+++ juju-0.5-bzr531/juju/providers/common/utils.py 2012-05-31 15:44:06.605014021 -0300@@ -85,7 +85,7 @@

 def format_cloud_init(-    authorized_keys, packages=(), repositories=None, scripts=None, data=None):+    authorized_keys, packages=(), repositories=None, scripts=None, data=None, modules=None):     """Format a user-data cloud-init file.

     This will enable package installation, and ssh access, and script@@ -117,8 +117,8 @@         structure.     """     cloud_config = {-        "apt-update": True,-        "apt-upgrade": True,+        "apt-update": False,+        "apt-upgrade": False,         "ssh_authorized_keys": authorized_keys,         "packages": [],         "output": {"all": "| tee -a /var/log/cloud-init-output.log"}}@@ -136,6 +136,11 @@     if scripts:         cloud_config["runcmd"] = scripts

+    if modules:+        cloud_config["modules"] = modules+     output = safe_dump(cloud_config)     output = "#cloud-config\n%s" % (output)     return output

This patch is also packed up within juju-centos-6 repository, which provides sources for building RPM packages for juju, and also some pre-built RPM packages.

Now just build an AMI image with cloudinit pre-installed, configure your juju environments.yaml file to use this image in the environment and you are ready to deploy cloud services on CentOS machines using Juju!

Some caveats:

  • Juju needs a user called ubuntu to interact with its machines, so you will need to create this user in your CentOS AMI/template.
  • You need to host all RPM packages for juju, cloud-init and following dependencies in some yum repository (I haven’t submitted them to any public repository):
  • With this patched Juju, you will have a pure-centos cloud. It does not enable you to have multiple OSes in the same environment.

It’s important to notice that we are going to put some effort to make the Go version of juju born supporting multiple OSes, ideally through an interface that makes it extensible to any other OS, not Ubuntu and CentOS only.

[Francisco Souza] Using Juju to orchestrate CentOS-based cloud services

Saturday, July 28th, 2012

Earlier this week I had the opportunity to meet Kyle MacDonald, head of Ubuntu Cloud, during FISL, and he was surprised when we told him we are using Juju with CentOS at Globo.com. Then I decided to write this post explaining how we came up with a patched version of Juju that allows us to have CentOS clouds managed by Juju.

For those who doesn’t know Juju, it’s a service orchestration tool, focused on devops “development method”. It allows you to deploy services on clouds, local machine and even bare metal machines (using Canonical’s MAAS).

It’s based on charms and very straightforward to use. Here is a very basic set of commands with which you can deploy a Wordpress related to a MySQL service:

% juju bootstrap% juju deploy mysql% juju deploy wordpress% juju add-relation wordpress mysql% juju expose wordpress

These commands will boostrap the environment, setting up a bootstrap machine which will manage your services; deploy mysql and wordpress instances; add a relation between them; and expose the wordpress port. The voilà, we have a wordpress deployed, and ready to serve our posts. Amazing, huh?

But there is an issue: although you can install the juju command line tool in almost any OS (including Mac OS), right now you are able do deploy only Ubuntu-based services (you must use an Ubuntu instance or container).

To change this behavior, and enable Juju to spawn CentOS instances (and containers, if you have a CentOS lxc template), we need to develop and apply some changes to Juju and cloud-init. Juju uses cloud-init to spawn machines with proper dependencies set up, and it’s based on modules. All we need to do, is add a module able to install rpm packages using yum.

cloud-init modules are Python modules that starts with cc_ and implement a `handle` function (for example, a module called “yum_packages” would be written to a file called cc_yum_packages.py). So, here is the code for the module yum_packages:

import subprocessimport traceback

from cloudinit import CloudConfig, util

frequency = CloudConfig.per_instance

def yum_install(packages):    cmd = ["yum", "--quiet", "--assumeyes", "install"]    cmd.extend(packages)    subprocess.check_call(cmd)

def handle(_name, cfg, _cloud, log, args):    pkglist = util.get_cfg_option_list_or_str(cfg, "packages", [])

    if pkglist:        try:            yum_install(pkglist)        except subprocess.CalledProcessError:            log.warn("Failed to install yum packages: %s" % pkglist)            log.debug(traceback.format_exc())            raise

    return True

The module installs all packages listed in cloud-init yaml file. If we want to install `emacs-nox` package, we would write this yaml file and use it as user data in the instance:

#cloud-configmodules: - yum_packagespackages: [emacs-nox]

cloud-init already works on Fedora, with Python 2.7, but to work on CentOS 6, with Python 2.6, it needs a patch:

--- cloudinit/util.py 2012-05-22 12:18:21.000000000 -0300+++ cloudinit/util.py 2012-05-31 12:44:24.000000000 -0300@@ -227,7 +227,7 @@         stderr=subprocess.PIPE, stdin=subprocess.PIPE)     out, err = sp.communicate(input_)     if sp.returncode is not 0:-        raise subprocess.CalledProcessError(sp.returncode, args, (out, err))+        raise subprocess.CalledProcessError(sp.returncode, args)     return(out, err)

I’ve packet up this module and this patch in a RPM package that must be pre-installed in the lxc template and AMI images. Now, we need to change Juju in order to make it use the yum_packages module, and include all RPM packages that we need to install when the machine borns.

Is Juju, there is a class that is responsible for building and rendering the YAML file used by cloud-init. We can extend it and change only two methods: _collect_packages, that returns the list of packages that will be installed in the machine after it is spawned; and render that returns the file itself. Here is our CentOSCloudInit class (within the patch):

diff -u juju-0.5-bzr531.orig/juju/providers/common/cloudinit.py juju-0.5-bzr531/juju/providers/common/cloudinit.py--- juju-0.5-bzr531.orig/juju/providers/common/cloudinit.py 2012-05-31 15:42:17.480769486 -0300+++ juju-0.5-bzr531/juju/providers/common/cloudinit.py 2012-05-31 15:55:13.342884919 -0300@@ -324,3 +324,32 @@             "machine-id": self._machine_id,             "juju-provider-type": self._provider_type,             "juju-zookeeper-hosts": self._join_zookeeper_hosts()}+++class CentOSCloudInit(CloudInit):++    def _collect_packages(self):+        packages = [+            "bzr", "byobu", "tmux", "python-setuptools", "python-twisted",+            "python-txaws", "python-zookeeper", "python-devel", "juju"]+        if self._zookeeper:+            packages.extend([+                "zookeeper", "libzookeeper", "libzookeeper-devel"])+        return packages++    def render(self):+        """Get content for a cloud-init file with appropriate specifications.++        :rtype: str++        :raises: :exc:`juju.errors.CloudInitError` if there isn't enough+            information to create a useful cloud-init.+        """+        self._validate()+        return format_cloud_init(+            self._ssh_keys,+            packages=self._collect_packages(),+            repositories=self._collect_repositories(),+            scripts=self._collect_scripts(),+            data=self._collect_machine_data(),+            modules=["ssh", "yum_packages", "runcmd"])

The other change we need is in the format_cloud_init function, in order to make it recognize the modules parameter that we used above, and tell cloud-init to not run apt-get (update nor upgrade). Here is the patch:

diff -ur juju-0.5-bzr531.orig/juju/providers/common/utils.py juju-0.5-bzr531/juju/providers/common/utils.py--- juju-0.5-bzr531.orig/juju/providers/common/utils.py 2012-05-31 15:42:17.480769486 -0300+++ juju-0.5-bzr531/juju/providers/common/utils.py 2012-05-31 15:44:06.605014021 -0300@@ -85,7 +85,7 @@

 def format_cloud_init(-    authorized_keys, packages=(), repositories=None, scripts=None, data=None):+    authorized_keys, packages=(), repositories=None, scripts=None, data=None, modules=None):     """Format a user-data cloud-init file.

     This will enable package installation, and ssh access, and script@@ -117,8 +117,8 @@         structure.     """     cloud_config = {-        "apt-update": True,-        "apt-upgrade": True,+        "apt-update": False,+        "apt-upgrade": False,         "ssh_authorized_keys": authorized_keys,         "packages": [],         "output": {"all": "| tee -a /var/log/cloud-init-output.log"}}@@ -136,6 +136,11 @@     if scripts:         cloud_config["runcmd"] = scripts

+    if modules:+        cloud_config["modules"] = modules+     output = safe_dump(cloud_config)     output = "#cloud-config\n%s" % (output)     return output

This patch is also packed up within juju-centos-6 repository, which provides sources for building RPM packages for juju, and also some pre-built RPM packages.

Now just build an AMI image with cloudinit pre-installed, configure your juju environments.yaml file to use this image in the environment and you are ready to deploy cloud services on CentOS machines using Juju!

Some caveats:

  • Juju needs a user called ubuntu to interact with its machines, so you will need to create this user in your CentOS AMI/template.
  • You need to host all RPM packages for juju, cloud-init and following dependencies in some yum repository (I haven’t submitted them to any public repository):
  • With this patched Juju, you will have a pure-centos cloud. It does not enable you to have multiple OSes in the same environment.

It’s important to notice that we are going to put some effort to make the Go version of juju born supporting multiple OSes, ideally through an interface that makes it extensible to any other OS, not Ubuntu and CentOS only.

[Rafael Biriba] Globo.com: Vagas para desenvolvedores – Venha trabalhar com a gente

Friday, July 27th, 2012

Conhece alguém da área de informatica ? Gostaria de trabalhar na maior empresa de conteúdo do Brasil ?

Mande seu curriculum para talentos@corp.globo.com e venha trabalhar com a gente.

Entrei como estagiário em 2008 e estou por aqui como analista pleno.
Nem preciso falar que EU recomendo a empresa né ? :)

Para estágio segue o link: http://estag.globo.com

Qualquer coisa estou a disposição para tirar dúvidas :)

TwitterFacebookShare

Leia também:

[Guilherme Garnier] Sprites automáticos com Compass

Thursday, July 26th, 2012

Um dos temas mais atuais no desenvolvimento web é a otimização de sites. A motivação para reduzir o tempo de carregamento das páginas vem não só de estudos que mostram que quando maior o tempo de carregamento, maior o número de usuários que abandonam o site, mas também do fato de que o Google considera o tempo de resposta na criação do PageRank.

Um dos itens mais importantes ao otimizar um site, de acordo com Steve Souders, é diminuir o número de requests. Um recurso muito útil para isso é a criação de sprites, ou seja, um único arquivo contendo várias imagens que são utilizadas no site. Nos locais que fazem referência a estas imagens, são definidos a largura, altura e offset do sprite. Desta forma, é feito um único request para obter todas as imagens. O uso de sprites é muito comum em grandes sites como Twitter, Facebook, Google e Yahoo.

A criação de um sprite manualmente é uma tarefa bem trabalhosa. É necessário criar uma imagem utilizando uma ferramenta como o Photoshop, por exemplo, e colar cada imagem uma abaixo (ou ao lado) da outra, deixando alguns pixels entre imagens. Sempre que se quiser adicionar uma nova imagem, será necessário abrir o sprite novamente no Photoshop e repetir o processo para inserir a nova imagem.

Para simplificar esta tarefa, é possível utilizar uma ferramenta para geração automática de sprites. Uma das melhores ferramentas para isso é o Compass. Após instalar e configurar, basta colocar as imagens num diretório e o sprite será gerado automaticamente. A instalação num projeto Rails é extremamente simples, e está bem explicada no help do Compass.

A configuração básica do Compass para geração de sprites segue o padrão abaixo:

$imagens-spacing: 2px
$imagens-sprite-dimensions: true
@import "imagens/*.png"

No exemplo acima, todas as propriedades são configuradas com o prefixo imagens. O sprite é configurado com espaçamento de 2 pixels entre cada imagem, para evitar sobreposição no limite entre as imagens. A segunda linha habilita a inclusão das dimensões das imagens no CSS gerado, o que é útil para manter fixo o tamanho ocupado pela imagem enquanto ela é carregada. A terceira linha informa quais imagens serão adicionadas. Neste caso, são todas as imagens com extensão png que estão no diretório imagens. É importante lembrar que o nome deste diretório deve ser igual ao prefixo utilizado nas propriedades.

Além de gerar o sprite, o Compass cria classes CSS para referenciar cada imagem. Os nomes das classes começam com o prefixo utilizado acima, seguido por hífen e o nome da imagem sem extensão. Por exemplo, para uma imagem chamada excluir.png, a classe teria o nome imagens-excluir.

O uso as imagens do sprite no seu CSS pode ser feito de duas formas: usando diretamente as classes criadas pelo Compass (como imagens-excluir, no exemplo anterior) ou utilizando um mixin do Compass no seu arquivo Sass:

.minha-classe { @include imagens-sprite(excluir); }

Ao utilizar uma destas configurações, a imagem será configurada como background do elemento.

Para criar um segundo sprite, para a parte administrativa da aplicação, por exemplo, é necessário utilizar um prefixo diferente, como no exemplo abaixo:

$imagens-admin-spacing: 2px
$imagens-admin-sprite-dimensions: true
@import "admin/imagens-admin/*.png"

Neste exemplo, as imagens do sprite estão no diretório admin/imagens-admin, e o prefixo segue o nome do último diretório (imagens-admin). Isso significa que, no exemplo acima, não seria possível manter o sprite do admin em admin/imagens, pois haveria conflito de nomes com o outro sprite.

Os sprites gerados pelo Compass são arquivos png que tem como nome o prefixo utilizado na configuração seguido por um hash (ex: imagens-b03bdb7a79370e7ff107e7b37fe7df6e.png). Quando o sprite é modificado (em ambiente de desenvolvimento o Compass verifica automaticamente a cada request se alguma imagem foi adicionada ou removida, e em produção é necessário executar um rake task para isso), o Compass gera um novo hash para o nome do arquivo. Isto é feito para evitar que o sprite seja cacheado pelo browser. Se isso acontecesse, o browser não buscaria o sprite atualizado, mantendo o arquivo anterior.

Os exemplos descritos acima descrevem apenas as configurações básicas para geração de sprites. A documentação de sprites traz mais detalhes sobre as opções de configuração. Além disso, o Compass tem muitas outras funcionalidades. Vale a pena pesquisar a referência no site do Compass para mais detalhes.

Posts relacionados:


[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.

[Bernardo Heynemann] thumbor is a freaking awesome project or why open source will win every time

Saturday, July 21st, 2012

People who know me are well aware that I love Open Source with all my heart. I have more than 50 open-source repositories in my github account. Some are maintained, some are not.

This post is not to talk about me, though. It is to talk about how freaking incredibly awesome Open Source is and how people will surprise you every time.

tl;dr

Thumbor is a much better project because of the MANY MANY contributions we have received from the comunity.

I can’t stress enough how incredibly fortunate we are that there are so many VERY SMART people out there willing to contribute back to our project.

That’s why Open-Source will win every time against proprietary software. Because of the people. Keep reading if you want to know more about our story.

The Project

Thumbor is an open-source image operations server. Don’t let this simple description fool you. IT IS powerful. It does INCREDIBLE stuff that saves our company a boatload of money.

When we started the project, the development team decided on open-sourcing the project.

It is general purpose enough as not to require any of our internal information and/or business details to leak.

This decision comes with some trade-offs, that seem very negative at first. A couple questions that came up?

  • How are we going to change the way Thumbor stores images? Do we need to fork the project to have our “company version” of it?
  • How do we load images from our domains only? (Repeat first question proposed solution and rinse)
  • How do we stop attackers from overloading our servers with requests for images of different sizes?
  • How do we stop competitors from using our software to their advantage?
  • How is this any valuable compared to using a proprietary solution (given we have the money to buy it)?

As you can see, there are many questions people came up with NOT to open-source the project.

We decided we would tackle each of those problems when their time came.

The Team

I want to give a brief description of the team behind Thumbor just to clarify why we decided to open-source it even in the face of so many questions.

First, there’s Fábio Costa. He’s a kick-ass developer, committer of the MooTools project and a great colleague. He’s also a BIG supporter of the Open-Source philosophy.

Rafael Carício is also a big-time supporter of Open-Source projects, being committer of Pyvows and many other open-source projects. Recently he spent two days just fixing issues with the default Python interpreter. Pretty awesome if you ask me.

Then, there’s Cezar Sá. Again, an avid Open-Source supporter. He’s committer of Rubinius, an alternative implementation of the Ruby language. He’s the guy behind Thumbor filters architecture.

The Decision

If we were going to open-source Thumbor, we needed to make sure it was as extensible as possible.

Every single part of Thumbor needed to be easily switchable for a different part with the same contract.

This kind of architecture is not simple to build, so we came up with parts that would be general enough so you can start using Thumbor right away.

We also needed to come up with a system to stop people from exploiting Thumbor to generate an infinite number of images and thus overload the server. We came up with encrypted URLs. We don’t believe in security by obscurity either, meaning that even if the software was closed source, people would exploit it.

The company we work for, globo.com, has many, many images (millions) and many users (nearly 5B page views/mo). So we had to make sure Thumbor was up to the task. So we fine tuned it.

The Premises

Ok, so what were our premises for thumbor?

  1. We need everything to be extensible, so we also need to come up with reasonable implementations of the extensible parts;
  2. We need Thumbor to be safe, so we must stick to secure by convention, meaning that if you don’t change a thing, Thumbor is secure;
  3. We need Thumbor to be fast so it can handle many operations per second without requiring expensive clusters

I’m skipping intentionally the main premise which is we want Thumbor to be the best software at cropping images which is what drove us to build it in the first place.

Skip a couple months to the future…

Ok, we have the first version done! Let’s go live with it.

So we fire up our servers and Thumbor is a go. We notice it’s a little slow, but hey, it’s doing its job and we started with a small team of users.

Then the unexpected happens!

Community created issues start popping up! And then they start coming up with CODE ATTACHED.

Now let’s stop for a moment and analyse this. There are MANY companies out there that charge A LOT of money for testing services.

We have FREE skilled testers in our project now. People who are proactively testing it for us and reporting back their findings.

Not only that, they are fixing our software for us and giving us back the code with NO STRINGS ATTACHED.

Let me say this again, these people, highly skilled individuals, all of them WITH JOBS, are working for free in a project they did not start.

This is humans at their best if you ask me!

Extreme Makeover

Remember I said that we’d implemented all the extensible parts and security?

That’s another incredible aspect of Open-Source Software: people READ your code.

People read ours. They found MANY, MANY things to improve/change/add/remove. We are grateful for every single one of them.

The project would not be as good as it is for our users if it wasn’t for the people that are contributing.

Why do I say that Thumbor underwent Extreme Makeover? Because if you look at the first version that we released and how Thumbor is right now there’s no way you would say its the same software.

Through contributions we improved storage, loading, graphics engines, security (A LOT), performance (A LOT) and our software practices.

That’s actually one thing of the process of developing open source software that is very humbling. People pay more attention to software practices like testing and continuous integration when they are trying to get their patches accepted.

And they call on you when you are slipping on your side of the fence. And we got called! And we listened. All of us came out of the process better at our craft.

The Conclusion

Thumbor has already payed for itself many times. It is so useful to us that we don’t care if our competitors use it, as long as the community keeps improving it.

As for buying a proprietary software, I haven’t found a single one that does the same as Thumbor and even if we do, we’ll never get this level of creativity, support and diversity from any given company.

This means if we have to choose again between open or closed source, I think we’ll stay with open source every single time.

HUGE MEGA THANKS WITH RAINBOWS AND UNICORNS

I think I did stress in this post how much I appreciate all the contributions, but I still feel obligated to thank you guys. Your contributions have been incredible and are all INVALUABLE.

So sincere thanks to (in no particular order):

[Rafael Biriba] Dropbox aumentará o espaço em disco dos seus planos

Tuesday, July 10th, 2012

O Dropbox anunciou hoje em seu blog o aumento dos planos de 50GB e 100GB para 100GB e 200GB.

Para os atuais clientes pagantes do dropbox essa mudança vai ocorrer automaticamente nessa madrugada. Nenhuma alteração foi feita com relação aos valores dos planos… Ou seja, $9 por 100GB agora :)

Também foi anunciado um terceiro plano de 500GB, para que precisa de mais espaço.

Todos os assinante do dropbox terão oportunidade de convidar seus amigos a experimentarem o plano Pro 100GB num trial de 3 meses.

Essa mudança aconteceu em uma excelente hora… Meu dropbox estava quase chegando a 95% de uso :)

Assim que liberarem o link para o trial, eu coloco aqui para quem quiser experimentar o plano de 100GB por 3 meses…

Pessoal, infelizmente o DropBox liberou apenas 1 convite por usuário assintante… Então não poderei liberar o link aqui =/ Mas não desanimem… Podem assinar o DropBox tranquilamente pois o serviço é excelente… :)

TwitterFacebookShare

Leia também:

[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.

[Rafael Biriba] 10000 coisas que todos os estudantes de computação deveriam fazer antes da formatura

Tuesday, July 3rd, 2012

Esse post é uma tradução do original “10000 things all ICS students should do before graduating”

Resolvi traduzir esse post pois achei a ideia do autor muito boa.

Hoje, muitos alunos de análise de sistemas, ciência da computação, engenharia da computação, e outros cursos da área, estão sendo formados com base em tecnologias antigas e ultrapassadas…

Por exemplo, creio que 80% de uma turma de um último período em analise de sistemas, não vai saber que a linguagem Ruby existe, ou muito menos a definição de TDD (Coisas que são utilizados hoje em dia em empresas grandes como a Globo.com).

Digo isso pois já entrevistei muitos candidatos para estágio, e o nível do pessoal está ainda bem no básico. Mesmo sabendo que para estagiar, você não precisa “saber nada”, existe algumas coisas que podem te ajudar a “se classificar melhor” numa seleção técnica.

Então abaixo segue a lista na integra retirada do blog mencionado acima:

0000 – Registrar seu próprio domínio
0001 – Instalar o Apache web server e configura-lo de uma forma não tão simples (Por exemplo, múltiplos sites rodando no mesmo apache)
0010 – Instale o WordPress e mantenha seu próprio Blog. Escreva bem os seus posts e regularmente. Uma boa escrita é uma habilidade fundamental para dominar esta profissão.
0011 – Rode seu próprio site na sua casa ou numa empresa de hospedagem.
0100 – Crie pelo menos uma aplicação web LAMP (Linux, Apache, Mysql, PHP). Seria legal se escrevesse duas. Uma em PHP e outra em Python…. Ou em Ruby (LAMR ?? haha)
0101 – Tenha seu próprio servidor (físico ou virtual) na nuvem
0110 – Instale o VMWare/Virtualbox ou equivalente para poder rodar mais de um sistema operacional em seu laptop
0111 – Configure seu roteador da sua cada para servir um site ou alguns arquivos do computador da sua casa para os seus amigos acessarem…
1000 – Use um sniffer de pacote (Wireshark) para aprender e ver as requisições de rede que são feitas entre seu computador e um jogo on-line favorito.
1001 – Faça contribuições a projetos open-source.
1010 – Desenvolva um aplicativo que use pelo menos umas das APIs Web mais populares como a do Facebook Connect ou do Google.
1011 – Habilite o Google Adsense em seu blog ou site, e ganhe um pouco de dinheiro baseado no tráfego virtual.
1100 – Compilar um projeto de código aberto complicado a partir do zero. Tente o FFMpeg por exemplo !
1101 – Ler obras de literatura e, além de desfrutar a leitura, preste muita atenção à forma como o autor conta a história e faz uso das palavras. Os programas devem ser tão cuidadosamente escritos como aquelas obras de arte!
1110 – Obter-se envolvido em um projeto de software onde os requisitos mudam no meio – cerca de 0,01% dos projetos pessoais e cerca de 99,99% dos projetos do mundo real, assim que encontrar um deste último tipo, concluir o projeto com a paciência e ter capacidade de aceitar críticas de forma construtiva.
1111 – Escrever um aplicativo usando Map-Reduce. Executá-lo no Google app-engine ou na Amazon EC2.

Eu particularmente teria adicionado na lista mais alguns pontos:

- Colocar todo código desenvolvido em algum “controle de versão” (Exemplo: Git)
- Estudar sobre testes automatizados e desenvolver utilizando a técnica TDD
- Considerando que a maioria das empresas utilizam um linux em seus servidores de produção, estudar conceitos básicos de unix e saber dar um ssh de um servidor para outro, por exemplo.

Espero que as dicas sirvam para alguns estudantes :) Não deixe de ler o post original na integra: http://tagide.com/blog/2011/06/things-ics-students-should-do-before-graduating/

TwitterFacebookShare

Leia também:

[Rafael Biriba] Timelapse: Acelerando seus vídeos usando o FFmpeg

Sunday, July 1st, 2012

FFmpeg: http://www.ffmpeg.org/

Para quem tem um vídeo de 9 horas de duração e gostaria de fazer um vídeo acelerado resumindo o video original, segue a solução usando ffmpeg:

ffmpeg -i video-grande.mp4 -vf setpts=0.05*PTS video-rapido.mp4

Agora, entenda a linha:

-vf é a opção para habilitar o “Video Filter”. Para o parametro setpts você deve passar a taxa de aceleração. Para fazer o “timelapse”, o ffmpeg irá descartar uma série de frames, e com isso é possível gerar um vídeo “acelerado”.

No exemplo que usei acima, um video de 20 minutos é acelerado para um de apenas 1 minuto. Portanto o calculo: 1/20=0.05

Para acelerar um vídeo de 9 horas para 3 minutos você teria que calcular da seguinte forma:
3/(9*60)=0.00555, então o parametro ficaria: “-vf setpts=0.00555*PTS”

Espero ter ajudado com essa pequena dica :)

TwitterFacebookShare

Leia também: