Livro sobre C e Linux

Hoje decidi abrir uma exceção e postar esse link aqui. É um livro que escrevi há 9 anos e nunca foi revisado nem publicado, mas que ainda pode ser útil para muitos novos programadores.

 

Anúncios

cyclone ganha um site: cyclone.io

Há pouco mais de 2 anos atrás, o FriendFeed havia sido comprado pelo Facebook, e uma das coisas mais interessantes pra nós, na época, foi a publicação do web server que eles haviam desenvolvido para uso interno. Era Setembro de 2009.
Sua descrição permanece a mesma até hoje:

Tornado is an open source version of the scalable, non-blocking web server and tools that power FriendFeed. The FriendFeed application is written using a web framework that looks a bit like web.py or Google’s webapp, but with additional tools and optimizations to take advantage of the underlying non-blocking infrastructure.

Muita gente reclamou pelo fato do FriendFeed não ter procurado o pessoal do Twisted, pois desde os primórdios já era a lib padrão pra programar servidores assíncronos em Python. Ao invés disso, os caras do FriendFeed postaram coisas reclamando do Twisted – que era lento, mal documentado, etc; tudo que ainda reclamam nos dias de hoje. Veja aqui o anúncio do lançamento do Tornado, por Bret Taylor.

O fato é que o Tornado usava um IO loop próprio, e era bem mais rápido que a API nativa do Twisted pra web, Twisted Web. Além disso, o Tornado era baseado no framework que se chamava de anti-framework, web.py, por ser absurdamente simples. Mais ainda, o próprio Google havia publicado no mesmo ano o App Engine, que também era baseado no web.py. O resultado é que escrever um web service usando Tornado era muito mais interessante e obviamente mais simples que usar o Twisted Web.

Mas, como quase tudo, o Tornado tinha um grande problema: nenhum protocolo nativo do Python funcionava naquele IO loop. Nem mesmo coisas básicas como fazer uma query em DNS, ou acessar um PostgreSQL. O Twisted, já naquela época, tinha uma lista enorme de protocolos nativos, todos implementados pro IO loop do Twisted, e ainda mais protocolos que a própria lib padrão do Python. Protocolos como IRC, SSH, e suporte nativo pra qualquer database suportado pela DB API do Python, todos podiam ser mixados de maneira assíncrona no mesmo app, e coisas alucinantes podiam acontecer dentro de um web server.

Mas, justamente o protocolo HTTP era implementado pro Twisted Web, e o grupo de pessoas que cuidava disso havia criado o que seria um framework completo pra desenvolver aplicativos web, chamado Divmod. Por isso, eles não deram muita atenção pro Tornado, e pareciam focados nos componentes do Divmod que incluíam entre outros, até um sistema de banco de dados alternativo.

Dois dias depois do anúncio do Tornado, o glyph (cara que criou o Twisted) publicou um texto em seu blog dizendo o que ele gostaria que o Tornado fosse. Esse texto fez com que várias pessoas ficassem reclamando e chorando na teia, tipo esse post aqui.

Naquela época, eu estava desenvolvendo algumas coisas com Twisted Web, e obviamente fui testar o Tornado. No #twisted da freenode havia mais gente como eu, e o que mais havia progredido com algo interessante era um cara chamado Dustin Sallings. Ao invés de ficar chorando, ele havia portado o Tornado pra Twisted. Era exatamente o que deveria ter sido desde o começo. Em algumas conversas, ele havia dito que “estava pronto”.

Então, nasceu o cyclone. O motivo era simples: o Twisted Web era uma API pra criar web servers, e muito bagunçado pra criar web apps. O Tornado, era uma API muito decente, mas servia apenas pra criar web apps, sem suporte a nenhum outro protocolo além de HTTP. O cyclone, um mix dos dois: a API do Tornado pra criar web apps, mas que interagem com todos bancos de dados possíveis, e ainda todos protocolos do Twisted – incluindo os protocolos que se comunicam com sub-sistemas de telefonia.

Nos seus 2 anos de vida, o cyclone evoluiu muito. Muita gente contribuiu, mandou patch, arrumou bugs, adicionou coisas… e recebo alguns emails por semana de gente que usa e gosta, e agradece por ter algo como o cyclone disponível. Além de ser extremamente estável, passou a ter suporte nativo a sqlite, redis, mongodb, e uma gigantesca lista de features – tem até uma API semelhante à do bottle como opção. Coisas muito interessantes como o Nuswit, o RestMQ e o FreeGeoIP são baseados no cyclone.

Finalmente, peguei o domínio e o Gleicon comandou no site: cyclone.io


yeah, tudo assíncrono!

Ultimamente tem sido tudo assim, assíncro. O Nuswit já vai fazer aniversário de 1 ano, e vale lembrar que está em produção contínua, sem dar nenhuma manutenção.

Já estou mais que convencido que o caminho pros próximos anos dessa década não pode ser outro, ainda mais com o WebSocket no w3c.

Para contribuir com a interwebs, tenho mantido os seguintes projetos:

http://github.com/fiorix/cyclone
Um clone do Tornado, webserver assíncrono do FriendFeed, que desde o ano passado é do Facebook. Essa implementação, batizada de Cyclone, tem algumas diferenças:

  • Core I/O baseado no Twisted
  • Suporte nativo a XMLRPC
  • Suporte a localização baseada no gettext – ao invés do CSV, original do Tornado

Com vários aplicativos de exemplos, todos os plug-ins do Tornado para autenticação no Google, Twitter, Facebook, OAuth, OpenID, etc…

O RestMQ (coisas do Gleicon, que ajudei a implementar) é baseado nele. A nova versão do Nuswit também será.

http://github.com/fiorix/twisted-twitter-stream
Uma API bem simples para acessar a Streaming API do Twitter. Provê suporte a todos os métodos publicados pela API.

Não depende do TwistedWeb, a implementação do HTTP 1.1 está inteira no código – na verdade, apenas o lado do client com suporte a Comet.

Permite criar sistemas como este.

http://github.com/fiorix/txredisapi
Um driver assíncrono pro Redis, também baseado no Twisted. O protocolo de comunicação já existia, mas era carente de algumas coisas, que implementei:

Além de estável, é muito rápido! Também foi usado no RestMQ, e aparentemente, está se tornando popular. Hoje achei algumas referências enquanto procurava no Google.

http://github.com/fiorix/mongo-async-python-driver
Outro driver de banco de dados, pro MongoDB. O driver original para Python é síncrono, o que dificulta (embora não impossibilita) de usar em sistemas assíncronos, especialmente baseados no Twisted.

Boa parte da implementação é baseada no pymongo original, inclusive o codec de BSON (em C), formato binário usado pelo Mongo, baseado em JSON.

Provavelmente se tornará o driver assíncrono oficial do Mongo para Python+Twisted, e está em vias de se tornar estável – isso devido às várias mudanças na API, e implementação de vários recursos incluindo Lazy Connections, e Document Reference.

Também, já tem algumas pessoas de olho no GitHub, acompanhando o desenvolvimento.

Entre os vários dbs nosql (couch, redis, etc) o Mongo é um dos mais completos, com uma linguagem de query muito decente, entre os vários outros recursos nativos. O fato de usar mmap para acessar os dados também faz com que ele seja muito rápido.


tornado

Logo após o Facebook comprar o FriendFeed por meros U$50 milhões, não demorou pra começarem a publicar software gratuito.

O primeiro da fila foi o servidor e framework web usado pelo FriendFeed, chamado Tornado.

O FriendFeed é famoso por sua integração com Facebook, Twitter e Google. O que ninguém sabia, é que boa parte dessa integração é feita direto no servidor e framework web. Ainda, um dos recursos mais legais é o stream de dados do servidor assíncrono, também conhecido como comet, que permite long polling consumindo o mínimo de recursos.

É interessante como nos últimos anos essas tecnologias avançam no ambiente web. Na década de 90, a coisa mais moderna era desenvolver um CGI isolado, em Perl, que era executado pelo Apache, que por sua vez distribuía as requisições para um de seus processos usando um pool.

Depois, o procedimento de executar um programa externo foi se tornando um pouco ultrapassado, e o CGI foi parar dentro do Apache, com nomes como PHP e Coldfusion. Nessa época, enfiaram até Java dentro do Apache, colocaram o nome de Tomcat nele, e os CGIs passaram a se chamar Servlets.

Anos depois, fizeram o mesmo com o Python, e colocaram lá dentro do Apache o mod_python. No caso específico do Python, foram ainda mais longe: criaram um protocolo padrão para aplicativos web se comunicar com servidores web, chamado WSGI, e também foi parar dentro do Apache com nome de mod_wsgi.

Me lembro de cada uma dessas etapas, das dificuldades e dos benefícios de cada uma dessas adaptações. Programei em todas essas linguagens, nos últimos 15 anos. Meia vida.

Mais recentemente, antigas tecnologias como o FastCGI reabriram o caminho para aplicativos web 2.0. Servidores mais leves que o Apache, como o lighttpd e nginx começaram a ganhar muito espaço em sites com grande volume de acesso, por consumirem menos recursos trabalhando de forma assíncrona.

Mas antes do Apache, já existiam diversos serviços de rede que usavam o conceito de conexão assíncrona. Os servidores de irc, por exemplo, já formavam redes com centenas, e depois milhares, e depois centenas de milhares de usuários simultâneos, no bate-papo infinito.

Quanto mais o ambiente web cresce, mais pessoas acessam sites. Esses sites precisam de mais servidores, e esses servidores precisam de mais recursos. Usar conexões assíncronas permite aceitar mais pessoas simultâneas, consumindo menos recursos que tecnologias baseadas em thread, por exemplo. É uma espécie de redução de custo com otimização de recurso, ao mesmo tempo. Para programadores, é um conceito bem diferente do tradicional, especialmente para web, e exige um pouco de cérebro para se adaptar.

Os sistemas operacionais já vêm evoluindo suas internas para aceitar mais e mais conexões simultâneas, consumindo menos e menos recursos. Nomes como epoll, kqueue e /dev/poll estão cada vez mais no dia-a-dia dos programadores. E facilitadores como a libevent também.

O resultado disso é que os novos frameworks para aplicativos web já possuem servidores web embutidos, como é o caso do Tornado, Twisted Web, Merb, etc etc… Na frente deles, lighttpd e nginx fazem a distribuição de carga, e milhares de conexões web simultâneas, em um único computador físico, começa a se tornar realidade.


mais sobre crawlers e spiders

logo_mercadolivreNo mês passado escrevi um artigo com um programa para capturar todos os items da primeira página de cada categoria do MercadoLivre.

Lá, lidava com alguns problemas como:

  • limite de concorrência no download das páginas
  • processamento de html em thread, síncrono
  • manter a maior parte do processo assíncrono, para ganhar tempo e CPU

Depois disso, precisei fazer umas alterações no código e acabei modificando um pouco programa, usando outras técnicas como:

Cada item desta lista corresponde aos itens da lista mais acima, respectivamente.

O esquema de cooperação do twisted é muito melhor que o Controller que havia criado anteriormente. Porém, muito mais complicado para jovens aprendizes. Recomendo este link para mais detalhes.

Sobre o processamento do html, vale a pena verificar o lxml. Antes, havia usado BeautifulSoup, que é muito bom, mas perde violentamente em desempenho e suporte a broken-html.

Por fim, o truque de usar generators para executar alguns callbacks inline é incrível, e absurdamente prático em casos como esse, do programa abaixo.

O resultado é final é o mesmo, mas a melhoria em desempenho é absurda. Fiz alguns testes na minha máquina e obtive o seguinte:

  • esta versão consome, em média, 20% menos de CPU
  • como não há necessidade de gravar os arquivos no disco, não consome disco
  • o processo todo ficou 657% mais rápido, simplesmente
  • ainda, o código é muito menor +_+

Veja ai:

#!/usr/bin/env python
# coding: utf-8

from lxml import html
from twisted.web import client
from twisted.python import log
from twisted.internet import task, defer, reactor

class MercadoLivre:
    def __str__(self):
        return 'http://www.mercadolivre.com.br/jm/ml.allcategs.AllCategsServlet'

    def parse_categories(self, content):
        category = subcategory = ''
        doc = html.fromstring(content)
        for link in doc.iterlinks():
            el, attr, href, offset = link
            try: category = el.find_class('categ')[0].text_content()
            except: pass
            else: continue
            if category:
                try: subcategory = el.find_class('seglnk')[0].text_content()
                except: continue
                else: yield (href, category, subcategory)

    def parse_subcategory(self, content):
        doc = html.fromstring(content)
        for element in doc.find_class('col_titulo'):
            yield element[0].text_content()

class Engine:
    def finish(self, result):
        reactor.stop()

    @defer.inlineCallbacks
    def fetch_categories(self, link, parser):
        try:
            doc = yield client.getPage(link)
            defer.returnValue(parser(doc))
        except Exception, e:
            print e

    def fetch_subcategory(self, links, parser, limit):
        coop = task.Cooperator()
        work = (client.getPage(link[0]).addCallback(parser).addCallback(self.page_items, *link) for link in links)
        result = defer.DeferredList([coop.coiterate(work) for x in xrange(limit)])
        result.addCallback(self.finish)
        result.addErrback(log.err)

    def page_items(self, items, href, category, subcategory):
        print 'Categoria: %s / %s' % (category.encode('utf-8'), subcategory.encode('utf-8'))
        for item in items: print ' -> %s' % item.encode('utf-8')
        print ''

def main(limit, *parsers):
    e = Engine()
    for parser in parsers:
        links = e.fetch_categories(str(parser), parser.parse_categories)
        links.addCallback(e.fetch_subcategory, parser.parse_subcategory, limit)

if __name__ == '__main__':
    reactor.callWhenRunning(main, 150, MercadoLivre())
    reactor.run()

novo serviço de geo localização

freegeoipnetAos poucos, vou tirando meu serviço de Geo Localização do ar. Apesar de bastante acesso por dia no freegeoip.appspot.com, os problemas com o datastore do GAE me desanimam.

O serviço está no ar há cerca de 1 ano, e a média de acesso é de 10 e 15 requests por segundo. A recente redução das cotas tiram o serviço do ar muitas vezes, e isso porque diminuí ao máximo o nível de processamento, e a maioria das buscas é feita em memória direto no memcache.

freegeoip-quotas
Apesar do sistema ainda funcionar, não tenho mais paciência pra atualizar os dados lá. O datastore não permite deletar os dados antigos de uma maneira simples, e por isso gasta muito processamento quando uso o bulkloader, ou mesmo a remote_api.

Cansado de todos esses problemas, acabei pegando um VPS no slicehost.com e fiz uma versão nova do sistema. Tornei o código fonte disponível pela GPL v2, e daqui um tempo vou redirecionar o serviço do GAE pro slicehost.

Além de rodar mais rápido, é muito mais fácil pra atualizar o banco de dados de IPs, e tenho shell na máquina.

Se você costumava usar o serviço antigo, passe a usar o novo:

http://freegeoip.net

Espero que em breve possa atualizar meu banco de dados usando a Geolocation API que está sendo definida pelo W3C.

Sobre o slicehost: é o melhor serviço de hosting que já encontrei, por um preço ótimo. O formulário de signup é bem simples, e a configuração do ambiente é incrível. Ainda, permitem adicionar seus domínios direto no DNS deles, e apontar pras máquinas virtuais.