/* * Copyright 2015 Edward Capriolo * * 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 io.teknek.nibiru.plugins; import io.teknek.nibiru.ServerId; import io.teknek.nibiru.Store; import io.teknek.nibiru.Keyspace; import io.teknek.nibiru.Server; import io.teknek.nibiru.Token; import io.teknek.nibiru.coordinator.Coordinator; import io.teknek.nibiru.engine.DefaultColumnFamily; import io.teknek.nibiru.engine.SsTable; import io.teknek.nibiru.engine.SsTableStreamReader; import io.teknek.nibiru.engine.SsTableStreamWriter; import io.teknek.nibiru.engine.atom.AtomKey; import io.teknek.nibiru.engine.atom.AtomValue; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicLong; public class CompactionManager extends AbstractPlugin implements Runnable { public static final String MY_NAME = "compaction_manager"; private AtomicLong numberOfCompactions = new AtomicLong(0); private volatile boolean goOn = true; private Thread thread; private volatile boolean cleanOutOfTokenRange = false; public CompactionManager(Server server){ super(server); } @Override public String getName() { return MY_NAME; } @Override public void init() { thread = new Thread(this); thread.start(); } @Override public void shutdown() { this.setGoOn(false); } @Override public void run() { while (goOn){ for (Entry<String, Keyspace> keyspaces : server.getKeyspaces().entrySet()){ Keyspace keyspace = keyspaces.getValue(); for (Map.Entry<String,Store> columnFamilies : keyspace.getStores().entrySet()){ if (!(columnFamilies.getValue() instanceof DefaultColumnFamily)){ continue; } DefaultColumnFamily defaultColumnFamily = ((DefaultColumnFamily) columnFamilies.getValue()); maxCompactionThresholdCompaction(keyspace, defaultColumnFamily); } } try { Thread.sleep(100L); } catch (InterruptedException e) { } } } public void cleanupCompaction(Keyspace keyspace, DefaultColumnFamily defaultColumnFamily){ Set<SsTable> tables = new TreeSet<>(defaultColumnFamily.getSstable());//duplicate because we will mute the collection for (SsTable table : tables){ String newName = getNewSsTableName(); try { SsTable [] ssArray = {table}; SsTable s = compact(ssArray, newName, server.getServerId(), server.getCoordinator(), true, keyspace); defaultColumnFamily.getSstable().add(s); defaultColumnFamily.getSstable().remove(table); //todo delete old } catch (IOException e) { throw new RuntimeException(e); } } } public void majorCompaction(Keyspace keyspace, DefaultColumnFamily defaultColumnFamily){ Set<SsTable> tables = new TreeSet<>(defaultColumnFamily.getSstable());//duplicate because we will mute the collection SsTable [] ssArray = tables.toArray(new SsTable[] {}); try { String newName = getNewSsTableName(); SsTable s = compact(ssArray, newName, server.getServerId(), server.getCoordinator(), cleanOutOfTokenRange, keyspace); defaultColumnFamily.getSstable().add(s); for (SsTable table : ssArray){ defaultColumnFamily.getSstable().remove(table); } numberOfCompactions.incrementAndGet(); } catch (IOException e) { throw new RuntimeException(e); } } public void maxCompactionThresholdCompaction(Keyspace keyspace, DefaultColumnFamily defaultColumnFamily){ Set<SsTable> tables = defaultColumnFamily.getSstable(); if (tables.size() >= defaultColumnFamily.getStoreMetadata().getMaxCompactionThreshold()){ SsTable [] ssArray = tables.toArray(new SsTable[] {}); try { String newName = getNewSsTableName(); SsTable s = compact(ssArray, newName, server.getServerId(), server.getCoordinator(), cleanOutOfTokenRange, keyspace); tables.add(s); for (SsTable table : ssArray){ tables.remove(table); //TODO table.delete ? } numberOfCompactions.incrementAndGet(); } catch (IOException e) { throw new RuntimeException(e); } } } public static String getNewSsTableName(){ return String.valueOf(System.nanoTime()); } public static SsTable compact(SsTable [] ssTables, String newName, ServerId serverId, Coordinator coordinator, boolean cleanOutOfRange, Keyspace keyspace) throws IOException { DefaultColumnFamily columnFamily = (DefaultColumnFamily) ssTables[0].getColumnFamily(); SsTableStreamReader[] readers = new SsTableStreamReader[ssTables.length]; SsTableStreamWriter newSsTable = new SsTableStreamWriter(newName, columnFamily); newSsTable.open(); Token[] currentTokens = new Token[ssTables.length]; for (int i = 0; i < ssTables.length; i++) { readers[i] = ssTables[i].getStreamReader(); } for (int i = 0; i < currentTokens.length; i++) { currentTokens[i] = readers[i].getNextToken(); } while (!allNull(currentTokens)){ Token lowestToken = lowestToken(currentTokens); SortedMap<AtomKey,AtomValue> allColumns = new TreeMap<>(); for (int i = 0; i < currentTokens.length; i++) { if (currentTokens[i] != null && currentTokens[i].equals(lowestToken)) { SortedMap<AtomKey, AtomValue> columns = readers[i].readColumns(); merge(allColumns, columns); } } if (cleanOutOfRange){ if (coordinator.destinationsForToken(lowestToken, keyspace).contains(coordinator.getDestinationLocal())){ newSsTable.write(lowestToken, allColumns); } } else { newSsTable.write(lowestToken, allColumns); } advance(lowestToken, readers, currentTokens); } newSsTable.close(); SsTable s = new SsTable(columnFamily); s.open(newName, columnFamily.getKeyspace().getConfiguration()); return s; } private static void advance(Token lowestToken, SsTableStreamReader[] r, Token[] t) throws IOException{ for (int i = 0; i < t.length; i++) { if (t[i] != null && t[i].getToken().equals(lowestToken.getToken())){ t[i] = r[i].getNextToken(); } } } private static void merge(SortedMap<AtomKey,AtomValue> allColumns, SortedMap<AtomKey,AtomValue> otherColumns){ //TODO better compare rulese for (Map.Entry<AtomKey,AtomValue> column: otherColumns.entrySet()){ AtomValue existing = allColumns.get(column.getKey()); if (existing == null) { allColumns.put(column.getKey(), column.getValue()); } else if (existing.getTime() < column.getValue().getTime()){ allColumns.put(column.getKey(), column.getValue()); } // we should handle the equal/tombstone case here } } private static Token lowestToken(Token [] t){ Token lowestToken = null; for (Token j: t){ if (lowestToken == null){ lowestToken = j; } else { if (j.compareTo(lowestToken) == -1) { lowestToken = j; } } } return lowestToken; } private static boolean allNull(Token[] t){ for (Token j : t){ if (j != null){ return false; } } return true; } public long getNumberOfCompactions() { return numberOfCompactions.get(); } public boolean isGoOn() { return goOn; } public void setGoOn(boolean goOn) { this.goOn = goOn; } }