O documento descreve um projeto para analisar e modelar os dados da Open Library usando MongoDB. Ele discute a conversão e importação dos dados, análise com o framework de agregação e Map/Reduce, e refatoração do modelo de dados para MongoDB.
1. Luciano Ramalho
luciano@ramalho.org
@ramalhoorg
Open Library no MongoDB
Usando Map/Reduce e o Aggregation Framework
para análise e modelagem de dados
Tuesday, July 17, 12
2. Temas
• Sobre o projeto Open Library
• Conversão e importação da massa de dados
• Análise dos dados com o framework de agregação
• Análise dos dados com Map
/Reduce
• Refatoração do modelo de dados para o
MongoDB
• Encerramento
@ramalhoorg
Tuesday, July 17, 12
3. Sobre o projeto
Open Library
@ramalhoorg
Tuesday, July 17, 12
4. Sobre a Open Library
• Missão:
“One web page for every book”
• Um projeto do Internet Archive
• 117.439.126 registros bibliográficos em jun/2012
• Mais de 1.000.000 de e-books gratuitos para
baixar (livres, CC, domínio público etc.)
@ramalhoorg
Tuesday, July 17, 12
5. A Tecnologia da
Open Library
• Infobase: uma API Python para bases de dados
semi-estruturadas sobre tabelas normalizadas
• também conhecida como ThingDB
• Inclui versionamento de registros
• Muitos join para recuperar uma entidade conceitual
• Fortemente depenente do SOLR/Lucene para
exibir suas páginas
@ramalhoorg
Tuesday, July 17, 12
6. Modelo de dados
semi-estruturado
• Base teórica existe!
• Palavras-chave para pesquisa:
semistructured ou semi-structured database
“The semistructured data model is designed as an
evolution of the relational data model that allows the
representation of data with a flexible structure. ”
SUCIU, Dan. SemiStructured Data Model.
In: LIU, L. Encyclopedia of Database Systems
@ramalhoorg
Tuesday, July 17, 12
7. Data on the
Web (1999)
• From Relations to
Semistructured Data
and XML
• Autores: Abiteboul,
Buneman & Suciu
• Notação apresentada:
semelhante a JSON
@ramalhoorg
Tuesday, July 17, 12
8. Semistructured
Database Design
(2004)
• Autores: Ling, Lee & Dobbie
• Algoritmos de normalização
sem a 1ª Forma Normal
(N1NF = Non First Normal
Form)
@ramalhoorg
Tuesday, July 17, 12
9. Conversão e
importação da
massa de dados
@ramalhoorg
Tuesday, July 17, 12
10. Massa de dados
• OL Complete Dump: ol_cdump_latest.txt.gz*
• 118.598.056 linhas em 1/jun/2012
• 16 GB comprimidos (.gz), 91 GB sem compressão
• 32 tipos diferentes de registros
• 1.158.930 (~1%) não são registros bibliográficos
• Inclui todas as revisões de todos os registros
* http://openlibrary.org/developers/dumps
@ramalhoorg
Tuesday, July 17, 12
11. Converter para carregar
• Escolha de uma chave primária (campo _id)
• Chave composta: key+"-"+revision
• /books/OL1656964M-1
• Opção adotada: a conversão mais simples possível
• Usar JSON do dump, acrescido de campo _id
@ramalhoorg
Tuesday, July 17, 12
12. Carga: conversor_ol.py
import sys
import json
import io
def conv_linha(lin, indent=None):
rec_type, rec_key, rec_revision, rec_modified, rec_json = lin.split(u't')
rec = json.loads(rec_json)
rec[u'_id'] = rec_key + u'-' + rec_revision
return json.dumps(rec, indent=indent)
def conv_arquivo(nome_arq, max_lin=sys.maxsize, indent=None):
with io.open(nome_arq, encoding='utf-8') as arq:
for num_lin, lin in enumerate(arq, 1):
if not lin.strip():
continue
saida = conv_linha(lin, indent)
print saida.encode('utf-8')
if num_lin >= max_lin:
break
if __name__=='__main__':
if len(sys.argv) == 2:
converte_arquivo(sys.argv[1])
else:
print 'Modo de usar: %s <ol_dump_file>' % __name__
* https://github.com/ramalho/mongosp
@ramalhoorg
Tuesday, July 17, 12
13. Usando mongoimport
python conversor_ol.py $1 |
mongoimport -d openlibrary -c complete --stopOnError
• -d: database
• -c: collection
• --stopOnError: interromper se houver erro
• --upsert: sobrescrever ao importar _id duplicado
(default: ignorar o novo registro)
• --file: arquivo a inserir (default: stdin)
@ramalhoorg
Tuesday, July 17, 12
14. Análise dos dados usando
o framework de agregação
@ramalhoorg
Tuesday, July 17, 12
15. Indexação para análise
• Criar índices esparsos para:
• key
• revision
• type
• outros...
@ramalhoorg
Tuesday, July 17, 12
16. Aggregation Framework:
o básico
• Novidade no MongoDB 2.1/2.2
• Alternativa ao Map/Reduce
• Mais fácil de usar
• Melhor desempenho
• implementado em C++, usa threads
(Map/Reduce depende do interpretador
JavaScript Spidermonkey, mono-thread)
@ramalhoorg
Tuesday, July 17, 12
18. Exemplo: group_types.js
$ time mongo2.1
• O primeiro lote
group_types.js
MongoDB shell version: 2.1.2
de 1.000.000 de connecting to: test
605781! /type/edition
registros tem 9 382428! /type/author
tipos diferentes 9211!/type/work
1935!/type/redirect
• Os três primeiros 623! /type/delete
7! /type/template
são os mais 7! /type/page
importantes: 5! /type/doc
edition, author, 3! /type/macro
work real!0m23.658s
user!0m0.030s
sys! 0m0.004s @ramalhoorg
Tuesday, July 17, 12
19. Agregação em estágios
• Estágios: etapas em um fluxo (steps in a pipeline)
• Estágios são executados em ordem, na ordem dos
parâmetros da invocação de mapReduce
• Cada estágio aplica um operador especial
• O mesmo operador pode ser usado várias vezes
em estágios diferentes
@ramalhoorg
Tuesday, July 17, 12
23. O que não dá para fazer
(atualmente)
• Conjunto limitado de operadores
• Para lidar com strings, por exemplo:
• $substr, $toLower, $toUpper, $strcasecmp
• não tem length, regex, startswith, etc.
• O framework foi feito para ser extensível
• Mas não tem uma arquitetura de plug-ins
@ramalhoorg
Tuesday, July 17, 12
25. O problema do
“schema after”
• Conceito: “schema before” x “schema after”
• Michael Stonebraker (criou Ingres,VoltDB etc):
• MongoDB é “schema after”
• Em uma base “schema after” em produção, o
esquema real quase nunca é exatamente o
planejado
@ramalhoorg
Tuesday, July 17, 12
26. Análise profunda
dos dados
• Estatísticas sobre a estrutura dos registros
• para cada tipo de registro, quais campos
ocorrem, e em qual frequência
• Estatísticas sobre estrutura dos campos
• valores simples, arrays e documentos aninhados
(objetos)
@ramalhoorg
Tuesday, July 17, 12
27. Map/Reduce: o básico
• Executado através do método mapReduce:
db.complete.mapReduce(map, reduce,
{out: { inline : 1}, jsMode: true})
• Função map deve processar cada item (this) e
emitir um par de chave: valor
• Função reduce deve aceitar chave e um array de
valores, e devolver apenas um valor agregado
@ramalhoorg
Tuesday, July 17, 12
29. Map/Reduce
me lembra
Pacman
• Jogador faz reduce
dos pontinhos
• Resultado do reduce
é o score
@ramalhoorg
Tuesday, July 17, 12
30. Exemplo 284396!
251678!
subtitle
subject_place
592707! lc_classifications
264695! contributions
605777! title
604455! languages
• Obter lista de todos 475865!
598671!
subjects
publish_country
os campos e quantas 193955! series
vezes cada um 113818! title_prefix
605781! type
ocorre nos registros 538357! by_statement
de edition 605781! revision
600934! publishers
605781! last_modified
605781! key
@ramalhoorg
Tuesday, July 17, 12
31. Map
• Se o registro é do tipo edition, emitir um par de
(«nome_do_campo», 1) para cada campo
var map = function () {
if (this.type.key === "/type/edition") {
for (field_name in this) {
emit(field_name, 1);
}
}
}
@ramalhoorg
Tuesday, July 17, 12
32. Reduce
• Todos os pares de («chave», «valor»)
são agrupados em pares pela «chave»:
(«chave»: [«valor0», «valor1», «valor2»])
• A função reduce deve reduzir cada
«array_de_valores» a um único valor
var reduce = function (key, values) {
var total = 0;
values.forEach(function(n) { total += n; });
return total;
}
@ramalhoorg
Tuesday, July 17, 12
33. Executar mapReduce
var res = db.complete.mapReduce(map, reduce, {
"out": { "inline" : 1},
"jsMode": true
});
//exibir resultado
res.results.forEach(function (r) {
print(r.value+"t"+r._id);
});
print("-----");
for (var chave in res.counts) {
if (chave !== "_id") {
print(chave+"t"+res.counts[chave]);
}
}
print("-----");
print("tempo (s)t"+res.timeMillis/1000);
@ramalhoorg
Tuesday, July 17, 12
40. Refatoração do esquema
• Usar key+revision como chave primária _id
• Manter campos key e revision separados
• Para fazer pseudo-auto join recuperando o
histórico de um registro bibliográfico
• Embutir (embed) campo nome do autor no
documento
"authors": [
{
"key": "/authors/OL45038A",
"name": "W. A. Mozart"
}
], @ramalhoorg
Tuesday, July 17, 12
41. Representação do
histórico de versões
• Embutir pode ser uma boa opção para os tipos de
registros que são raramente atualizados
• Versões antigas embutidas
• Para registros que sofrem muitas atualizações, a
melhor opção é uma sequência de referências
(“chaves estrangeiras”)
• Um “pseudo self-join” pode ser feito pelo
atributo key para recuperar o histórico
@ramalhoorg
Tuesday, July 17, 12
42. Integridade referencial
• Identificação de problemas atuais
• Ferramentas de suporte
• Índices
• Uso de um framework com ODM (object-
document mapper)
• Tarefas de monitoração assíncrona
@ramalhoorg
Tuesday, July 17, 12
43. Algumas dicas
• Todo registro deve ter campos identificando:
• seu tipo
• a versão do esquema usada naquele registro
• Mudanças no esquema podem ser feitas de modo
incremental, quando um documento é alterado
• Use um ODM (Object-Document Mapper) para
aumentar a consistência dos dados armazenados
@ramalhoorg
Tuesday, July 17, 12
45. Excelente opção para
hospedagem de MongoDB.
Pequenas instâncias gratuitas,
instâncias maiores por preços
acessíveis, sem você precisar
gerenciar o servidor, sistema
operacional, storage etc.
Tuesday, July 17, 12