blob: 37dd34e92185b1a7afb560f55bb57dc684b4d28a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.db.rows;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import org.apache.cassandra.cache.IMeasurableMemory;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.partitions.PartitionStatisticsCollector;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ObjectSizes;
/**
* Stats used for the encoding of the rows and tombstones of a given source.
* <p>
* Those stats are used to optimize the on-wire and on-disk storage of rows. More precisely,
* the {@code minTimestamp}, {@code minLocalDeletionTime} and {@code minTTL} stats are used to
* delta-encode those information for the sake of vint encoding.
* <p>
* Note that due to their use, those stats can suffer to be somewhat inaccurate (the more incurrate
* they are, the less effective the storage will be, but provided the stats are not completly wacky,
* this shouldn't have too huge an impact on performance) and in fact they will not always be
* accurate for reasons explained in {@link SerializationHeader#make}.
*/
public class EncodingStats implements IMeasurableMemory
{
// Default values for the timestamp, deletion time and ttl. We use this both for NO_STATS, but also to serialize
// an EncodingStats. Basically, we encode the diff of each value of to these epoch, which give values with better vint encoding.
public static final long TIMESTAMP_EPOCH;
private static final int DELETION_TIME_EPOCH;
private static final int TTL_EPOCH = 0;
static
{
// We want a fixed epoch, but that provide small values when substracted from our timestamp and deletion time.
// So we somewhat arbitrary use the date of the summit 2015, which should hopefully roughly correspond to 3.0 release.
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT-0"), Locale.US);
c.set(Calendar.YEAR, 2015);
c.set(Calendar.MONTH, Calendar.SEPTEMBER);
c.set(Calendar.DAY_OF_MONTH, 22);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
TIMESTAMP_EPOCH = c.getTimeInMillis() * 1000; // timestamps should be in microseconds by convention
DELETION_TIME_EPOCH = (int)(c.getTimeInMillis() / 1000); // local deletion times are in seconds
}
// We should use this sparingly obviously
public static final EncodingStats NO_STATS = new EncodingStats(TIMESTAMP_EPOCH, DELETION_TIME_EPOCH, TTL_EPOCH);
public static long HEAP_SIZE = ObjectSizes.measure(NO_STATS);
public static final Serializer serializer = new Serializer();
public final long minTimestamp;
public final int minLocalDeletionTime;
public final int minTTL;
public EncodingStats(long minTimestamp,
int minLocalDeletionTime,
int minTTL)
{
// Note that the exact value of those don't impact correctness, just the efficiency of the encoding. So when we
// get a value for timestamp (resp. minLocalDeletionTime) that means 'no object had a timestamp' (resp. 'a local
// deletion time'), then what value we store for minTimestamp (resp. minLocalDeletionTime) doesn't matter, and
// it's thus more efficient to use our EPOCH numbers, since it will result in a guaranteed 1 byte encoding.
this.minTimestamp = minTimestamp == LivenessInfo.NO_TIMESTAMP ? TIMESTAMP_EPOCH : minTimestamp;
this.minLocalDeletionTime = minLocalDeletionTime == LivenessInfo.NO_EXPIRATION_TIME ? DELETION_TIME_EPOCH : minLocalDeletionTime;
this.minTTL = minTTL;
}
/**
* Merge this stats with another one.
* <p>
* The comments of {@link SerializationHeader#make} applies here too, i.e. the result of
* merging will be not totally accurate but we can live with that.
*/
public EncodingStats mergeWith(EncodingStats that)
{
long minTimestamp = this.minTimestamp == TIMESTAMP_EPOCH
? that.minTimestamp
: (that.minTimestamp == TIMESTAMP_EPOCH ? this.minTimestamp : Math.min(this.minTimestamp, that.minTimestamp));
int minDelTime = this.minLocalDeletionTime == DELETION_TIME_EPOCH
? that.minLocalDeletionTime
: (that.minLocalDeletionTime == DELETION_TIME_EPOCH ? this.minLocalDeletionTime : Math.min(this.minLocalDeletionTime, that.minLocalDeletionTime));
int minTTL = this.minTTL == TTL_EPOCH
? that.minTTL
: (that.minTTL == TTL_EPOCH ? this.minTTL : Math.min(this.minTTL, that.minTTL));
return new EncodingStats(minTimestamp, minDelTime, minTTL);
}
/**
* Merge one or more EncodingStats, that are lazily materialized from some list of arbitrary type by the provided function
*/
public static <V, F extends Function<V, EncodingStats>> EncodingStats merge(List<V> values, F function)
{
if (values.size() == 1)
return function.apply(values.get(0));
Collector collector = new Collector();
for (int i=0, isize=values.size(); i<isize; i++)
{
V v = values.get(i);
EncodingStats stats = function.apply(v);
if (stats.minTimestamp != TIMESTAMP_EPOCH)
collector.updateTimestamp(stats.minTimestamp);
if(stats.minLocalDeletionTime != DELETION_TIME_EPOCH)
collector.updateLocalDeletionTime(stats.minLocalDeletionTime);
if(stats.minTTL != TTL_EPOCH)
collector.updateTTL(stats.minTTL);
}
return collector.get();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EncodingStats that = (EncodingStats) o;
return this.minLocalDeletionTime == that.minLocalDeletionTime
&& this.minTTL == that.minTTL
&& this.minTimestamp == that.minTimestamp;
}
@Override
public int hashCode()
{
return Objects.hash(minTimestamp, minLocalDeletionTime, minTTL);
}
public long unsharedHeapSize()
{
if (this == NO_STATS)
return 0;
return HEAP_SIZE;
}
@Override
public String toString()
{
return String.format("EncodingStats(ts=%d, ldt=%d, ttl=%d)", minTimestamp, minLocalDeletionTime, minTTL);
}
public static class Collector implements PartitionStatisticsCollector
{
private boolean isTimestampSet;
private long minTimestamp = Long.MAX_VALUE;
private boolean isDelTimeSet;
private int minDeletionTime = Integer.MAX_VALUE;
private boolean isTTLSet;
private int minTTL = Integer.MAX_VALUE;
public void update(LivenessInfo info)
{
if (info.isEmpty())
return;
updateTimestamp(info.timestamp());
if (info.isExpiring())
{
updateTTL(info.ttl());
updateLocalDeletionTime(info.localExpirationTime());
}
}
public void update(Cell<?> cell)
{
updateTimestamp(cell.timestamp());
if (cell.isExpiring())
{
updateTTL(cell.ttl());
updateLocalDeletionTime(cell.localDeletionTime());
}
else if (cell.isTombstone())
{
updateLocalDeletionTime(cell.localDeletionTime());
}
}
public void update(DeletionTime deletionTime)
{
if (deletionTime.isLive())
return;
updateTimestamp(deletionTime.markedForDeleteAt());
updateLocalDeletionTime(deletionTime.localDeletionTime());
}
public void updateTimestamp(long timestamp)
{
isTimestampSet = true;
minTimestamp = Math.min(minTimestamp, timestamp);
}
public void updateLocalDeletionTime(int deletionTime)
{
isDelTimeSet = true;
minDeletionTime = Math.min(minDeletionTime, deletionTime);
}
public void updateTTL(int ttl)
{
isTTLSet = true;
minTTL = Math.min(minTTL, ttl);
}
public void updateColumnSetPerRow(long columnSetInRow)
{
}
public void updateHasLegacyCounterShards(boolean hasLegacyCounterShards)
{
// We don't care about this but this come with PartitionStatisticsCollector
}
public EncodingStats get()
{
return new EncodingStats(isTimestampSet ? minTimestamp : TIMESTAMP_EPOCH,
isDelTimeSet ? minDeletionTime : DELETION_TIME_EPOCH,
isTTLSet ? minTTL : TTL_EPOCH);
}
public static EncodingStats collect(Row staticRow, Iterator<Row> rows, DeletionInfo deletionInfo)
{
Collector collector = new Collector();
deletionInfo.collectStats(collector);
if (!staticRow.isEmpty())
Rows.collectStats(staticRow, collector);
while (rows.hasNext())
Rows.collectStats(rows.next(), collector);
return collector.get();
}
}
public static class Serializer
{
public void serialize(EncodingStats stats, DataOutputPlus out) throws IOException
{
out.writeUnsignedVInt(stats.minTimestamp - TIMESTAMP_EPOCH);
out.writeUnsignedVInt(stats.minLocalDeletionTime - DELETION_TIME_EPOCH);
out.writeUnsignedVInt(stats.minTTL - TTL_EPOCH);
}
public int serializedSize(EncodingStats stats)
{
return TypeSizes.sizeofUnsignedVInt(stats.minTimestamp - TIMESTAMP_EPOCH)
+ TypeSizes.sizeofUnsignedVInt(stats.minLocalDeletionTime - DELETION_TIME_EPOCH)
+ TypeSizes.sizeofUnsignedVInt(stats.minTTL - TTL_EPOCH);
}
public EncodingStats deserialize(DataInputPlus in) throws IOException
{
long minTimestamp = in.readUnsignedVInt() + TIMESTAMP_EPOCH;
int minLocalDeletionTime = (int)in.readUnsignedVInt() + DELETION_TIME_EPOCH;
int minTTL = (int)in.readUnsignedVInt() + TTL_EPOCH;
return new EncodingStats(minTimestamp, minLocalDeletionTime, minTTL);
}
}
}