Por favor, você pode ajudar a identificar por que este mapReduce está falhando (algumas vezes) e propor como garantir que funcione o tempo todo?
Estou usando o mapReduce do MongoDB para realizar o agrupamento de valores de matriz por datas e, em seguida, determinar o valor médio em cada data. Isso geralmente está funcionando bem, no entanto, estou descobrindo que alguns pontos estão falhando, com o mapa reduzido retornando valores "nan" e não consigo descobrir o porquê. O que é mais estranho é que, embora os pontos que falham sejam consistentes se eu executar novamente a função, eles nem sempre falham quando eu executo o map-reduce em menos números de documentos, mesmo que os documentos que vão para o grupo específico que falhar não mudará. Espero que as figuras abaixo esclareçam o que quero dizer.
Dada uma carga de pontos em um mapa (pontos), a função de mapa os agrupa em caixas com base em sua localização. Cada ponto tem uma matriz de datas e valores. A função de redução soma esses valores em cada data e a função de finalização calcula o valor médio de cada data, fornecendo um valor médio para cada quadrado no mapa.
Figura 1. Pontos agrupados em quadrados, onde o ponto vermelho (e possivelmente rosa, embora esteja no limite) não foi adicionado.
O exemplo da Figura 1. (não é o mesmo quadrado da Figura 2. e 3.) mostra o que quero dizer com relação ao erro e, embora o ponto no círculo vermelho certamente deva estar dentro desse quadrado (o quadrado não verde), a função mapReduce errou aqui e falhou ao adicionar o ponto.
Para demonstrar o comportamento inconsistente, abaixo mostro a função de redução de mapa executada em uma pequena área de uma cidade.
Figura 2. Função mapReduce funcionando bem em uma pequena área, caixa vermelha desenhada para indicar o quadrado do problema.
Aqui está a função mapReduce idêntica executada em um subconjunto menor de my, que foi obtido modificando a função query: {$geoWithin...} para selecionar apenas documentos dentro de um pequeno polígono que caiba na figura. A próxima figura é a mesma função mapReduce , mas com a seleção de consulta :{$geoWithin} 1/8 do Reino Unido, mostrada como no quadrado vermelho da Figura 4.
Figura 3. Mesma função mapReduce usada na Figura 2, mas agora com um erro no somatório.
Como pode ser visto na Figura 3, a maioria dos quadrados foi bem processada e produziu o mesmo resultado. No entanto, há um quadrado mostrado aqui (e vários outros em outros lugares), que falhou e, ao consultar a saída do mapReduce , eles resultam em valores "nan".
Com a versão de trabalho, o documento na caixa vermelha se parece com:
{
"_id" : "18_129961_84424",
"geometry" : {
"type" : "Polygon",
"coordinates" : [[
[-1.525726318359375, 53.79335064315454],
[-1.525726318359375, 53.794161837371036],
[-1.52435302734375, 53.794161837371036],
[-1.52435302734375, 53.79335064315454],
[-1.525726318359375, 53.79335064315454]
]]
},
"properties" : [
{
"date" : ISODate("2015-08-15T00:00:00Z"),
"sum" : -9.486295223236084,
"points" : 4,
"displace" : -2.371573805809021
}
]
}
Considerando que na versão quebrada, o mesmo documento se parece com:
{
"_id" : "18_129961_84424",
"geometry" : {
"type" : "Polygon",
"coordinates" : [[
[-1.525726318359375, 53.79335064315454],
[-1.525726318359375, 53.794161837371036],
[-1.52435302734375, 53.794161837371036],
[-1.52435302734375, 53.79335064315454],
[-1.525726318359375, 53.79335064315454]
]]
},
"properties" : [
{
"date" : ISODate("2015-08-15T00:00:00Z"),
"sum" : NaN,
"points" : 3,
"displace" : NaN
}
]
}
O fato de que esse quadrado pode processar às vezes significa que não duvido dos dados, mas algo está acontecendo na função mapReduce . Os quatro pontos que devem ter sido somados corretamente são:
{ "_id" : ObjectId("57a888d4c7afa6e97e7fe00c"), "geometry" : { "type" : "Point", "coordinates" : [ -1.5254854131489992, 53.79415290802717 ] }, "properties" : [ { "date" : ISODate("2015-08-15T00:00:00Z"), "displace" : -2.3721842765808105 } ] }
{ "_id" : ObjectId("57a888d4c7afa6e97e7fe37a"), "geometry" : { "type" : "Point", "coordinates" : [ -1.5254854131489992, 53.79335290752351 ] }, "properties" : [ { "date" : ISODate("2015-08-15T00:00:00Z"), "displace" : -2.382347822189331 } ] }
{ "_id" : ObjectId("57a888d4c7afa6e97e7fe37b"), "geometry" : { "type" : "Point", "coordinates" : [ -1.52468541264534, 53.79335290752351 ] }, "properties" : [ { "date" : ISODate("2015-08-15T00:00:00Z"), "displace" : -2.372774124145508 } ] }
{ "_id" : ObjectId("57a888d4c7afa6e97e7fe00d"), "geometry" : { "type" : "Point", "coordinates" : [ -1.52468541264534, 53.79415290802717 ] }, "properties" : [ { "date" : ISODate("2015-08-15T00:00:00Z"), "displace" : -2.3589890003204346 } ] }
Suspeito que possa ser um problema na minha função Reduce não ser idempotente, conforme proposto na resposta ao MongoDB MapReduce retornando resultados inesperados e agrupando duas vezes , mas não tenho certeza se isso é verdade e, em caso afirmativo, não tenho certeza de como para garantir que é idempotente neste caso. Para completar, incluo minha função mapReduce real abaixo.
var map = function(){
function lon2tile (lon, zoom){ return Math.floor((lon+180)/360*Math.pow(2,zoom)); }
function lat2tile (lat, zoom){ return Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom)); }
function tile2long(x,z) { return (x/Math.pow(2,z)*360-180); }
function tile2lat(y,z) {
var n=Math.PI-2*Math.PI*y/Math.pow(2,z);
return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));
}
function tile2poly(x, y, z){
xl = tile2long(x, z);
yt = tile2lat(y,z);
xr = tile2long(x+1,z);
yb = tile2lat(y+1,z);
poly = [[
[xl, yb],
[xl, yt],
[xr, yt],
[xr, yb],
[xl, yb]
]];
return poly
}
var zoom = 18;
var lon = this.geometry.coordinates[0];
var lat = this.geometry.coordinates[1];
var xtile = lon2tile(lon, zoom);
var ytile = lat2tile(lat, zoom);
var key = zoom+'_'+xtile+'_'+ytile;
var poly = tile2poly(xtile, ytile, zoom);
var value = {
geometry: {type: 'Polygon', coordinates: poly},
properties: this.properties
};
for(var idx=0; idx< value.properties.length; idx++){
value.properties[idx].points = 1;
};
emit (key, value);
}
var reduce = function(mapKey, mapVal){
redVal = {
"geometry" : mapVal[0].geometry,
"properties": []
};
for(var idx=0; idx< mapVal.length; idx++){
for(var pidx=0; pidx< mapVal[idx].properties.length; pidx++){
loc = -1;
for (var el=0; el<redVal.properties.length; el++){
if(redVal.properties[el].date.toISOString() == mapVal[idx].properties[pidx].date.toISOString()){
loc = el;
break;
}
}
if (loc == -1){
redVal.properties.push({'date': mapVal[idx].properties[pidx].date,
'sum': mapVal[idx].properties[pidx].displace,
'points': 1});
}
else{
redVal.properties[loc].sum += mapVal[idx].properties[pidx].displace;
redVal.properties[loc].points += mapVal[idx].properties[pidx].points;
}
}
};
return redVal;
}
var final = function(redKey, redVal){
for (var el=0; el<redVal.properties.length; el++){
if (!("sum" in redVal.properties[el])){
redVal.properties[el].sum = redVal.properties[el].displace;
}
redVal.properties[el].displace = redVal.properties[el].sum / redVal.properties[el].points;
}
return redVal;
}
var query_in = {
'geometry': {
'$geoIntersects': {
'$geometry': {
'type': 'Polygon',
'coordinates': [[
[-2.8125, 53.33087298301705],
[-2.8125, 54.1624339680678],
[-1.40625, 54.1624339680678],
[-1.40625, 53.33087298301705],
[-2.8125, 53.33087298301705]
]]
}
}
}
}
db.c0.mapReduce(map, reduce, {out: "mrTest", query:query_in, finalize:final})
Após uma investigação mais aprofundada, vejo que esses erros estão aparecendo apenas perto do final do processo de redução do mapa (veja a imagem abaixo). Os dados que serão selecionados na etapa de consulta mapReduce são todos os pontos dentro da caixa vermelha. O espaço para a borda direita é excluído, pois não há dados lá.
Figura 4. Cobertura do Reino Unido, mostrando o ponto perdido mais ao sul.
Depois de fazer uma investigação mais aprofundada do único quadrado do problema na Figura 3, posso ver que a parte do mapa está agrupando corretamente 4 pontos. Isso foi obtido construindo um array durante a fase de redução, onde cada valor mapeado é inserido em um array toda vez que é chamado. Isso mostra pontos nas fases de redução que falham, embora eu não consiga entender o porquê.
Modificando a função de redução para: function(mapKey, mapVal){ redVal = { "all_mapped": [], "geometry" : mapVal[0].geometry, "properties": [] };
for(var idx=0; idx< mapVal.length; idx++){
redVal.all_mapped.push({'iter':idx, 'map': mapVal[idx]});
for(var pidx=0; pidx< mapVal[idx].properties.length; pidx++){
var loc = -1;
for (var el=0; el<redVal.properties.length; el++){
if(redVal.properties[el].date.toISOString() === mapVal[idx].properties[pidx].date.toISOString()){
loc = el;
break;
}
}
if (loc === -1){
redVal.properties.push({'date': mapVal[idx].properties[pidx].date,
'sum': mapVal[idx].properties[pidx].displace,
'points': mapVal[idx].properties[pidx].points});
}
else{
redVal.properties[loc].sum += mapVal[idx].properties[pidx].displace;
redVal.properties[loc].points += mapVal[idx].properties[pidx].points;
}
}
};
return redVal;
};
é possível ver na saída que a função de redução é chamada duas vezes. Na primeira vez, ele possui 3 valores mapeados que são somados corretamente. Em seguida, a função de redução é chamada uma segunda vez para combinar a saída reduzida anteriormente com o 1 ponto adicional. Eu acredito que é aqui que a soma falha.