/* * 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.ir; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.xml.bind.JAXBException; import org.apache.commons.io.FileUtils; import org.fastcatsearch.alert.ClusterAlertService; import org.fastcatsearch.cluster.NodeLoadBalancable; import org.fastcatsearch.common.QueryCacheModule; import org.fastcatsearch.control.JobService; import org.fastcatsearch.env.Environment; import org.fastcatsearch.exception.FastcatSearchException; import org.fastcatsearch.ir.analysis.AnalyzerFactoryManager; import org.fastcatsearch.ir.analysis.AnalyzerPoolManager; import org.fastcatsearch.ir.common.IRException; import org.fastcatsearch.ir.common.SettingException; import org.fastcatsearch.ir.config.CollectionConfig; import org.fastcatsearch.ir.config.CollectionContext; import org.fastcatsearch.ir.config.CollectionsConfig; import org.fastcatsearch.ir.config.CollectionsConfig.Collection; import org.fastcatsearch.ir.config.IndexingScheduleConfig; import org.fastcatsearch.ir.config.IndexingScheduleConfig.IndexingSchedule; import org.fastcatsearch.ir.config.JDBCSourceConfig; import org.fastcatsearch.ir.config.JDBCSourceInfo; import org.fastcatsearch.ir.config.JDBCSupportConfig; import org.fastcatsearch.ir.group.GroupResults; import org.fastcatsearch.ir.group.GroupsData; import org.fastcatsearch.ir.query.InternalSearchResult; import org.fastcatsearch.ir.query.Result; import org.fastcatsearch.ir.search.CollectionHandler; import org.fastcatsearch.ir.settings.AnalyzerSetting; import org.fastcatsearch.job.PriorityScheduledJob; import org.fastcatsearch.job.ScheduledJobEntry; import org.fastcatsearch.job.indexing.MasterCollectionAddIndexingJob; import org.fastcatsearch.job.indexing.MasterCollectionFullIndexingJob; import org.fastcatsearch.module.ModuleException; import org.fastcatsearch.notification.NotificationService; import org.fastcatsearch.notification.message.CollectionLoadErrorNotification; import org.fastcatsearch.service.AbstractService; import org.fastcatsearch.service.ServiceManager; import org.fastcatsearch.settings.SearchPageSettings; import org.fastcatsearch.settings.SettingFileNames; import org.fastcatsearch.settings.Settings; import org.fastcatsearch.util.CollectionContextUtil; import org.fastcatsearch.util.FilePaths; import org.fastcatsearch.util.JAXBConfigs; public class IRService extends AbstractService { private Map<String, CollectionHandler> collectionHandlerMap; // TODO 캐시방식을 변경하자. private QueryCacheModule<String, Result> searchCache; private QueryCacheModule<String, InternalSearchResult> shardSearchCache; private QueryCacheModule<String, GroupResults> groupingCache; private QueryCacheModule<String, GroupsData> groupingDataCache; private QueryCacheModule<String, Result> documentCache; private CollectionsConfig collectionsConfig; private JDBCSourceConfig jdbcSourceConfig; private JDBCSupportConfig jdbcSupportConfig; private SearchPageSettings searchPageSettings; private File collectionsRoot; private RealtimeQueryCountModule realtimeQueryStatisticsModule; private AnalyzerFactoryManager analyzerFactoryManager; private Set<String> dataNodeCollectionIdSet; //이 노드가 데이터노드인 컬렉션세트. 쿼리 count집계시 사용된다. public IRService(Environment environment, Settings settings, ServiceManager serviceManager) { super(environment, settings, serviceManager); realtimeQueryStatisticsModule = new RealtimeQueryCountModule(environment, settings); } public void setAnalyzerFactoryManager(AnalyzerProvider analyzerProvider){ this.analyzerFactoryManager = analyzerProvider.getAnalyzerFactoryManager(); } protected boolean doStart() throws FastcatSearchException { try{ realtimeQueryStatisticsModule.load(); }catch(Throwable t){ ClusterAlertService.getInstance().alert(t); } collectionHandlerMap = new ConcurrentHashMap<String, CollectionHandler>(); // collections 셋팅을 읽어온다. collectionsRoot = environment.filePaths().getCollectionsRoot().file(); try { collectionsConfig = JAXBConfigs.readConfig(new File(collectionsRoot, SettingFileNames.collections), CollectionsConfig.class); } catch (JAXBException e) { logger.error("[ERROR] fail to read collection config. " + e.getMessage(), e); ClusterAlertService.getInstance().alert(e); } try { jdbcSourceConfig = JAXBConfigs.readConfig(new File(collectionsRoot, SettingFileNames.jdbcSourceConfig), JDBCSourceConfig.class); } catch (JAXBException e) { logger.error("[ERROR] fail to read jdbc source list. " + e.getMessage(), e); ClusterAlertService.getInstance().alert(e); } if(jdbcSourceConfig == null) { jdbcSourceConfig = new JDBCSourceConfig(); } try { jdbcSupportConfig = JAXBConfigs.readConfig(new File(collectionsRoot, SettingFileNames.jdbcSupportConfig), JDBCSupportConfig.class); } catch (JAXBException e) { logger.error("[ERROR] fail to read jdbc support. " + e.getMessage(), e); ClusterAlertService.getInstance().alert(e); } if(jdbcSupportConfig == null) { jdbcSupportConfig = new JDBCSupportConfig(); } File file = environment.filePaths().configPath().file(SettingFileNames.searchPageSettings); if(file.exists()){ try { searchPageSettings = JAXBConfigs.readConfig(file, SearchPageSettings.class); } catch (JAXBException e) { logger.error("[ERROR] fail to read search page settings. " + e.getMessage(), e); ClusterAlertService.getInstance().alert(e); } }else{ searchPageSettings = new SearchPageSettings(); } dataNodeCollectionIdSet = new HashSet<String>(); List<Collection> collectionList = collectionsConfig.getCollectionList(); for (int collectionInx = 0 ; collectionInx < collectionList.size(); collectionInx++) { Collection collection = collectionList.get(collectionInx); try { String collectionId = collection.getId(); loadCollectionHandler(collectionId, collection); } catch (Throwable e) { logger.error("[ERROR] " + e.getMessage(), e); } } try { //가공된 컬렉션 xml 을 저장한다. JAXBConfigs.writeConfig(new File(collectionsRoot, SettingFileNames.collections), collectionsConfig, CollectionsConfig.class); } catch (JAXBException e) { logger.error("", e); ClusterAlertService.getInstance().alert(e); } searchCache = new QueryCacheModule<String, Result>(environment, settings); shardSearchCache = new QueryCacheModule<String, InternalSearchResult>(environment, settings); groupingCache = new QueryCacheModule<String, GroupResults>(environment, settings); groupingDataCache = new QueryCacheModule<String, GroupsData>(environment, settings); documentCache = new QueryCacheModule<String, Result>(environment, settings); try { searchCache.load(); shardSearchCache.load(); groupingCache.load(); groupingDataCache.load(); documentCache.load(); } catch (ModuleException e) { ClusterAlertService.getInstance().alert(e); throw new FastcatSearchException("ERR-00320"); } return true; } public CollectionHandler loadCollectionHandler(String collectionId) throws IRException, SettingException { return loadCollectionHandler(collectionId, null); } public CollectionHandler loadCollectionHandler(String collectionId, Collection collection) throws IRException, SettingException { Throwable t = null; try { realtimeQueryStatisticsModule.registerQueryCount(collectionId); CollectionContext collectionContext = null; CollectionHandler collectionHandler = null; logger.info("Load Collection [{}]", collectionId); if(collection == null){ for (Collection col : collectionsConfig.getCollectionList()) { if(col.getId().equalsIgnoreCase(collectionId)){ collection = col; break; } } } try { collectionContext = loadCollectionContext(collection); } catch (SettingException e) { logger.error("컬렉션context 로드실패 " + collectionId); throw e; } if (collectionContext == null) { return null; } else { collectionHandler = new CollectionHandler(collectionContext, analyzerFactoryManager); collectionHandler.setQueryCounter(realtimeQueryStatisticsModule.getQueryCounter(collectionId)); if(collectionContext.collectionConfig().getDataNodeList() != null && collectionContext.collectionConfig().getDataNodeList().contains(environment.myNodeId())){ dataNodeCollectionIdSet.add(collectionId); } } collectionHandler.load(); /* * 이전 컬렉션 handler가 있다면 닫아준다. */ CollectionHandler previousCollectionHandler = collectionHandlerMap.put(collectionId, collectionHandler); if(previousCollectionHandler != null){ try { previousCollectionHandler.close(); } catch (IOException e) { throw new IRException(e); } } return collectionHandler; } catch(IRException e) { t = e; throw e; } catch(SettingException e) { t = e; throw e; } finally { if(t != null) { ClusterAlertService.getInstance().alert(t); NotificationService notificationService = ServiceManager.getInstance().getService(NotificationService.class); notificationService.sendNotification(new CollectionLoadErrorNotification(collection.getId(), t)); } } } public JDBCSourceConfig getJDBCSourceConfig() { return jdbcSourceConfig; } public JDBCSourceInfo getJDBCSourceInfo(String jdbcId) { List<JDBCSourceInfo> jdbcList = jdbcSourceConfig.getJdbcSourceInfoList(); for (JDBCSourceInfo jdbcInfo : jdbcList) { logger.trace("jdbc-id:{}", jdbcInfo.getId()); if(jdbcId.equals(jdbcInfo.getId())) { return jdbcInfo; } } return null; } public JDBCSupportConfig getJDBCSupportConfig() { return jdbcSupportConfig; } public void updateJDBCSourceConfig(JDBCSourceConfig jdbcSourceConfig) throws JAXBException { this.jdbcSourceConfig = jdbcSourceConfig; //가공된 컬렉션 xml 을 저장한다. JAXBConfigs.writeConfig(new File(collectionsRoot, SettingFileNames.jdbcSourceConfig), jdbcSourceConfig, JDBCSourceConfig.class); } public CollectionHandler collectionHandler(String collectionId) { if(collectionHandlerMap !=null && collectionHandlerMap.containsKey(collectionId)) { return collectionHandlerMap.get(collectionId); } return null; } public CollectionContext collectionContext(String collectionId) { CollectionHandler h = collectionHandler(collectionId); if (h != null) { return h.collectionContext(); } else { return null; } } public List<Collection> getCollectionList() { return collectionsConfig.getCollectionList(); } public CollectionHandler createCollection(String collectionId, CollectionConfig collectionConfig) throws IRException, SettingException { if (collectionsConfig.contains(collectionId)) { // 이미 컬렉션 존재. throw new SettingException("Collection id already exists. " + collectionId); } try { FilePaths collectionFilePaths = environment.filePaths().collectionFilePaths(collectionId); collectionFilePaths.file().mkdirs(); CollectionContext collectionContext = CollectionContextUtil.create(collectionConfig, collectionFilePaths); collectionsConfig.addCollection(collectionId); JAXBConfigs.writeConfig(new File(collectionsRoot, SettingFileNames.collections), collectionsConfig, CollectionsConfig.class); CollectionHandler collectionHandler = new CollectionHandler(collectionContext, analyzerFactoryManager); collectionHandlerMap.put(collectionId, collectionHandler); realtimeQueryStatisticsModule.registerQueryCount(collectionId); return collectionHandler; } catch (IRException e) { throw e; } catch (Exception e) { logger.error("Error while create collection", e); throw new SettingException(e); } } public CollectionContext loadCollectionContext(Collection collection) throws SettingException { FilePaths collectionFilePaths = environment.filePaths().collectionFilePaths(collection.getId()); if (!collectionFilePaths.file().exists()) { // 디렉토리가 존재하지 않으면. logger.error("[{}]컬렉션 디렉토리가 존재하지 않습니다.", collection); return null; } return CollectionContextUtil.load(collection, collectionFilePaths); } public boolean removeCollection(String collectionId) throws SettingException { if (!collectionsConfig.contains(collectionId)) { return false; } else { try { CollectionHandler collectionHandler = collectionHandlerMap.remove(collectionId); if(collectionHandler != null){ collectionHandler.close(); } collectionsConfig.removeCollection(collectionId); JAXBConfigs.writeConfig(new File(collectionsRoot, SettingFileNames.collections), collectionsConfig, CollectionsConfig.class); FilePaths collectionFilePaths = environment.filePaths().collectionFilePaths(collectionId); //FileUtils.deleteDirectory(collectionFilePaths.file()); if(collectionFilePaths.file().exists()) { FileUtils.forceDelete(collectionFilePaths.file()); } return true; } catch (Exception e) { logger.error("Error while remove collection", e); throw new SettingException(e); } } } // public CollectionHandler promoteCollection(CollectionHandler collectionHandler, String collectionId) throws IRException { // Exception ex = null; // try { // String collectionTmp = collectionHandler.collectionId(); // File prevFile = collectionHandler.indexFilePaths().file(); // File newFile = environment.filePaths().collectionFilePaths(collectionId).file(); // collectionHandlerMap.remove(collectionHandler.collectionId()); // collectionHandler.close(); // // collectionsConfig.removeCollection(collectionTmp); // collectionsConfig.addCollection(collectionId); // logger.trace("remove ok. {}", collectionTmp); // JAXBConfigs.writeConfig(new File(collectionsRoot, SettingFileNames.collections), collectionsConfig, CollectionsConfig.class); // logger.trace("ok collection promoted {}->{}:{}", collectionTmp, collectionId, newFile.getAbsoluteFile()); // prevFile.renameTo(newFile); // logger.trace("rename ok. {}", newFile); // collectionHandler = loadCollectionHandler(collectionId); // logger.trace("load ok. {}", collectionId); // return collectionHandler; // } catch (IOException e) { ex = e; // } catch (JAXBException e) { ex = e; // } catch (SettingException e) { ex = e; // } finally { // // if(ex!=null) { // logger.error("",ex); // throw new IRException(ex); // } // } // return null; // } public CollectionHandler removeCollectionHandler(String collectionId) { realtimeQueryStatisticsModule.removeQueryCount(collectionId); return collectionHandlerMap.remove(collectionId); } public CollectionHandler putCollectionHandler(String collectionId, CollectionHandler collectionHandler) { //write config file if not exists if(!collectionHandlerMap.containsKey(collectionId)) { collectionsConfig.addCollection(collectionId); try { JAXBConfigs.writeConfig(new File(collectionsRoot, SettingFileNames.collections), collectionsConfig, CollectionsConfig.class); } catch (JAXBException e) { logger.error("", e); } } return collectionHandlerMap.put(collectionId, collectionHandler); } public CollectionHandler loadCollectionHandler(CollectionContext collectionContext) throws IRException, SettingException { return new CollectionHandler(collectionContext, analyzerFactoryManager).load(); } protected boolean doStop() throws FastcatSearchException { realtimeQueryStatisticsModule.unload(); Iterator<Entry<String, CollectionHandler>> iter = collectionHandlerMap.entrySet().iterator(); while (iter.hasNext()) { Entry<String, CollectionHandler> entry = iter.next(); try { CollectionHandler collectionHandler = entry.getValue(); if (collectionHandler != null) { collectionHandler.close(); } logger.info("Shutdown Collection [{}]", entry.getKey()); } catch (IOException e) { logger.error("[ERROR] " + e.getMessage(), e); throw new FastcatSearchException("IRService 종료중 에러발생.", e); } } searchCache.unload(); shardSearchCache.unload(); groupingCache.unload(); groupingDataCache.unload(); documentCache.unload(); collectionHandlerMap.clear(); return true; } @Override protected boolean doClose() throws FastcatSearchException { collectionHandlerMap = null; realtimeQueryStatisticsModule = null; return true; } public QueryCacheModule<String, Result> searchCache() { return searchCache; } public QueryCacheModule<String, GroupResults> groupingCache() { return groupingCache; } public QueryCacheModule<String, Result> documentCache() { return documentCache; } public void registerLoadBanlancer(NodeLoadBalancable nodeLoadBalancable) { // 차후 검색시 로드밸런싱에 대비하여 먼저 collectionId로 node들을 등록해놓는다. for (Collection collection : getCollectionList()) { String collectionId = collection.getId(); CollectionHandler collectionHandler = collectionHandlerMap.get(collectionId); if (collectionHandler == null) { continue; } List<String> dataNodeIdList = collectionHandler.collectionContext().collectionConfig().getDataNodeList(); nodeLoadBalancable.updateLoadBalance(collectionId, dataNodeIdList); } } public RealtimeQueryCountModule queryCountModule() { return realtimeQueryStatisticsModule; } private SimpleDateFormat simpleDateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static String IndexingSchduleKey = "INDEXING-SCHEDULE-"; public boolean reloadSchedule(String collectionId) { CollectionContext collectionContext = collectionContext(collectionId); if (collectionContext == null) { return false; } String scheduleKey = IndexingSchduleKey + collectionId; JobService.getInstance().cancelSchedule(scheduleKey); IndexingScheduleConfig indexingScheduleConfig = collectionContext(collectionId).indexingScheduleConfig(); IndexingSchedule fullIndexingSchedule = indexingScheduleConfig.getFullIndexingSchedule(); IndexingSchedule addIndexingSchedule = indexingScheduleConfig.getAddIndexingSchedule(); List<ScheduledJobEntry> scheduledEntryList = new ArrayList<ScheduledJobEntry>(); if (fullIndexingSchedule != null) { MasterCollectionFullIndexingJob job = new MasterCollectionFullIndexingJob(); job.setArgs(collectionId); if (fullIndexingSchedule.isActive()) { String startTime = fullIndexingSchedule.getStart(); int periodInSecond = fullIndexingSchedule.getPeriodInSecond(); try { logger.debug("Load full indexing schdule {} : {}: {}", collectionId, startTime, periodInSecond); if(periodInSecond >= 0){ //실행이 보장되는 스케쥴 작업으로 생성. scheduledEntryList.add(new ScheduledJobEntry(job, simpleDateFormat.parse(startTime), periodInSecond, true)); } } catch (ParseException e) { logger.error("[{}] Full Indexing schedule time parse error : {}", collectionId, startTime); return false; } } } if (addIndexingSchedule != null) { MasterCollectionAddIndexingJob job = new MasterCollectionAddIndexingJob(); job.setArgs(collectionId); if (addIndexingSchedule.isActive()) { String startTime = addIndexingSchedule.getStart(); int periodInSecond = addIndexingSchedule.getPeriodInSecond(); try { logger.debug("Load add indexing schdule {} : {}: {}", collectionId, startTime, periodInSecond); if(periodInSecond >= 0){ scheduledEntryList.add(new ScheduledJobEntry(job, simpleDateFormat.parse(startTime), periodInSecond, false)); } } catch (ParseException e) { logger.error("[{}] Add Indexing schedule time parse error : {}", collectionId, startTime); return false; } } } if(scheduledEntryList.size() > 0){ PriorityScheduledJob scheduledJob = new PriorityScheduledJob(scheduleKey, scheduledEntryList); JobService.getInstance().schedule(scheduledJob, true); }else{ logger.info("Collection {} has no indexing schedule.", collectionId); } return true; } public void reloadAllSchedule() { // 색인 스케쥴등록. for (CollectionsConfig.Collection collection : getCollectionList()) { String collectionId = collection.getId(); reloadSchedule(collectionId); } } public Set<String> getDataNodeCollectionIdSet(){ return dataNodeCollectionIdSet; } // 모든 컬렉션들의 검색노드들을 모아서 리턴한다. public List<String> getSearchNodeList() { Set<String> searchNodeSet = new HashSet<String>(); for (CollectionHandler collectionHandler : collectionHandlerMap.values()) { List<String> searchNodeList = collectionHandler.collectionContext().collectionConfig().getSearchNodeList(); if (searchNodeList != null) { for (String searchNodeId : searchNodeList) { searchNodeSet.add(searchNodeId); } } } return new ArrayList<String>(searchNodeSet); } public AnalyzerPoolManager createAnalyzerPoolManager(List<AnalyzerSetting> analyzerSettingList) { AnalyzerPoolManager analyzerPoolManager = new AnalyzerPoolManager(); analyzerPoolManager.register(analyzerSettingList, analyzerFactoryManager); return analyzerPoolManager; } public SearchPageSettings getSearchPageSettings(){ return searchPageSettings; } public void updateSearchPageSettings(SearchPageSettings searchPageSettings){ this.searchPageSettings = searchPageSettings; } }