package io.teknek.nibiru.engine; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.SortedMap; import java.util.TreeMap; import java.util.Map.Entry; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import io.teknek.nibiru.Store; import io.teknek.nibiru.Token; import io.teknek.nibiru.engine.atom.AtomKey; import io.teknek.nibiru.engine.atom.AtomValue; import io.teknek.nibiru.engine.atom.ColumnKey; import io.teknek.nibiru.engine.atom.ColumnValue; import io.teknek.nibiru.engine.atom.RowTombstoneKey; import io.teknek.nibiru.engine.atom.TombstoneValue; public class VersionedMemtable extends AbstractMemtable { private ConcurrentSkipListMap<Token, ConcurrentSkipListMap<AtomKey,ConcurrentLinkedQueue<AtomValue>>> data; public VersionedMemtable(Store columnFamily, CommitLog commitLog) { super(columnFamily, commitLog); data = new ConcurrentSkipListMap<>(); } @Override public int size() { return data.size(); } @Override public void put(Token row, String column, String value, long stamp, long ttl) { ColumnValue v = new ColumnValue(value, stamp, timeSource.getTimeInMillis(), ttl); ColumnKey k = new ColumnKey(column); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> columns = new ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>>(); ConcurrentLinkedQueue<AtomValue> i = new ConcurrentLinkedQueue<AtomValue>(); i.add(v); columns.put(k, i); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> rowAlreadyExisted = data.putIfAbsent(row, columns); if (rowAlreadyExisted != null){ ConcurrentLinkedQueue<AtomValue> columnAlreadyExisted = rowAlreadyExisted.putIfAbsent(k, i); if (columnAlreadyExisted != null){ columnAlreadyExisted.add(v); optimize(columnAlreadyExisted); } } } private void optimize(ConcurrentLinkedQueue<AtomValue> columnAlreadyExisted){ if (columnAlreadyExisted.size()==1) return ; long oldest = Long.MIN_VALUE; for (AtomValue val : columnAlreadyExisted){ if (val.getTime() > oldest){ oldest = val.getTime(); } } Iterator<AtomValue> it = columnAlreadyExisted.iterator(); while (it.hasNext()){ AtomValue v = it.next(); if (v.getTime() < oldest){ it.remove(); } //TODO we can remove values shadowed by tombstones } } @Override public AtomValue get(Token row, String column) { ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> foundRow = data.get(row); if (foundRow == null){ return null; } ConcurrentLinkedQueue<AtomValue> findTomb = foundRow.get(new RowTombstoneKey()); if (findTomb == null){ ConcurrentLinkedQueue<AtomValue> i = foundRow.get(new ColumnKey(column)); return highest(i); } else { TombstoneValue v = highestTombstone(findTomb); ConcurrentLinkedQueue<AtomValue> i = foundRow.get(new ColumnKey(column)); if (i == null){ return v; } AtomValue k = highest(i); if (v.getTime() >= k.getTime()){ return v; } else { return k; } } } private TombstoneValue highestTombstone(ConcurrentLinkedQueue<AtomValue> tombstoneRow){ long highest = Long.MIN_VALUE; TombstoneValue t = null; for (AtomValue v : tombstoneRow){ if (v.getTime() > highest){ highest = v.getTime(); t = (TombstoneValue) v; } } return t; } public static AtomValue highest(Iterator<AtomValue> columns){ Queue<AtomValue> q = new ArrayDeque<AtomValue>(); while (columns.hasNext()){ q.add(columns.next()); } return highest(q); } public static AtomValue highest(Queue<AtomValue> columns){ AtomValue head = columns.peek(); if (columns.size() == 1){ //optimization return head; } long oldest = Long.MIN_VALUE; List<AtomValue> oldestList = new ArrayList<>(2); for (AtomValue val : columns){ if (val.getTime() > oldest){ oldest = val.getTime(); oldestList.clear(); oldestList.add(val); } else if (val.getTime() == oldest){ oldestList.add(val); } } if (oldestList.size() == 1){ //optimization return oldestList.get(0); } ColumnValue highest = null; for (AtomValue v : oldestList){ if (v instanceof TombstoneValue){ return v; } else if (v instanceof ColumnValue){ ColumnValue v1 = (ColumnValue) v; if (highest == null){ highest = v1; } else if (highest.getValue().compareTo(v1.getValue())>0){ highest = v1; } } } return highest; } @Override public SortedMap<AtomKey, AtomValue> slice(Token rowkey, String start, String end) { SortedMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> row = data.get(rowkey); if (row == null){ return new TreeMap<AtomKey,AtomValue>(); } ConcurrentLinkedQueue<AtomValue> tombstoneList = row.get(new RowTombstoneKey()); TombstoneValue tomb = null; if (tombstoneList != null){ tomb = highestTombstone(tombstoneList); } ConcurrentNavigableMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> i = data.get(rowkey).subMap(new ColumnKey(start), new ColumnKey(end)); SortedMap<AtomKey,AtomValue> results = new TreeMap<>(); for (Entry<AtomKey, ConcurrentLinkedQueue<AtomValue>> j : i.entrySet()) { AtomValue v = highest(j.getValue()); if (! (v instanceof TombstoneValue)){ if (tomb == null){ results.put(j.getKey(), v); } else { if (tomb.getTime()< v.getTime()){ results.put(j.getKey(), v); } } } } return results; } @Override public void delete(Token row, long time) { TombstoneValue v = new TombstoneValue(time); RowTombstoneKey k = new RowTombstoneKey(); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> columns = new ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>>(); ConcurrentLinkedQueue<AtomValue> i = new ConcurrentLinkedQueue<AtomValue>(); i.add(v); columns.put(k, i); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> rowAlreadyExisted = data.putIfAbsent(row, columns); if (rowAlreadyExisted != null){ ConcurrentLinkedQueue<AtomValue> columnAlreadyExisted = rowAlreadyExisted.putIfAbsent(k, i); if (columnAlreadyExisted != null){ columnAlreadyExisted.add(v); } } } @Override public void delete(Token row, String column, long time) { TombstoneValue v = new TombstoneValue(time); ColumnKey k = new ColumnKey(column); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> columns = new ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>>(); ConcurrentLinkedQueue<AtomValue> i = new ConcurrentLinkedQueue<AtomValue>(); i.add(v); columns.put(k, i); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> rowAlreadyExisted = data.putIfAbsent(row, columns); if (rowAlreadyExisted != null){ ConcurrentLinkedQueue<AtomValue> columnAlreadyExisted = rowAlreadyExisted.putIfAbsent(k, i); if (columnAlreadyExisted != null){ columnAlreadyExisted.add(v); } } } @Override public Iterator<MemtablePair<Token, Map<AtomKey, Iterator<AtomValue>>>> getDataIterator() { final Iterator<Entry<Token, ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>>>> i = data.entrySet().iterator(); return new Iterator<MemtablePair<Token, Map<AtomKey, Iterator<AtomValue>>>>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public MemtablePair<Token, Map<AtomKey, Iterator<AtomValue>>> next() { Entry<Token, ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>>> b = i.next(); ConcurrentSkipListMap<AtomKey, ConcurrentLinkedQueue<AtomValue>> l = b.getValue(); SortedMap<AtomKey, Iterator<AtomValue>> m = new TreeMap<AtomKey, Iterator<AtomValue>>(); for (Entry<AtomKey, ConcurrentLinkedQueue<AtomValue>> x : l.entrySet()){ m.put(x.getKey(), x.getValue().iterator()); } return new MemtablePair<Token, Map<AtomKey, Iterator<AtomValue>>> (b.getKey(), m); } @Override public void remove() { } }; } }