As emocionantes aventuras de um sysadmin linux na procura pelo uptime perfeito!

MongoDB: sharding e mapReduce()

Posted: novembro 14th, 2010 | Author: coredump | Filed under: Linux e Open Source, Programação | Tags: , , ,

Uma das coisas interessantes que me atraiu a testar o MongoDB foram as capacidades de sharding e replicação do mesmo. Sharding é uma técnica muito usada atualmente para lidar com escalabilidade de massas de dados, consiste basicamente em dividir os dados de uma aplicação entre vários bancos: por exemplo, numa aplicação com 1000 usuários o sharding faria que os usuários com nomes de A a J ficassem em um servidor, e de K a Z em outro servidor. Claro que tem mais coisas envolvida, recomendo a leitura do excelente artigo Sharding for startups que discute como particionar os dados e como fazer aplicações que suportam sharding.

O MongoDB é um banco de dados não SQL que tem ganhado espaço. Foursquare por exemplo usa o Mongo com uma massa de dados razoável (3 shards de 60Gb, de acordo com uma informação já um pouco desatualizada). Nas versões mais recentes, o MongoDB também ganhou suporte nativo a sharding e uma replicação mais robusta.

Bancos NoSQL como  Mongo tem grandes diferenças com os RDB existentes. A mais importante, SQL não existe, então algumas coisas como SELECT SUM() ou SELECT … GROUP BY que se está acostumado a usar tem de ser implementados de outra forma. As vezes é melhor implementar todo esse tratamento na aplicação, mas no caso do MongoDB ele possui uma implementação de Map/Reduce nativa, exatamente para se fazer esse tipo de tratamento de dados. Um detalhe interessante na performance deste sistema é que no caso de bancos com shards o trabalho é dividido entre os mesmos.

Para testar o ganho de performance e se a divisão era feita corretamente, fiz o seguinte teste: habilitei sharding em uma collection com dados de logs com 19 milhões de registros, cada registro com tamanho entre 150 e 220 bytes. Usei as seguintes funções para o Map/Reduce:

Função de mapeamento (map):

function m() {
	emit(this.c, {duration: this.d,
			size: this.s,
			conn: 1});
}

Função de redução (reduce):

function r(key, val) {
	var total_duration = 0;
	var total_size = 0;
	var total_conn = 0;
	for (var i = 0; i < val.length; i++) {
		total_duration += val[i].duration;
		total_size += val[i].size;
		total_conn += 1;
	}
	return {duration: total_duration,
			size: total_size,
			total_conn : total_conn };
}

O balanceamento dos dados entre os dois shards que eu criei demorou quase três horas, nesse tempo o banco continua disponível exceto por updates em dados que estão no processo de serem movidos para outro shard. Depois de terminado esse processo eu tinha dez milhões de registros no servidor original e os outros nove milhões e qualquer coisa no segundo shard.

O processo de Map/Reduce com apenas um banco demorou 15 minutos para completar, agindo sobre todos os registros. Depois do sharding, esse tempo caiu pela metade indo para 7 minutos. Se a melhora for sempre nesta proporção a adição de shards implica diretamente na divisão do tempo do processamento pelo número de shards envolvidos. De acordo com a equipe de desenvolvimento do MongoDB eles estão trabalhando agora para melhorar o paralelismo da operação de Map/Reduce mas aparentemente isso depende da engine que eles usam para interpretar o javascript das funções.

O sharding do MongoDB é bem implementado, mas tem de se ter cuidado na hora de escolher a chave pela qual ele vai fazer a divisão dos dados. Uma chave mal escolhida pode fazer com que os shards fiquem mal balanceados e acabe com todo o ganho de performance que teria sido conseguido. Um exemplo seria se no meu teste eu tivesse acabado com um milhão de records em um dos shards, e os outros dezoito no outro: os resultados estariam disponíveis no shard com menos data muito antes do outro terminar.

intel

No Comments »