/* * Copyright (c) 2011 LinkedIn, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.flaptor.indextank.storage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; import org.apache.thrift.TException; import com.flaptor.indextank.rpc.LogRecord; import com.flaptor.indextank.util.FormatLogger; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; public class IndexLog { private static final FormatLogger alertLogger = FormatLogger.getAlertsLogger(); private static final FormatLogger logger = new FormatLogger(); public static final int DEFAULT_SEGMENT_SIZE = 30 * 1024 * 1024; String code; private final int segmentSize; private final LogRoot root; public IndexLog(String code) throws FileNotFoundException { this(code, new LogRoot(), DEFAULT_SEGMENT_SIZE); } public IndexLog(String code, LogRoot root, int segmentSize) { this.root = root; this.code = code; this.segmentSize = segmentSize; this.getOptimizedPath().mkdirs(); this.getSegmentsPath().mkdirs(); } public List<Segment> getSegments() { return Segment.getSegments(root, getSegmentsPath()); } public List<Segment> getSortedSegments() { return Segment.getSegments(root, getSegmentsPath(), true); } public synchronized void appendBuffer(long initialTimestamp, MemoryBuffer buffer) throws TException, IOException { // filter out empty records but add a warning to the alert log Predicate<LogRecord> isNonEmpty = new Predicate<LogRecord>() { public boolean apply(LogRecord r) { if (!isValidRecord(r)) { throw new RuntimeException(String.format("At least one of the records was missing a docid: code=%s | fields: %s | vars: %s | categories: %s", r.get_index_code(), r.get_fields(), r.get_variables(), r.get_categories())); } if (!r.is_set_docid()) { alertLogger.warn("Tried to append an empty record for index %s", r.get_index_code()); return false; } return true; } }; // write the actual segment Segment.createUnsortedSegment(root, getSegmentsPath(), initialTimestamp, Iterators.filter(new RecordIterator(null, buffer.protocol, "Buffer for " + this.code + " at " + initialTimestamp), isNonEmpty)); sort(false); } public synchronized void sortNow() throws TException, IOException { sort(true); } synchronized void sort(boolean now) throws TException, IOException { List<Segment> unsortedSegments = Segment.getSegments(root, getSegmentsPath(), false); if (!unsortedSegments.isEmpty()) { now = now || unsortedSegments.size() > 30; now = now || totalSize(unsortedSegments) > segmentSize; if (now) { // we should sort now Segment sorted = Segment.createSortedSegment(root, getSegmentsPath(), unsortedSegments.get(0).timestamp, unsortedSegments); // and now delete the replaced segments for (Segment segment : unsortedSegments) { segment.delete(); } logger.info("Sorted %d unsorted segments into %s", unsortedSegments.size(), sorted); } } } private int totalSize(List<Segment> unsortedSegments) { int totalSize = 0; for (Segment segment : unsortedSegments) { totalSize += segment.length(); } return totalSize; } public Segment getLargestOptimizedSegment() { return Iterables.getLast(Segment.iterateSegments(root, getOptimizedPath(), true), null); } public List<Segment> getOptimizedSegments() { return Segment.getSegments(root, getOptimizedPath()); } public Segment getOptimizedSegment(long timestamp) { return Segment.getSegment(root, getOptimizedPath(), timestamp, true); } public File getSegmentsPath() { return new File(root.getIndexLogPath(code), "segments"); } public File getOptimizedPath() { return new File(root.getIndexLogPath(code), "optimized"); } public Segment getSortedSegment(long timestamp) { return Segment.getSegment(root, getSegmentsPath(), timestamp, true); } public Segment getSegment(long timestamp) { return Segment.getSegment(root, getSegmentsPath(), timestamp, null); } public Segment getFirstSortedSegment() { return Iterables.get(Segment.iterateSegments(root, getSegmentsPath(), true), 0, null); } public Segment getFirstSegment() { return Iterables.get(Segment.iterateSegments(root, getSegmentsPath()), 0, null); } public Segment getLastSegment() { return Iterables.getLast(Segment.iterateSegments(root, getSegmentsPath()), null); } public static boolean isLog(File file) { return new File(file, "segments").exists(); } public void markReadNow() { try { File file = getLastReadFile(); if (!file.createNewFile()) { file.setLastModified(System.currentTimeMillis()); } } catch (IOException e) { throw new RuntimeException(e); } } public long getLastRead() { return getLastReadFile().lastModified(); } private File getLastReadFile() { return new File(root.getIndexLogPath(code), "last_read"); } public boolean isValidRecord(LogRecord r) { return r.is_set_docid() | !(r.is_set_fields() && r.is_set_variables() && r.is_set_categories()); } }