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.
Uma variedade de coisas que eu tentaria:
loc
não é var'd - é possível que este global possa ser acessado por executores paralelos?Date
a comparação deve usar===
- e eles definitivamente não são atualizados automaticamente nem nada - poderia mudar em uma sessão mais longa de redução de mapa?Desde a minha última edição onde identifiquei a função reduce sendo chamada duas vezes, encontrei o erro no mapReduce , que é o seguinte.
Como a função de redução é chamada duas vezes, a saída da primeira chamada resulta em uma saída do seguinte objeto reduzido:
Este objeto não contém o campo " properties.displace ". Portanto, quando a função de redução for chamada novamente, ela está passando neste objeto acima, portanto, no ponto na função de redução onde o campo " properties.displace " é adicionado ao valor " properties.sum ", o código apresentará um erro.
Para resolver esse problema, atualizei o mapReduce para que a função map também gere um campo " properties.sum " que é igual em valor ao campo " properties.displace ", então a fase de redução soma todas as " properties.sum " valores, de modo que, mesmo que execute a função de redução várias vezes, todos os campos existam e contenham os valores acumulados apropriados. O mapReduce completo agora é o seguinte:
Mapa
Reduzir