Aggregated feeds from the Django community.
Brian Rosner just now, with Changeset 7967, merged the NewForms-Admin branch of Django into Trunk. This is a huge step forward in the push to version 1.0. I want to personally thank all of the numerous contributors that have made NewForms-Admin possible. It’s been an enormous amount of work, and I am so appreciative. Thanks!
Watch Brian Rosner merge Django NewForms-Admin live. This might be a first.
Just looking through DjangoSites.org and Googling around, I'm absolutely puzzled by the lack of Django-based browser games. It is such an idea platform for developing these, yet there are only a few that are actually open to the public (some of which aren't in English, and are thus inaccessible to me). There seem to be a few games out there in French, Chinese, and some other things I don't really recognize, though.
Here are a few games in English I've been able to round up:
I'm particularly interested in anything open-source, as I'd love to contribute from time to time, but I'd love to hear of any playable games that I have missed, or any open-source games in development. As far as close-sourced games in development, they're not of much interest to me if they're at least not open for testing. Without access to the source or a reachable site, it's as good as vaporware (and many of them never see completion).
So speak up, let me know if there's something that I've completely missed!
My new search engine is gaining steam! Traffic has doubled in just two days. I hope the momentum keeps up!
http://bitcircle.com
Anyway, quite a few announcements in this post.
(Regular readers, please pardon a small, targeted begging request.)
I'm trying to make it to Washington, DC, for the Django sprint on August 1. Is there anybody in the area who has a couch or floor I could sleep on for the night before the sprint and the night afterwards? If I can save having to buy a hotel room around the area in the peak of summer, that would be nice.
Any responses to "malcolm at pointy-stick" (or firstname.lastname at gmail). Thanks.
This Week in Django is a weekly podcast about all things Django.
This week we have a special guest, Kevin Fricovsky, that joins us as we talk about DjangoCon, EuroPython, a few source commits, some cool projects from the community, and the Tip of the Week.
Que tal aprender uma nova linguagem enquanto faz uma boa tarefa para melhorar a escalabilidade do banco de dados?
Bom, foi isso que eu fiz hoje durante quase todo o dia. Descobri no início do dia que Lua é a linguagem de script do MySQL Proxy e mergulhei pra fazer duas coisas bastante interessantes: Load Balancing e Cache de resultados.
Load Balancing
Eu conheço 3 formas de fazer load balancing com MySQL:
Mas na verdade não conhecia em profundidade nenhum deles, apenas uma idéia superficial.
No final de semana testei o Master/Slave. Parece ser o mais poderoso dentre os três métodos, mas usá-lo no Django significa mexer em boa parte do código do ORM. Não é a minha intenção. Cheguei a construir um backend pra isso, em cima do backend do MySQL, e até descobri que havia outro semelhante [4]. Mas nem o meu backend improvisado, nem o do Ivan Sagalaev me seduziram: muito limitado e cheio de falhas de design.
Pois bem, o MySQL Cluster também me desanimou depois que eu li diversos depoimentos contrários [5].
Sobrou o MySQL Proxy.
MySQL Proxy
Este é um software um tanto recente da MySQL, que basicamente faz a ponte entre o(s) servidor(es) de MySQL e a sua aplicação. É vantajoso pois é independente de linguagem ou framework: você desenha as regras e o que vem depois delas pouco importa, funciona da mesma forma.
Não quero explicar como instala e usa este software, portanto vou me limitar ao script que montei para fazer load balancing. Não foi devidamente testado e em algumas situações foi exibido o erro (1105, '#07000(proxy) all backends are down'), portanto, antes de ir migrando seu servidor, faça muitos testes e verifique mais detalhes.
O script, feito em Lua, ficou assim
function connect_server()
local num = tonumber(os.date("%S")) % 2
proxy.connection.backend_ndx = num + 1
print("Using " .. proxy.backends[proxy.connection.backend_ndx].address)
endSalve o arquivo como load_balancing.lua e execute da seguinte forma:
mysql-proxy \
--proxy-lua-script=load_balancing.lua \
--proxy-backend-addresses=127.0.0.1:3306 \
--proxy-backend-addresses=vs2:3306O parâmetro --proxy-backend-addresses pode ser repetido quantas vezes quiser, um para cada réplica do MySQL, quanto mais réplicas, mais poderoso é o seu "cluster".
Os hosts "127.0.0.1" e "vs2" são respectivamente, minha máquina e uma máquina virtual rodando no vmware-server, portanto, use os IPs ou hostnames conforme a sua realidade.
Ele não suporta master/slave (não encontrei nada na documentação que orientasse como fazer nesse caso), portanto foi necessário configurar os hosts como master/master, que você pode ver como fazer em [6]
O funcionamento é assim: nos segundos pares as conexões são repassadas ao servidor 1, e nas ímpares as conexões são repassadas para o servidor 2. Se houvessem 10 servidores, o módulo de 2 (local num = tonumber(os.date("%S")) % 2) seria feito com 10 (local num = tonumber(os.date("%S")) % 10) e o comando de chamada do mysql-proxy teria 10 vezes o parâmetro --proxy-backend-addresses. Simples né?
Cache em memória
Bom, eu já havia feito o mesmo tipo de coisa hackeando a QuerySet e criando o método .cache() [7], que me ajudou muito. Esta solução que eu criei hoje pode ser considerado inferior à anterior por oferecer menos granularidade e funcionar somente com MySQL, mas caso você não queira modificar o código original do Django, esta pode ser uma boa alternativa para você.
Este segundo script funciona da seguinte forma: quando uma consulta do tipo SELECT é feita ao banco de dados, seu resultado é armazenado em um servidor Memcached, com tempo de expiração definido por pattern (cada pattern, ou seja, cada modelo de SELECT pode possuir um tempo de expiração diferente) e se uma cosulta idêntica for requisitada dentro do tempo de expiração, o servidor de cache será consultado, ao invés do banco de dados.
Bacana né?
Então lá vai
-- Packages required require("Memcached") -- http://luamemcached.luaforge.net/ require("json") -- http://www.chipmunkav.com/downloads/Json.lua -- Connect to memcached server local conn = Memcached.Connect('localhost', 11211) -- Default expire time for cache items local default_expire_time = 30 -- Prefix for cache keys local key_prefix = 'mysql-proxy-' -- Patterns to define expiration time for different types of queries. -- More details in: http://lua-users.org/wiki/PatternsTutorial local patterns_expire_time = { {'^%s*select .+from .*auth_user', 150}, {'^%s*select', default_expire_time}, } -- Converts a string to valid key function encode_key(str) return key_prefix .. string.gsub(str, ' ', '-') end -- Converts a resultset to JSON. This can't be done by -- Json library directly because it's a userdata datatype -- instance function resultset_to_str(resultset) local rfields = resultset.fields local rrows = resultset.rows local fields = {} local rows = {} local pos = 1 -- Rows for row in rrows do rows[pos] = row pos = pos + 1 end -- Fields pos = 1 for i = 0, #rfields do if rfields[i] then fields[pos] = { type = rfields[i].type, name = rfields[i].name, } end pos = pos + 1 end return Json.Encode({ fields = fields, rows = rows, }) end -- Callback called before request database server function read_query(packet) if string.byte(packet) == proxy.COM_QUERY then local sql = string.sub(packet, 2) if string.match(string.lower(sql), '^%s*select') then -- Transform to valid key string local key = encode_key(sql) -- Gets from cache local rset = conn:get(key) -- If not found in cache, requests from database server if rset == nil then proxy.queries:append(1, packet) return proxy.PROXY_SEND_QUERY end -- Print out print('from cache', key) -- Json -> table rset = Json.Decode(rset) -- Todo: check for error returns proxy.response.type = proxy.MYSQLD_PACKET_OK proxy.response.resultset = rset return proxy.PROXY_SEND_RESULT end end end -- Callback called after request to database server function read_query_result(inj) local res = resultset_to_str(inj.resultset) local sql = string.lower(string.sub(inj.query, 2)) local key = encode_key(sql) local expire_time = default_expire_time -- Looks at patterns for respective expire time for i = 0, #patterns_expire_time do if patterns_expire_time[i] and string.match(sql, patterns_expire_time[i][1]) then expire_time = patterns_expire_time[i][2] break end end -- Saves to cache conn:set(key, res, expire_time) end
Lá no início, as linhas que definem local conn e local patterns_expire_time devem ser ajustadas à sua realidade (endereço do servidor e patterns). Para saber como definir expressões regulares em Lua veja em [8] (é um pouquinho diferente do convencional).
Este script depende de dois pacotes externos à linguagem: json.lua [9] e Memcached.lua [10], que por sua vez depende do LuaSocket [11].
No Ubuntu, quando se instala o pacote mysql-proxy a lib da Lua 5.0 é instalada também, mas acontece que os pacotes que eu citei acima - especialmente o LuaSocket - não funcionam corretamente com a versão 5.0.
A solução foi instalar a versão 5.1 em paralelo (o LuaSocket possui um pacote no Ubuntu chamado liblua5.1-socket2) e eliminar a pasta antiga de bibliotecas da versão 5.0 (/usr/share/lua/50/), criando um symlink da versão 5.1 (/usr/share/lua/5.1/) com o mesmo nome.
Ainda foi necessário definir a seguinte variável de ambiente
export LUA_INIT=@/usr/share/lua/50/compat-5.1.lua
Por fim, para dar vida ao script, basta executar
mysql-proxy \
--proxy-lua-script=cached_queries.lua \
--proxy-backend-addresses=127.0.0.1:3306Ao executar este comando, será aberta a porta 4040 que deve ser setada na setting DATABASE_PORT. Caso queira saber como sobrepor a porta 3306, veja em [19].
Uma observação importante: o MySQL possui um bug [12] (ou sei lá o que é) que obriga conexões para "localhost" serem via porta 3306, isso vale tanto para o client quanto para o pacote MySQLdb do Python. Você faz uma conexão para a porta 1365465321 ou qualquer outra e ele aponta para 3306. Portanto, ao usar a porta 4040, mude a setting DATABASE_HOST para "127.0.0.1" caso esteja usando "localhost".
No script acima há ainda uma séria limitação quanto ao tamanho da SELECT. Caso ela tenha mais que 245 caracteres, você terá um erro de tamanho da chave no cache. A solução é converter a expressão SELECT com MD5, SHA ou outor algorítimo que crie uma string única em cima de uma SELECT complexa. Eu não consegui fazer isso ainda, mas recomendo expressamente que faça esse ajuste eu espere que eu o faça antes de colocar em um servidor de produção.
Para mais detalhes sobre MySQL Proxy, veja em [13], [14], [15] e [16]
Para mais detalhes sobre Lua, veja em [17] e [18].
É isso aí... artigo feito no fim da noite tem quer ser rápido assim. Dúvidas, é só falar :)
PS: apesar de serem muito úteis para quem usa Django, todas essas configurações são compatíveis com qualquer linguagem ou sistema operacional, e com versões igual ou acima de 5.1 do MySQL.
PS2: agradeço ao Javier Guerra e David Given pos esclarecimentos sobre a arquitetura da Lua e ao Giuseppe Maxia pelo bom tutorial [19] que desenvolvou sobre MySQL Proxy
PS3: essa foi a primeira vez na vida que escrevi algo em Lua, por favor, sinta-se à vontade para apontar eventuais falhas
Links relacionados
| [1] | http://dev.mysql.com/downloads/cluster/index.html |
| [2] | http://forge.mysql.com/wiki/MySQL_Proxy |
| [3] | http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-replication-connection.html |
| [4] | http://softwaremaniacs.org/soft/mysql_cluster/en/ |
| [5] | http://blog.globoi.com/producao/2008/04/16/brasileiros-na-mysql-conference/ |
| [6] | http://www.howtoforge.org/mysql_master_master_replication |
| [7] | http://marinho.webdoisonline.com/blog/p/metodo-cache-para-queryset_158/ |
| [8] | http://lua-users.org/wiki/PatternsTutorial |
| [9] | http://www.chipmunkav.com/downloads/Json.lua |
| [10] | http://luamemcached.luaforge.net/ |
| [11] | http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/ |
| [12] | https://bugs.launchpad.net/ubuntu/+source/mysql-dfsg-5.0/+bug/241802 |
| [13] | http://del.icio.us/marinho/mysql+escalabilidade |
| [14] | http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy.html |
| [15] | http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy-scripting.html |
| [16] | http://classdump.org/articles/2008/02/14/mysql-proxy-enhancements |
| [17] | http://www.lua.org/manual/5.1/pt/ |
| [18] | http://lua-users.org/wiki/TutorialDirectory |
| [19] | (1, 2) http://dev.mysql.com/tech-resources/articles/proxy-gettingstarted.html |
Wapi is somewhat 80% finished, so I think it's ready for the first release. The zipfile includes the wapi code as well as some examples taken from byNotes.
You'll notice there are authentication methods for Basic, Digest and OAuth. However, only the former is usable for now, since the other two depend on Django applications which are't ready for release (but are likely to be ready before this week ends).
If you try this, leave some feedback in a comment ;). I advise you to not use this code into production yet, since the API may change in the near future.
There’s been lots of discussion and hints over the past several days about a forthcoming Django based conference called DjangoCon. The official announcement was just released. I’ve always been fond of small intimate group settings and this one will certainly be that with only 200 attendees allowed to participate. I only hope that I get one of the coveted spots.
It's always been a goal of mine to post screencasts here on my blog, but for whatever reason I never ended up getting around to it. Today, that all changes as I have created two new screencasts. Of course, this space is already very well-covered by both Michael Trier and Brian Rosner, so hopefully this adds something new to the conversation.
In this screencast I show how I typically set up my Django development environment. It goes through installing Django by checking out the latest development version and linking it to the correct places on your system. It also talks about how to install reusable applications. Finally, it covers how to update all of those projects and keep a toolbox of snippets for your personal use.
The simple pylink command that I use in the screencast is this:
#!/bin/bash
ln -s `pwd`/$1 `python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"`/$1
Please let me know in the comments if you have any other tips and tricks for setting up a development environment for Django.
Django-pagination is an application that I wrote and released a while ago, which I use all the time, but that hasn't really seen much attention. In this screencast, I show how to take an existing project with too much data on one page, and use django-pagination to quickly and easily paginate the items on the page. There is a bit more documentation for the project that's available in the project directory if you do a subversion checkout, and docstrings throughout the source code, if you're interested in how it works.
These are my very first screencasts, ever. I'm not entirely sure what I'm doing yet, and the only way I can improve is by your feedback. If you have any advice and/or criticisms of these screencasts, please don't keep your mouth shut--speak up, and let me know in the comments. Hopefully someone finds these useful, and thanks for watching!
Heard from Robert Lofthouse on Twitter. The Djangocon 2008 conference will be held at Google Campus (Googleplex) in Mountain View!! September 6 and 7th. That's only two months away, so hopefully this gets pulled together well. An official post should be up on djangoproject.com soon, and the conference website will launch on Friday.
DjangoCon 2008. Venue: Gooleplex, San Francisco Bay Area. Dates: 6th and 7th Sept. Official post will be on djangoproject.com soon.
Já faz alguns meses que eu queria fazer alguns testes com o mod_wsgi, a alternativa ao mod_python que vem sendo cada vez mais comentada e sugerida na comunidade Django.
Ontem eu aproveitei o dia tranquilo pra unir o útil ao agradável e colocar isso em prática.
O que é WSGI?
Vamos "começar do começo": WSGI é mais simples do que parece, trata-se de uma interface definida pela PEP 333 [1] para intermediar a comunicação entre servidores web e frameworks Python. Ela surgiu pela necessidade qualquer um de nós nota com um pouco tempo: Python tem quilos de frameworksmuitas delas excelentes, e às vezes o mod_python parece ser um ornitorrinco em um ninho de coelhos quando implantamos alguns sites. É esquisito.
Mudando para o mod_wsgi
Para usar o WSGI puramente, é bastante simples, vamos fazer um pequeno script para ilustrar
def application(environ, start_response): status_code = '200 OK' headers = [('Content-Type', 'text/html')] start_response(status_code, headers) return ['So <b>testando</b>!'] from paste import httpserver httpserver.serve(application, port='8000')
Ao executar este script minúsculo e carregar em seu navegador a URL "http://localhost:8000", será exibido "So testando". Simples não? Veja
$ python my_app.py serving on http://127.0.0.1:8000
No Django, a coisa exige só um pouco mais de detalhes, mas nada complexo. Como já explanado há poucos dias pelo Eric, da Metaphormedia, em [2], é recomendável que se crie dentro de seu projeto uma pasta "apache" com um script dentro chamado "django.wsgi" - estes nomes são escolha sua, mas eles têm sido usados por mais de uma referência. Com o conteúdo abaixo (modificado segundo as suas necessidades)
#!/usr/bin/env python import os, sys sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)+'/../')) os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler()
E configure seu apache, removendo o código que implementa o mod_python e adicionando o seguinte
WSGIScriptAlias / /caminho/do/seu/projeto/apache/django.wsgi
Agora, para funcionar, é preciso que seu sistema operacional tenha o mod_wsgi instalado. No Ubuntu/Debian você instala com o seguinte comando
$ sudo apt-get install libapache2-mod-wsgi
Pronto, ao reiniciar o Apache, seu projeto estará lá, bonitão, rodando através do WSGI :)
Porque mod_wsgi?
Bom, essa deve ser a pergunta que você deve estar se fazendo. Se o mod_python sempre foi a alternativa recomendada e funciona bem, porque mudar para o mod_wsgi?
O mod_wsgi tem se comportado mais escalável que o mod_python. Por escalabilidade, entende-se a capacidade de um site de suportar o crescimento de uso sem perder a performance ou travar.
Ao ajustar um servidor para usar o mod_wsgi, pude notar essas diferenças. Elas não são tão grandes que se possa perceber com um simples teste com Apache Bench. É necessário forçar para notar a diferença, que tem sido até 10% superior.
Esse servidor que usei para fazer esses testes é uma VM (em Xen) com 512MB de RAM, rodando em um amd64, numa rede bastante estável, em Londres, enquanto que o Apache Bench foi executado em minha máquina.
Veja abaixo como os dois modos se comportaram:
Para compreender a legenda, o número após o "n" trata-se da quantidade de requisições enviadas e o número após o "c" trata-se da quantidade de requisições concorrentes, ou seja, "n1000c5" equivale a "1000 requisições enviadas de 5 em 5).
Tempo de resposta por requisição
Requisições por segundo
Load de 1 minuto
Load de 5 minutos
RSS / Memória real ocupada
Percentual da CPU
Conclusões
Todos os números apresentados acima não podem ser tomados como base exata para comparação, por alguns motivos, como a influência que o teste anterior efetua no subsequente, a oscilação da internet e a influência de outros processos.
Eu fiz questão de usar um servidor em produção onde está sendo servido um outro sistema da empresa, que mesmo que não estivesse em seu horário de pico, era usado normalmente por alguns usuários.
Há ainda toda a questão de controvérsias em torno do uso real da CPU e da memória.
Mas dá pra notar uma realidade clara: o mod_swgi se mostra mais vantajoso em memória e tempo de resposta. Não esqueçamos que 200 requisições concorrentes para um VPS com 512MB de RAM é uma senhora carga, equivalente a alguns milhões de pageviews por mês.
No teste de 2000 requisições a 200 concorrentes, ficou visível que a configuração de MaxRequestsPerChild (de 1000) entrou em cena e fez diferença no desempenho, tanto para segurar a carga quando para mudar um pouco a evolução do desempenho.
Por fim, devo avisar que no teste seguinte, de 5000 requisições a 200 concorrentes, o Apache caiu antes de completar 3000, em 3 tentativas.
Mais detalhes sobre o WSGI podem ser encontrados em [4], [5] e [6]
Links relacionados
| [1] | http://www.python.org/dev/peps/pep-0333/ |
| [2] | http://www.ericholscher.com/blog/2008/jul/8/setting-django-and-mod_wsgi/ |
| [3] | http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango |
| [4] | http://www.slideshare.net/hdiogenes/wsgi-a-resposta-para-a-questo-definitiva-sobre-python-a-web-e-tudo-mais-368429?src=embed |
| [5] | http://en.wikipedia.org/wiki/Wsgi |
| [6] | http://www.wsgi.org/wsgi/ |
| [7] | http://del.icio.us/marinho/wsgi |