Monday, June 20, 2011

Configurando mysql no home

Utilizando um cluster comunitário, muitas vezes, não queremos configurar um serviço centralizado em que todos os usuários da máquina possam ter acesso. Na maioria das vezes, é possível personalizar configurações para cada usuário, mas, no meu caso, prefiro instalar meus programas localmente no meu home. Só assim fico sossegada de que tudo estará nos "meus conformes".

Como instalar o mysql é uma tarefa um pouco recorrente no meu trabalho, vou postar aqui um how-to express para que eu nunca mais me esqueça de como fazê-lo. Nada que não esteja no arquivo INSTALL do mysql. Na minha instalação eu utilizei o mysql 5.5 em uma máquina com SO Ubuntu 2.6.32.27, 64 bits. Vamos aos passos.

Primeiro, copie o binário para o seu computador. No meu caso:

wget http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-5.5.12-linux2.6-x86_64.tar.gz/from/http://mirrors.ircam.fr/pub/mysql/

Descompacte:

tar -zxvf mysql-5.5.12-linux2.6-x86_64.tar.gz

Faça um link simbólico para o diretório criado:

ln -s mysql-5.5.12-linux2.6-x86_64 mysql

Entre no diretório mysql:

cd mysql

Altere as variáveis MYSQL e MYSQLDUMP do arquivo bin/mysqlaccess fazendo-as apontar para o seu diretório local.

Crie as tabelas de permissão do mysql:

scripts/mysql_install_db

Inicie o servidor de banco de dados:

bin/mysqld_safe &

Mysqld_safe é a maneira mais segura de se iniciar um servidor mysql. Ele faz, por exemplo, o servidor reiniciar quando um erro acontece e automaticamente registra a informação em tempo de execução a um arquivo de log.

Em algumas máquinas precisei instalar o pacote libaio1. O fiz de duas maneiras. Primeiro utilizando o apt-get, quando as máquinas não apresentavam conflitos. E depois utilizando o dpkg do debian em máquinas com conflito de pacotes (não quis forçar a instalação, pois isso poderia quebrar o ambiente de algum usuário do cluster).

Friday, June 3, 2011

Como ativar o log do HAProxy

O HAProxy é uma aplicação que oferece funcionalidades para se atingir alta disponibilidade e balanceamento de carga em sistemas baseados em TCP e HTTP. O HAProxy funciona como um proxy interceptando requisições entre a aplicação cliente e a aplicação servidora. No meu caso, estou utilizando o HAProxy para distribuir as requisições feitas a um cluster Cassandra.

Por questões de desempenho, o HAProxy loga utilizando um servidor syslog e não um arquivo comum. O Syslog consiste em um daemon presente em muitos servidores Linux. No entanto, o HAProxy exige que esse daemon escute na porta 514 a qual, frequentemente, não está habilitada por padrão.

Um servidor syslog:
- recebe entradas a serem logadas
- decide o que é importante de ser registrado
- escreve em disco de uma maneira otimizada

Para ativar o log do HAProxy, primeiro é preciso especificar o uso do log no arquivo haproxy.cfg. No meu caso, eu tenho algo do tipo:

global
        maxconn           5000
        log                     localhost               local0              notice

Nas linhas anteriores, instruo ao HAProxy que ele registre eventos na máquina local (localhost), utilizando o rsyslog através de local0. O parâmetro notice informa que somente eventos considerados importantes serão logados. Para mais informações sobre esses parâmetros, consulte a documentação anexa ao código fonte do HAProxy (arquivo doc/configuration.txt).

Em seguida, crie um arquivo haproxy.cnf em /etc/rsyslog.d/. Ponha o seguinte conteúdo nesse arquivo:

$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
local0.* -/var/log/haproxy.log
Agora reinicie o servidor rsyslog.

sudo /etc/init.d/rsyslog restart

Para ver o que está sendo logado sobre o HAProxy, acesse:

tail -f /var/log/haproxy*.log
 
Um ponto importante, como destaca esse post, é configurar a rotação do log. Para isso, crie um arquivo haproxy dentro de /etc/logrotate.d/ com o seguinte conteúdo:

/var/log/haproxy*.log
{
    rotate 4
    weekly
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        reload rsyslog >/dev/null 2>&1 || true
    endscript
}

Simples, não?

Índices Secundários no Cassandra

O Cassandra provê índice secundário baseado em hashing. Assim, só é possível fazer consultas da forma:

SELECT user_id FROM users WHERE last_name='silva'

Não se pode, por exemplo fazer buscas do tipo

SELECT user_id FROM users WHERE last_name='s*'

A indexação secundária provida pelo Cassandra apresenta uma outra limitação -- ela não deve ser utilizada com chaves de alta cardinalidade, ou seja, chaves que podem assumir muitos valores. Nesse caso, de acordo com os desenvolvedores de Cassandra, Column Families são mais indicadas para a indexação de valores.

Pelo que entendi, a partir de discussões na lista de desenvolvedores (Cassandra-749), parte dessa limitação vem do fato de que os índices secundários são armazenados localmente em cada nó, e não de maneira distribuída como uma Column Family estaria. Cada nó do cluster armazena na própria MemTable/SSTable ponteiros para os dados indexados. Ao fazer uma busca por um índice secundário, todos os nós respondem ao nó cliente apresentando todos os registros que cada um contém. Essa implementação evita que uma rodada de comunicação seja necessária para saber onde cada chave a ser procurada se encontra no cluster. Além disso, atualizações em uma CF e em seus índice secundários são atômicas, ao contrário de atualizações feitas em diversas CFs. Essa característica deve ser considerada ao analisar que tipo de índice (secundário nativo ou CF) utilizar. Outros benefícios do uso do índice secundário do Cassandra incluem: i) o uso do commitlog local para a sincronização do índice e dos dados; ii) sharding automático do índice, pois cada nó armazena parte dos dados indexados.
Por fim, Cassandra apresenta uma outra característica que aumenta o desempenho dos seus índices. Trata-se dos row bloom filters os quais informam sobre a *ausência* de uma determinada chave muito rapidamente e de forma precisa -- falsos negativos nunca ocorrem, porém falsos negativos são possíveis. 

Quando não se deve utilizar os índices secundários providos pelo Cassandra, porque o campo a ser indexado apresenta alta cardinalidade, por exemplo, há a opção de construir o seu próprio índice através de novas Column Families. A página http://www.datastax.com/docs/0.7/data_model/cfs_as_indexes introduz o assunto e o post de Pranab Ghosh na página http://pkghosh.wordpress.com/2011/03/02/cassandra-secondary-index-patterns/ o explica um pouco mais. Aqui, vou reproduzir parte desses posts.

Column Families como Índices Secundários

Dependendo do tipo de dados que se quer indexar com uma Column Family, deve-se utilizar diferentes estratégias. De qualquer maneira, é preciso considerar que cada entrada (row key) de uma CF é armazenada em uma única máquina e, por isso, manter uma entrada no índice a qual é frequentemente acessada pode gerar um desbalanço de carga na máquina que armazena aquela entrada. 

Para evitar situações assim, existem as seguintes estratégias.

Um para Um

Essa estratégia é indicada para os casos onde para cada valor indexado existe somente um dado correspondente. Nesse caso, o índice inteiro é armazenado como uma row em uma CF. Um exemplo seria um índice que mapeie os nomes dos estados do Brasil para as siglas desses estados.


Cada valor de coluna a ser indexada consiste em uma row com uma coluna por row indexada. O nome da row key deve ser o nome do índice (único no keyspace), os nomes das colunas devem ser os valores das colunas sendo indexados e os valores das colunas devem ser as row keys sendo indexadas.

Um para Alguns

Essa estratégia é indicada para os casos onde para cada valor de coluna indexado existem alguns dados correspondentes. Um exemplo seria uma CF que, dado uma lista de livros a serem vendidos e seus respectivos vendedores, mapeie todos os livros que um dado usuário está vendendo.



Nesse caso, cada índice consiste em uma row com uma super column por valor de coluna indexado. Row key será o nome do índice (único no keyspace). Os nomes das super colunas serão os valores de coluna sendo indexados. Os nomes das sub-colunas serão as row keys sendo indexadas. As sub-colunas não apresentam valores.

Um para Vários

Essa estratégia é indicada para os casos onde para cada valor indexado podem haver muitos dados correspondentes, tantos que seria impossível armazená-lo todos em um só nó. Assim, nessa estratégia se aconselha ter uma CF para cada coluna a ser indexada. Só para lembrar, nas estratégias anteriores, tínhamos uma row de CF para cada coluna a ser indexada, ou seja, uma row por índice. Um exemplo de aplicação dessa estratégia seria uma CF que mapeie, para cada região do Brasil, todos os livros que estão sendo vendidos por usuários dessa região. 



Nesse caso, um índice estará distribuído entre muitas row keys, sendo que cada valor de coluna indexado recebe a sua própria row. Em um caso mais simples, onde não haja necessidade de ordenação, o nome das colunas pode ser as row keys sendo indexadas. Já em um caso em que a ordenação é importante (por data de anúncio, por exemplo), podem-se utilizar super columas cujos nomes sejam as datas de anúncio e os nomes das sub-colunas sejam as row key indexadas.

Tuesday, February 22, 2011

A consistência dos dados no Cassandra

No post anterior, falei sobre o teorema CAP o qual diz que em um sistema distribuído não se pode ter simultâneamente as propriedades de consistência, particionamento e disponibilidade de dados. Neste post vou me aprofundar um pouco mais nesse teorema utilizando o Cassandra, um dos sistemas de armazenamento distribuído mais populares do momento, como exemplo.

De maneira geral, o Cassandra é classificado como um sistema AP (availability and partition), isto é, que  prioriza a disponibilidade e a tolerância a partições na rede em detrimento à consistência de dados. Isso quer dizer que os dados armazenados no Cassandra estarão inconsistentes? A resposta é um sonoro PROVAVELMENTE NÃO! Na literatura, essa resposta leva um nome mais bonito. Ela pode ser chamada, por exemplo, de eventual consistency ou weak consistency e é aí que se encontra a graça do Cassandra.

O Cassandra foi projetado para ser altamente escalável podendo assim gerenciar uma grande quantidade de dados. Imagine, por exemplo, que esse sistema é utilizado para gerenciar os dados postados em um serviço de microblog. Esse microblog tem milhões de usuários que podem fazer buscas por tópicos de interesse. Toda vez que alguém postar algo sobre um tópico x, as pessoas interessadas nesse tópico poderão ler o que foi postado sobre x. Agora imagine se toda vez que alguém requisitar as últimas notícias de x o sistema bloquear as escritas com esse tópico a fim de evitar conflito de dados. O resultado seria que a latência em uma requisição seria tão grande que inviabilizaria o uso do sistema, tornando, pode-se dizer, os dados de x indisponíveis.

Bancos de dados relacionais prezam pela forte consistência de dados e por isso não escalam bem ao gerenciar grande quantidade de dados. Por isso, sistemas ditos NoSQL como o Cassandra defendem a idéia de que muitas aplicações se beneficiam mais de consultas rápidas e com uma eventual inconsistência do que de consultas consistentes mas que não podem envolver um grande número de clientes acessando os dados simultâneamente. O Cassandra, por exemplo, apresenta um trade-off o qual permite ao gerenciador do sistema configurar o nível de consistência desejado. Quanto maior o nível de consistência, maior a latência para operações de leitura/escrita. Ou seja, no fundo, no fundo, é uma questão de escolher entre a consistência das consultas ou o desempenho das mesmas. Desempenho... Estava demorando para essa palavra aparecer, não é?


Configurando a consistência de dados no Cassandra

Para garantir tolerância a falhas, os dados no Cassandra precisam ser replicados. Considerando que um keyspace do Cassandra esteja configurado para conter N réplicas de cada valor associado a uma chave e R seja o número de réplicas lidas e W o número de réplicas escritas, então a consistência é garantida se:

R + W > N

Onde R e W podem assumir, entre outras configurações mais específicas, os valores:

ONE (1) - Na escrita, garante que o dado foi escrito em um commit log e uma tabela de memória de ao menos uma réplica antes de responder ao cliente. Na leitura, o dado será retornado a partir do primeiro nó onde a chave buscada foi encontrada. Essa prática pode resultar em dados antigos sendo retornados, porém, como cada leitura gera uma verificação de consistência em background, consultas subsequentes retornarão o valor correto do dado;

QUORUM (Q) - Na escrita garante que o dado foi escrito em N/2+1 réplicas. Na escrita retorna o valor mais recente lido de N/2+1 réplicas. As réplicas restantes são sincronizadas em background;

ALL (N) - Garante que operações de leitura e escrita envolverão todas as réplicas. Assim, qualquer nó que não responda às consultas fará as operações falharem. 

Possíveis configurações para obter dados consistentes são:
W=1, R=N
W=N, R=1
W=R=Q



Nível de Consistência Padrão

De acordo com o cliente que se utiliza para acessar os dados do Cassandra, o nível de consistência adotado pode mudar. Assim, a fim de evitar surpresas, é sempre aconselhável configurar qual nível de consistência utilizar. No cliente que uso, o Hector, essa configuração é feita no método createKeyspace da Classe HFactory, informando-se a classe que implementa a política de consistência. Tal classe deve implementar a interface ConsistencyLevelPolicy.

A seguir, apresento um exemplo no qual operações de leitura são feitas acessando apenas uma réplica, enquanto operações de escrita (tipicamente mais rápidas no Cassandra) são feitas em N réplicas.

public final class MyWebConsistencyLevel implements ConsistencyLevelPolicy {

    @Override
    public HConsistencyLevel get(OperationType op) {
        switch (op){
          case READ:return HConsistencyLevel.ONE;
          case WRITE: return HConsistencyLevel.ALL;
          default: return HConsistencyLevel.QUORUM; //Just in Case
       }
    }

    @Override
    public HConsistencyLevel get(OperationType op, String cfName) {
        switch (op){
          case READ:return HConsistencyLevel.ONE;
          case WRITE: return HConsistencyLevel.ALL;
          default: return HConsistencyLevel.QUORUM; //Just in Case
       }
    }
}

Esse exemplo funciona com a versão 0.7 do Cassandra e do Hector. 

Monday, February 7, 2011

CAP Teorema: Capture essa idéia!


Nunca conhecer os princípios dos algoritmos distribuídos foi tão importante para um desenvolvedor de software. Hoje os sistemas apresentam escalas tão largas que só são possíveis de se tornarem reais com o uso de plataformas altamente distribuídas. Redes sociais tais como o Facebook e Twitter são exemplos desses sistemas. A consequência é que problemas há muito tempo conhecidos pelos estudiosos de Sistemas Distribuídos (SD), surgem repaginados e potencializados.

Considere o caso do Facebook como mero exemplo. De modo geral, queremos que essa ferramenta esteja online 100% do tempo, funcionando corretamente e com baixos tempos de resposta mesmo se, por algum motivo qualquer, algumas máquinas da infraestrutura estejam offline. Ou seja, o que queremos pode ser definido como:

Consistência: garante que qualquer operação de leitura que comece após uma operação de escrita, se atendida, deve retornar o valor escrito ou o resultado de uma escrita mais recente. De maneira mais simples isso pode significar que, ao fazer upload da minha foto no perfil do FB, quero que a mesma apareça para todos os meus amigos e não mais uma foto antiga qualquer.


Disponibilidade: garante que os dados do sistema estarão sempre à disposição, ou seja, todas as requisições ao sistema receberão uma resposta.


Tolerância a Partições: garante que mesmo quando falhas na conexão entre os nós ocorram, sendo potencialmente causadas pela falha de um ou mais nós, as requisições continuarão sendo atendidas.
Ocorre que recentemente, Eric Brewer, formulou uma conjectura que dizia que em qualquer sistema distribuído, em um modelo de rede assíncrono, apenas duas das três propriedades de disponibilidade, particionamento de dados e consistência podem ser simultâneamente garantidas [1]. Mais tarde, Gilbert e Lynch estabeleceram uma prova formal da conjectura transformando-o em um teorema[2]. Tal teorema é conhecido também pelo nome CAP o qual consiste em um acrônimo para Consistency, Availability e Partition Tolerance.

A Prova

Por contradição, vamos provar que não se pode ter as propriedades de consistência e disponibilidade de dados simultâneamente, mesmo em casos onde haja perda de mensagens de rede. Assim, assuma que existe um sistema onde os dados estão sempre disponíveis e consistentes, mesmo quando há partição na rede e algumas mensagens são perdidas. Suponha que a rede seja formada por dois nós {N1, N2} distintos e incomunicáveis. Segue que se uma operação de escrita acontece em N1, pela propriedade da disponibilidade, o valor inicial v0 será transformando em v1. Como N1 e N2 não se comunicam, N1 continuará com o valor inicial de v0. Supondo que nenhuma outra operação de escrita ocorra na rede, imagine que uma operação de leitura ocorre no valor v0 de N2. Pela propriedade da disponibilidade, essa operação gerará uma resposta que deve ser v0. Segue que os resultados das duas operações são distintos, contradizendo a hipótese de que havia consistência mesmo quando mensagens entre os nós eram perdidas.

Também podemos provar que o mesmo ocorre se considerarmos uma rede onde todas as mensagens são entregues. Nesse caso, a idéia principal é baseada no fato de que em um modelo assíncrono, um algoritmo não tem como determinar se uma mensagem foi perdida ou está atrasada na transmissão. Segue que se existisse um algoritmo capaz de garantir consistência em execuções onde não há perda de mensagens, então existiria um algoritmo que garantiria consistência em todos os tipos de execução, o que consiste em um absurdo de acordo com o provado no parágrafo anterior.

Intuitivo, não? O resultado prático do teorema CAP pode ser observado em muitos sistemas de armazenamento distribuídos. Esses sistemas apresentam combinações das três propriedades. BigTable da Google e o HBase da Yahoo! prezam pela consistência e disponibilidade. Por esse motivo são ditos sistemas CA (consistency and availability). Por outro lado, sistemas como o Dynamo da Amazon e o Cassandra, originalmente desenvolvido pelo Facebook, sacrificam a consistência a favor das outras duas propriedades, sendo chamados sistemas AP (availability and partitioning).

É importante notar que o teorema CAP assegura que as três propriedades não podem ser atendidas ao mesmo tempo, mas podem-se mesclar propriedades de acordo com o cenário em que se encontra o ambiente. Assim, algumas iniciativas apresentam um sistema híbrido como é o caso do PNUTS, desenvolvido pela Yahoo!.

Muitas pessoas argumentam que o Teorema CAP é incompleto por não classificar sistemas como esses ou por sugerir que o problema de gerenciamento de dados distribuído é apenas um trade-off entre disponibilidade e consistência. Daniel Abadi, professor da Universidade de Yale é uma dessas pessoas. Um artigo seu sobre o assunto pode ser encontrado aqui (http://dbmsmusings.blogspot.com/2010/04/problems-with-cap-and-yahoos-little.html).

Posições como a do Prof. Abadi têm se multiplicado na internet. Tanto que o próprio Eric Brewer já anunciou em seu twitter que precisa escrever outro artigo sobre o Teorema. Isso quer dizer que ainda há muita coisa a ser descoberta por trás dessas três letras. Quem se atreve?

Referências:
1- Brewer, E., Principles of Distributed Computing (PODC 2000): "Towards robust distributed systems." Portland, OR. July 2000.
2 - Nancy Lynch and Seth Gilbert, "Brewer's conjectures and the feasibility of consistent, available, partition-tolerant web services", ACM SIGACT News, Volume 33 Issue 2 (2002), pg. 51-59.

Thursday, January 27, 2011

Do que se trata?!?

Este site é uma expressão do meu lado nerd. Por trabalhar com pesquisa em Computação, muitas vezes leio artigos sobre tecnologias, teorias e experimentos que acho muito interessantes, mas que, ao longo de alguns dias, me esqueço do que se tratam. É estranho, mas a minha memória científica é curta. Por essa razão, decidi exercitá-la formalizando conceitos e idéias do que leio em conteúdo digital. Primeiramente pensei em um wiki privado, mas, se assim o fizesse, perderia a oportunidade de interagir com outras pessoas. Um blog sim, seria mais apropriado. Surgiu então o "Horas de Ócio Nerd", um blog onde abordarei tópicos de Computação do meu interesse. Trata-se de um espaço despretencioso em que utilizarei uma linguagem mais simples do que as encontradas em artigos científicos, afinal, não estou no trabalho ;)

Desfrutem!