8
I am finishing the development of an ad system, the system uses the redis server to save data and display campaigns directly from ram memory, since the pathology of these systems require a lot of disk consumption. Every time I need to get the values spread through collections on my server redis and save in two relational tables on my mysql server. However, I am too young for asynchronous programming and do not master the promises, although the code below seems to work well. I would not like to leave you with this essay because it becomes difficult to maintain the code.
This routine inserts the cost of each campaign, including user connections, campaign impressions, and ad clicks on affiliate websites. In other words, I need a loop for each zone created by the editors, adding up the number of connections in those zones, then adding to that collection the impressions and clicks of each campaign and sending over 15-second intervals to the mysql server.
SalvarStatsCustosCampanha = function(){
cliente_redis.smembers("campanhas_disponiveis",function(err,campanhas){
var multi = cliente_redis.multi();
for(var i= 0; i<campanhas.length;i++){
multi.smembers("zona_membros_campanhas:"+campanhas[i]);
}
multi.exec(function(err,zonas){
async.forEachOf(zonas,function(lista,indice,callback){
var desconectados = 0;
var conectados = 0;
async.each(lista,function(zona,fn){
desconectados += (typeof(global.desconectados_zona[zona]) != 'undefined')? global.desconectados_zona[zona] : 0;
conectados += (typeof(global.conectados_zona[zona]) != 'undefined')? global.conectados_zona[zona] : 0;
fn();
},function(err){
cliente_redis.hmget("campanha:"+campanhas[indice],['id_usuario','cliques','impressoes','custo'],function(err,info){
let campanha = {};
campanha.id_anunciante = info[0];
campanha.id_campanha = campanhas[indice];
campanha.conectados = desconectados;
campanha.impressoes = (info[2] == null)? 0 : info[2];
campanha.cliques = (info[1] == null)? 0 : info[1];
campanha.custo = (info[3] == null)? 0 : info[3];
if(campanha.cliques == 0){
campanha.ctr = 0;
campanha.ctr = campanha.ctr.toFixed(3);
}else campanha.ctr = (parseFloat(campanha.cliques) / parseFloat(campanha.conectados)).toFixed(3);
campanha.data = moment().format("YYYY-MM-DD");
campanha.fechamento = moment().format();
var data = {};
data.campanha = campanha;
data.data = moment().format("YYYY-MM-DD");
fnCampanha.GetCustoCampanhaDia(data,function(data,custo){
CampanhaSangria = function(campanha){
cliente_redis.hmset("campanha:"+campanha.id_campanha,{"cliques": 0,"custo" : 0,"impressoes": 0});
}
if(typeof(custo) != 'undefined'){
data.campanha.id_custo = custo.id_custo;
fnCustos.AlterarCustos(data.campanha,CampanhaSangria);
}else{
fnCustos.AdicionarCustos(data.campanha,CampanhaSangria);
}
});
});
});
});
});
});
}
The same logic is employed for the campaign zones responsible for linking the ads on affiliated sites, only the number of impressions is not computed because the zone receives per socket in real time dozens of campaigns so I only effective the accounting of impressions for campaigns.
SalvarStatsRendimentosZona = function(){
cliente_redis.smembers('zonas_disponiveis',function(err,zonas){
if(zonas.length > 0){
Falha = function(){
console.log("O processo de update de rendimentos falhou");
}
Sucesso = function(data,zona){
cliente_redis.hmget("zona:"+data.zona.id_zona,['cliques','rendimentos'],function(err,cliques){
if(cliques != null){
let rendimento = {};
rendimento.conectados = (!isNaN(global.desconectados_zona[zona.id_zona]))? global.desconectados_zona[zona.id_zona]: 0;
/* Se não houver conexões sobre a zona no dia corrente não há necessidade de alterar ou inserir apenas ignoramos
esse registro até que alguma conexão exista */
if(parseInt(rendimento.conectados) > 0){
rendimento.id_zona = zona.id_zona;
rendimento.id_publicador = zona.id_publicador;
rendimento.cliques = cliques[0];
if(rendimento.cliques == 0){
rendimento.ctr = 0;
rendimento.ctr = rendimento.ctr.toFixed(3);
}else rendimento.ctr = (parseFloat(rendimento.cliques) / parseFloat(rendimento.conectados)).toFixed(3);
rendimento.ganhos = parseFloat(cliques[1]).toFixed(3);
rendimento.fechamento = moment().format();
data.rendimento = rendimento;
data.data = moment().format("YYYY-MM-DD");
fnRendimento.GetRendimentosZonaDia(data,function(data,rendimentos){
ZonaSangria = function(){
cliente_redis.hmset("zona:"+data.zona.id_zona,{"cliques": 0,"rendimentos" : 0});
cliente_redis.hmset("stats:zonas:"+data.zona.id_zona,{"desconectados" : 0});
global.desconectados_zona[data.zona.id_zona] = 0;
}
data.rendimento.data = data.data;
if(typeof(rendimentos) != 'undefined'){
data.rendimento.id_rendimento = rendimentos.id_rendimento;
fnRendimento.AlterarRendimento(data.rendimento);
ZonaSangria();
}else{
fnRendimento.AdicionarRendimentos(data.rendimento);
ZonaSangria();
}
});
}
}
});
}
zonas.forEach(function(id_zona){
var data = {};
data.zona = {};
data.debug = 'zonas disponiveis';
data.zona.id_zona = id_zona;
data.sucesso = Sucesso;
data.sucesso_redis = Sucesso;
data.falha = Falha;
fnZona.GetZonaById(data);
});
}
});
}
Finally, I have to run these two routines simultaneously to avoid clearing the global variable of the script that keeps the number of visitors disconnected before saving to my database, since after saving to both tables I assign zero to it, in each campaign zone. Losing disconnections would be a risk for the statistical calculation of the application. I need to prevent the socket disconnection event from confusing the results that are being saved in the database when my setInterval runs because it is a global scope variable, so I used the new async await standard by changing the version of my Node.js server from 6 to 8, to simplify encoding and run functions simultaneously. How can I improve this code? I would like something vertical that allows me better code maintenance in the future.
setInterval(function(){
async function ExecuteActions(){
await SalvarStatsCustosCampanha();
await SalvarStatsRendimentosZona();
}
ExecuteActions();
},configs.ttl_save_stats);
I know the question is comprehensive and time-consuming analysis plus any opinion would be valid to me!
You are using at least version 8 of Node?
– Sorack
Yes, I installed it yesterday. But the code was all developed in version 6. So I didn’t have async and await support.
– Rafael Salomão
From version 8 you can use
async
andawait
, which will greatly facilitate the maintenance and understanding of the code. If it is possible to use this version I can rewrite the code for you using the new features ofECMAScript
and applying the standard ofAirBNB
coding inJavascript
– Sorack
If you give me a simple example of working, it would already help me a lot not need to be exactly following my code can be a brief explanation about the functioning of async and await. Yesterday I analyzed the documentation but it does not seem to me comprehensive of examples.
– Rafael Salomão
So, there’s actually a lot of stuff that you can improve on your code, so I think it’s easier to extract the concepts and rewrite. One thing, for example, is that you are doing everything in one function and that already greatly increases the complexity. There are several things that can be improved and I write a complete answer about it, but for that I need to know if version 8 will be available or not
– Sorack
Yes yesterday I upgraded my server to version 8
– Rafael Salomão
I recommend that you try to reduce the question to a simpler example, something that demonstrates your problem without all this complexity of your application. It would be especially useful if you could post something that is "executable" locally, for example a code that populates Redis and allows you to run your code. The chances of you getting good answers would be much higher with something like this.
– BrunoRB
The code works perfectly, but it is exactly the complexity that generated Hell callback, I need a more functional way to write this without so much confusion in the scopes to facilitate future maintenance. Even with the advent of async and await I don’t have the knack of how to modularize this so that I have access to the variables in each scope.
– Rafael Salomão