/* * Copyright (c) 2013 Websquared, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v2.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * swsong - initial API and implementation */ package org.fastcatsearch.job.indexing; import java.io.File; import java.util.ArrayList; import java.util.List; import org.fastcatsearch.cluster.ClusterUtils; import org.fastcatsearch.cluster.Node; import org.fastcatsearch.cluster.NodeJobResult; import org.fastcatsearch.cluster.NodeService; import org.fastcatsearch.common.io.Streamable; import org.fastcatsearch.control.JobService; import org.fastcatsearch.control.ResultFuture; import org.fastcatsearch.db.mapper.IndexingResultMapper.ResultStatus; import org.fastcatsearch.exception.FastcatSearchException; import org.fastcatsearch.ir.CollectionAddIndexer; import org.fastcatsearch.ir.IRService; import org.fastcatsearch.ir.MirrorSynchronizer; import org.fastcatsearch.ir.common.IndexingType; import org.fastcatsearch.ir.config.CollectionContext; import org.fastcatsearch.ir.config.CollectionIndexStatus.IndexStatus; import org.fastcatsearch.ir.config.DataInfo.RevisionInfo; import org.fastcatsearch.ir.config.DataInfo.SegmentInfo; import org.fastcatsearch.ir.index.IndexWriteInfoList; import org.fastcatsearch.ir.search.CollectionHandler; import org.fastcatsearch.job.CacheServiceRestartJob; import org.fastcatsearch.job.Job.JobResult; import org.fastcatsearch.job.cluster.NodeDirectoryCleanJob; import org.fastcatsearch.job.cluster.NodeSegmentUpdateJob; import org.fastcatsearch.job.result.IndexingJobResult; import org.fastcatsearch.job.state.IndexingTaskState; import org.fastcatsearch.service.ServiceManager; import org.fastcatsearch.task.IndexFileTransfer; import org.fastcatsearch.transport.vo.StreamableThrowable; /** * 특정 collection의 index node에서 수행되는 job. * index node가 아닌 노드에 전달되면 색인을 수행하지 않는다. * * */ public class CollectionAddIndexingJob extends IndexingJob { private static final long serialVersionUID = 7898036370433248984L; @Override public JobResult doRun() throws FastcatSearchException { prepare(IndexingType.ADD, "ALL"); Throwable throwable = null; ResultStatus resultStatus = ResultStatus.RUNNING; Object result = null; long startTime = System.currentTimeMillis(); try { IRService irService = ServiceManager.getInstance().getService(IRService.class); //find index node CollectionHandler collectionHandler = irService.collectionHandler(collectionId); CollectionContext collectionContext = irService.collectionContext(collectionId); if(collectionContext == null) { throw new FastcatSearchException("Collection [" + collectionId + "] is not exist."); } String indexNodeId = collectionContext.collectionConfig().getIndexNode(); NodeService nodeService = ServiceManager.getInstance().getService(NodeService.class); Node indexNode = nodeService.getNodeById(indexNodeId); if(!nodeService.isMyNode(indexNode)){ //Pass job to index node // nodeService.sendRequest(indexNode, this); //작업수행하지 않음. throw new RuntimeException("Invalid index node collection[" + collectionId + "] node[" + indexNodeId + "]"); } if(!updateIndexingStatusStart()) { logger.error("Cannot start indexing job. {} : {}", collectionId, indexNodeId); resultStatus = ResultStatus.CANCEL; return new JobResult(); } //증분색인은 collection handler자체를 수정하므로 copy하지 않는다. // collectionContext = collectionContext.copy(); /* * Do indexing!! */ ////////////////////////////////////////////////////////////////////////////////////////// SegmentInfo lastSegmentInfo = collectionContext.dataInfo().getLastSegmentInfo(); // if(lastSegmentInfo == null){ // //색인이 안된상태이다. // throw new FastcatSearchException("Cannot index collection. It has no full indexing information. collectionId = "+collectionContext.collectionId()); // } String lastRevisionUUID = null; if(lastSegmentInfo != null) { lastRevisionUUID = lastSegmentInfo.getRevisionInfo().getUuid(); } boolean isIndexed = false; CollectionAddIndexer collectionIndexer = new CollectionAddIndexer(collectionHandler); indexer = collectionIndexer; collectionIndexer.setTaskState(indexingTaskState); Throwable indexingThrowable = null; try { indexer.doIndexing(); }catch(Throwable e){ indexingThrowable = e; } finally { if (collectionIndexer != null) { try { isIndexed = collectionIndexer.close(); } catch (Throwable closeThrowable) { // 이전에 이미 발생한 에러가 있다면 close 중에 발생한 에러보다 이전 에러를 throw한다. if (indexingThrowable == null) { indexingThrowable = closeThrowable; } } } if(indexingThrowable != null){ throw indexingThrowable; } } if(!isIndexed && stopRequested){ //여기서 끝낸다. throw new IndexingStopException(); } /* * 색인파일 원격복사. */ indexingTaskState.setStep(IndexingTaskState.STEP_FILECOPY); IndexWriteInfoList indexWriteInfoList = collectionIndexer.indexWriteInfoList(); SegmentInfo segmentInfo = collectionContext.dataInfo().getLastSegmentInfo(); if(segmentInfo != null) { String segmentId = segmentInfo.getId(); logger.debug("Transfer index data collection[{}] >> {}", collectionId, segmentInfo); RevisionInfo revisionInfo = segmentInfo.getRevisionInfo(); int revisionId = revisionInfo.getId(); int dataSequence = collectionContext.getIndexSequence(); File revisionDir = collectionContext.collectionFilePaths().dataPaths().revisionFile(dataSequence, segmentId, revisionId); File segmentDir = collectionContext.collectionFilePaths().dataPaths().segmentFile(dataSequence, segmentId); /* * 색인파일 원격복사. */ List<Node> nodeList = new ArrayList<Node>(nodeService.getNodeById(collectionContext.collectionConfig().getDataNodeList())); //색인노드가 data node에 추가되어있다면 제거한다. nodeList.remove(nodeService.getMyNode()); if(nodeList.size() > 0) { //색인노드만 있어도 OK. NodeJobResult[] nodeResultList = null; //이전 세그먼트 존재시 확인. if(lastRevisionUUID != null) { /* * lastRevisionUUID가 일치하는 보낼노드가 존재하는지 확인한다. * 존재한다면 mirror sync file을 만든다. * * */ GetCollectionIndexRevisionUUIDJob getRevisionUUIDJob = new GetCollectionIndexRevisionUUIDJob(); getRevisionUUIDJob.setArgs(collectionId); nodeResultList = ClusterUtils.sendJobToNodeList(getRevisionUUIDJob, nodeService, nodeList, false); //성공한 node만 전송. nodeList = new ArrayList<Node>(); for (int i = 0; i < nodeResultList.length; i++) { NodeJobResult r = nodeResultList[i]; logger.debug("node#{} >> {}", i, r); if (r.isSuccess()) { String uuid = (String) r.result(); if(lastRevisionUUID.equals(uuid)){ nodeList.add(r.node()); }else{ logger.error("{} has different uuid > {}", r.node(), uuid); } }else{ logger.warn("Cannot get revision information > {}", r.node()); } } } //TODO indexWriteInfoList를 파일로 저장해놓아야 실패한 노드에 차후 전송이 가능하게 된다. 또는 mirror sync파일을 매번 만들어 놓는다던지.. //revision이 여러번 바뀌면 mirror sync를 여러번전송해서 한번씩 업데이트. File transferDir = null; /* * 동기화 파일 생성. * 여기서는 1. segment/ 파일들에 덧붙일 정보들이 준비되어있어야한다. revision은 그대로 복사하므로 준비필요없음. */ //0보다 크면 revision이 증가된것이다. boolean revisionAppended = revisionInfo.getId() > 0; boolean revisionHasInserts = revisionInfo.getInsertCount() > 0; File mirrorSyncFile = null; if(revisionAppended){ if(revisionHasInserts){ mirrorSyncFile = new MirrorSynchronizer().createMirrorSyncFile(indexWriteInfoList, revisionDir); logger.debug("동기화 파일 생성 >> {}", mirrorSyncFile.getAbsolutePath()); } transferDir = revisionDir; }else{ //세그먼트 전체전송. transferDir = segmentDir; logger.debug("세그먼트 생성되어 segment dir 전송필요"); } if(stopRequested){ throw new IndexingStopException(); } // 색인전송할디렉토리를 먼저 비우도록 요청.segmentDir File relativeDataDir = environment.filePaths().relativise(transferDir); NodeDirectoryCleanJob cleanJob = new NodeDirectoryCleanJob(relativeDataDir); nodeResultList = ClusterUtils.sendJobToNodeList(cleanJob, nodeService, nodeList, false); //성공한 node만 전송. nodeList = new ArrayList<Node>(); for (int i = 0; i < nodeResultList.length; i++) { NodeJobResult r = nodeResultList[i]; logger.debug("node#{} >> {}", i, r); if (r.isSuccess()) { nodeList.add(r.node()); }else{ logger.warn("Do not send index file to {}", r.node()); } } // 색인된 Segment 파일전송. //case 1. segment-append 파일과 revision/ 파일들을 전송한다. //case 2. 만약 segment가 생성 or 수정된 경우라면 그대로 전송하면된다. TransferIndexFileMultiNodeJob transferJob = new TransferIndexFileMultiNodeJob(transferDir, nodeList); ResultFuture resultFuture = JobService.getInstance().offer(transferJob); Object obj = resultFuture.take(); if(resultFuture.isSuccess() && obj != null){ nodeResultList = (NodeJobResult[]) obj; } //성공한 node만 전송. nodeList = new ArrayList<Node>(); for (int i = 0; i < nodeResultList.length; i++) { NodeJobResult r = nodeResultList[i]; logger.debug("node#{} >> {}", i, r); if (r.isSuccess()) { nodeList.add(r.node()); }else{ logger.warn("Do not reload at {}", r.node()); } } if(stopRequested){ throw new IndexingStopException(); } /* * 데이터노드에 컬렉션 리로드 요청. */ NodeSegmentUpdateJob reloadJob = new NodeSegmentUpdateJob(collectionContext); nodeResultList = ClusterUtils.sendJobToNodeList(reloadJob, nodeService, nodeList, false); for (int i = 0; i < nodeResultList.length; i++) { NodeJobResult r = nodeResultList[i]; logger.debug("node#{} >> {}", i, r); if (r.isSuccess()) { logger.info("{} Collection reload OK.", r.node()); }else{ logger.warn("{} Collection reload Fail.", r.node()); } } } } indexingTaskState.setStep(IndexingTaskState.STEP_FINALIZE); int duration = (int) (System.currentTimeMillis() - startTime); /* * 캐시 클리어. */ getJobExecutor().offer(new CacheServiceRestartJob()); IndexStatus indexStatus = collectionContext.indexStatus().getAddIndexStatus(); indexingLogger.info("[{}] Collection Add Indexing Finished! {} time = {}", collectionId, indexStatus, duration); logger.info("== SegmentStatus =="); collectionHandler.printSegmentStatus(); logger.info("==================="); result = new IndexingJobResult(collectionId, indexStatus, duration); resultStatus = ResultStatus.SUCCESS; indexingTaskState.setStep(IndexingTaskState.STEP_END); return new JobResult(result); } catch (IndexingStopException e){ if(stopRequested){ resultStatus = ResultStatus.STOP; }else{ resultStatus = ResultStatus.CANCEL; } result = new IndexingJobResult(collectionId, null, (int) (System.currentTimeMillis() - startTime), false); return new JobResult(result); } catch (Throwable e) { indexingLogger.error("[" + collectionId + "] Indexing", e); throwable = e; resultStatus = ResultStatus.FAIL; throw new FastcatSearchException("ERR-00501", throwable, collectionId); // 색인실패. } finally { Streamable streamableResult = null; if (throwable != null) { streamableResult = new StreamableThrowable(throwable); } else if (result instanceof Streamable) { streamableResult = (Streamable) result; } updateIndexingStatusFinish(resultStatus, streamableResult); } } }