package org.fastcatsearch.ir;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Calendar;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.fastcatsearch.cluster.NodeService;
import org.fastcatsearch.common.io.Streamable;
import org.fastcatsearch.env.Environment;
import org.fastcatsearch.exception.FastcatSearchException;
import org.fastcatsearch.ir.io.DataInput;
import org.fastcatsearch.ir.io.DataOutput;
import org.fastcatsearch.job.MasterNodeJob;
import org.fastcatsearch.service.AbstractService;
import org.fastcatsearch.service.ServiceManager;
import org.fastcatsearch.settings.Settings;
/**
* 컬렉션별 검색갯수를 모은다. search노드는 1초에 한번 mater로 갯수를 보낸다. master노드는 1초에 한번 갯수를 취합하여 aggregateCountResult에 유지한다.
*
* */
public class CollectionQueryCountService extends AbstractService {
private RealtimeQueryCountModule aggregateModule;
private Map<String, Integer> aggregateCountResult;
private Timer aggregateTimer;
private Timer reportTimer;
public CollectionQueryCountService(Environment environment, Settings settings, ServiceManager serviceManager) {
super(environment, settings, serviceManager);
if (environment.isMasterNode()) {
aggregateModule = new RealtimeQueryCountModule(environment, settings);
}
}
/**
* 더해진 검색갯수는 1초에 한번씩 clear된다.
* */
public void addQueryCount(String collectionId, int count) {
if (aggregateModule != null) {
if (!aggregateModule.incrementQueryCount(collectionId, count)) {
aggregateModule.registerQueryCount(collectionId);
}
} else {
throw new UnsupportedOperationException();
}
}
public Map<String, Integer> aggregateCountResult() {
return aggregateCountResult;
}
@Override
protected boolean doStart() throws FastcatSearchException {
IRService irService = serviceManager.getService(IRService.class);
Set<String> collectionIdSet = irService.getDataNodeCollectionIdSet();
boolean isDataNode = collectionIdSet.size() > 0;
int period = 1000; // 1초.
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.SECOND, 1);
if (environment.isMasterNode()) {
aggregateModule.load();
aggregateTimer = new Timer("CollectionQueryCountAggregateTimer", true);
// 1초단위로 취합하는 timertask를 시작한다.
aggregateCountResult = new HashMap<String, Integer>();// 처음에 빈 객체.
aggregateTimer.scheduleAtFixedRate(new AggregateCountTask(), cal.getTime(), period);
}
if (isDataNode) {
logger.info("This is data node for {}", collectionIdSet);
// 1초 단위로 report하는 timertask를 시작한다.
reportTimer = new Timer("CollectionQueryCountReportTimer", true);
reportTimer.scheduleAtFixedRate(new ReportCountTask(), cal.getTime(), period);
}
return true;
}
@Override
protected boolean doStop() throws FastcatSearchException {
if (aggregateTimer != null) {
aggregateTimer.cancel();
}
if (reportTimer != null) {
reportTimer.cancel();
}
if (aggregateModule != null) {
aggregateModule.unload();
}
return true;
}
@Override
protected boolean doClose() throws FastcatSearchException {
aggregateModule = null;
aggregateTimer = null;
reportTimer = null;
return true;
}
private class ReportCountTask extends TimerTask {
@Override
public void run() {
NodeService nodeService = serviceManager.getService(NodeService.class);
IRService irService = serviceManager.getService(IRService.class);
RealtimeQueryCountModule module = irService.queryCountModule();
Set<String> collectionIdSet = irService.getDataNodeCollectionIdSet();
Map<String, Integer> result = new HashMap<String, Integer>(collectionIdSet.size());
for (String collectionId : collectionIdSet) {
int count = module.getQueryCount(collectionId);
if(count > 0){
result.put(collectionId, count);
// logger.debug("##[REPORT] search count {} > {}", collectionId, count);
}
}
//모두 0이면 보내지 않는다.
if(result.size() > 0){
ReportQueryCountJob job = new ReportQueryCountJob(result);
job.setNoResult();
nodeService.sendRequestToMaster(job);
}
}
}
private class AggregateCountTask extends TimerTask {
@Override
public void run() {
// logger.debug("RUN AggregateCountTask");
Set<String> set = aggregateModule.getRegisteredCollectionSet();
Map<String, Integer> result = new HashMap<String, Integer>();
for (String collectionId : set) {
int count = aggregateModule.getQueryCount(collectionId);
if (count >= 0) {
result.put(collectionId, count);
// logger.debug("##[AGGREGATE] search count {} > {}", collectionId, count);
}
}
// set
aggregateCountResult = result;
}
}
/*
* master서버에 검색갯수를 전달한다. masternode에서 실행되야한다.
*/
public static class ReportQueryCountJob extends MasterNodeJob implements Streamable {
private static final long serialVersionUID = -854194838400321409L;
public ReportQueryCountJob() {
}
public ReportQueryCountJob(Map<String, Integer> result) {
this.args = result;
}
@Override
public JobResult doRun() throws FastcatSearchException {
// logger.debug("ReportQueryCountJob > {}", args);
Map<String, Integer> result = (Map<String, Integer>) args;
CollectionQueryCountService collectionQueryCountService = ServiceManager.getInstance().getService(CollectionQueryCountService.class);
for (Entry<String, Integer> entry : result.entrySet()) {
if (entry.getValue() != null && entry.getValue() > 0) {
if (collectionQueryCountService.isRunning()) {
collectionQueryCountService.addQueryCount(entry.getKey(), entry.getValue());
}
// logger.debug("## add query count {} : {}", entry.getKey(), entry.getValue());
}
}
return new JobResult(true);
}
@Override
public void readFrom(DataInput input) throws IOException {
int size = input.readVInt();
Map<String, Integer> result = new HashMap<String, Integer>(size);
for (int i = 0; i < size; i++) {
result.put(input.readString(), input.readVInt());
}
this.args = result;
}
@Override
public void writeTo(DataOutput output) throws IOException {
Map<String, Integer> result = (Map<String, Integer>) args;
output.writeVInt(result.size());
for (Entry<String, Integer> entry : result.entrySet()) {
if (entry.getValue() != null) {
output.writeString(entry.getKey());
output.writeVInt(entry.getValue().intValue());
}
}
}
}
}