/** * Copyright 2016 LinkedIn Corp. All rights reserved. * * 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. */ package com.github.ambry.store; import com.github.ambry.utils.Time; import com.github.ambry.utils.Utils; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; /** * Represents the blob value stored in the index for a key. * * Version_0 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * | Blob Size | Offset | Flags | Expiration Time | Orig msg | * | (8 bytes) | (8 bytes) | (1 byte)| in Ms | offset | * | | | | ( 8 bytes) | (8 bytes) | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * * Version_1 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * | Blob Size | Offset | Flags | Expiration Time | Orig msg | OperationTime | ServiceId | ContainerId | * | (8 bytes) | (8 bytes) | (1 byte)| in Secs | offset | in secs | (2 bytes) | (2 bytes) | * | | | | ( 4 bytes) | (8 bytes) | (4 bytes) | | | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * */ class IndexValue { enum Flags { Delete_Index } final static byte FLAGS_DEFAULT_VALUE = (byte) 0; final static short SERVICE_CONTAINER_ID_DEFAULT_VALUE = -1; final static long UNKNOWN_ORIGINAL_MESSAGE_OFFSET = -1; private final static int BLOB_SIZE_IN_BYTES = 8; private final static int OFFSET_SIZE_IN_BYTES = 8; private final static int FLAG_SIZE_IN_BYTES = 1; private final static int EXPIRES_AT_MS_SIZE_IN_BYTES_V0 = 8; private final static int ORIGINAL_MESSAGE_OFFSET_SIZE_IN_BYTES = 8; private final static int EXPIRES_AT_SECS_SIZE_IN_BYTES_V1 = 4; private final static int OPERATION_TIME_SECS_SIZE_IN_BYTES = 4; private final static int SERVICE_ID_SIZE_IN_BYTES = 2; private final static int CONTAINER_ID_SIZE_IN_BYTES = 2; final static int INDEX_VALUE_SIZE_IN_BYTES_V0 = BLOB_SIZE_IN_BYTES + OFFSET_SIZE_IN_BYTES + FLAG_SIZE_IN_BYTES + EXPIRES_AT_MS_SIZE_IN_BYTES_V0 + ORIGINAL_MESSAGE_OFFSET_SIZE_IN_BYTES; final static int INDEX_VALUE_SIZE_IN_BYTES_V1 = BLOB_SIZE_IN_BYTES + OFFSET_SIZE_IN_BYTES + FLAG_SIZE_IN_BYTES + EXPIRES_AT_SECS_SIZE_IN_BYTES_V1 + ORIGINAL_MESSAGE_OFFSET_SIZE_IN_BYTES + OPERATION_TIME_SECS_SIZE_IN_BYTES + SERVICE_ID_SIZE_IN_BYTES + CONTAINER_ID_SIZE_IN_BYTES; private long size; private Offset offset; private byte flags; private final long expiresAtMs; private long originalMessageOffset; private final long operationTimeInMs; private final short serviceId; private final short containerId; private final short version; /** * Constructs the {@link IndexValue} with the passed in {@link ByteBuffer} and the given {@code version} * @param logSegmentName the log segment name to be used to construct the offset * @param value the {@link ByteBuffer} representation of the {@link IndexValue} * @param version the version of the {@link PersistentIndex} */ IndexValue(String logSegmentName, ByteBuffer value, short version) { this.version = version; switch (version) { case PersistentIndex.VERSION_0: if (value.capacity() != INDEX_VALUE_SIZE_IN_BYTES_V0) { throw new IllegalArgumentException("Invalid buffer size for version 0"); } size = value.getLong(); offset = new Offset(logSegmentName, value.getLong()); flags = value.get(); expiresAtMs = value.getLong(); originalMessageOffset = value.getLong(); operationTimeInMs = (int) Utils.Infinite_Time; serviceId = SERVICE_CONTAINER_ID_DEFAULT_VALUE; containerId = SERVICE_CONTAINER_ID_DEFAULT_VALUE; break; case PersistentIndex.VERSION_1: if (value.capacity() != INDEX_VALUE_SIZE_IN_BYTES_V1) { throw new IllegalArgumentException("Invalid buffer size for version 1"); } size = value.getLong(); offset = new Offset(logSegmentName, value.getLong()); flags = value.get(); long expiresAt = value.getInt(); expiresAtMs = expiresAt >= 0 ? TimeUnit.SECONDS.toMillis(expiresAt) : Utils.Infinite_Time; originalMessageOffset = value.getLong(); long operationTimeInSecs = value.getInt(); operationTimeInMs = operationTimeInSecs != Utils.Infinite_Time ? TimeUnit.SECONDS.toMillis(operationTimeInSecs) : Utils.Infinite_Time; serviceId = value.getShort(); containerId = value.getShort(); break; default: throw new IllegalArgumentException("Unsupported version " + version + " passed in for IndexValue "); } } /** * Constructs IndexValue based on the args passed * @param size the size of the blob that this index value refers to * @param offset the {@link Offset} in the {@link Log} where the blob that this index value refers to resides * @param expiresAtMs the expiration time in ms at which the blob expires */ IndexValue(long size, Offset offset, long expiresAtMs) { this(size, offset, FLAGS_DEFAULT_VALUE, expiresAtMs, offset.getOffset(), (int) Utils.Infinite_Time, SERVICE_CONTAINER_ID_DEFAULT_VALUE, SERVICE_CONTAINER_ID_DEFAULT_VALUE); } /** * Constructs IndexValue based on the args passed * @param size the size of the blob that this index value refers to * @param offset the {@link Offset} in the {@link Log} where the blob that this index value refers to resides * @param flags the {@link Flags} that needs to be set for the Index Value * @param expiresAtMs the expiration time in ms at which the blob expires * @param operationTimeInMs operation time ins ms of the entry in secs */ IndexValue(long size, Offset offset, byte flags, long expiresAtMs, long operationTimeInMs) { this(size, offset, flags, expiresAtMs, offset.getOffset(), operationTimeInMs, SERVICE_CONTAINER_ID_DEFAULT_VALUE, SERVICE_CONTAINER_ID_DEFAULT_VALUE); } /** * Constructs IndexValue based on the args passed * @param size the size of the blob that this index value refers to * @param offset the {@link Offset} in the {@link Log} where the blob that this index value refers to resides * @param expiresAtMs the expiration time in ms at which the blob expires * @param operationTimeInMs operation time in ms of the entry * @param serviceId the serviceId that this blob belongs to * @param containerId the containerId that this blob belongs to */ IndexValue(long size, Offset offset, long expiresAtMs, long operationTimeInMs, short serviceId, short containerId) { this(size, offset, FLAGS_DEFAULT_VALUE, expiresAtMs, offset.getOffset(), operationTimeInMs, serviceId, containerId); } /** * Constructs IndexValue based on the args passed * @param size the size of the blob that this index value refers to * @param offset the {@link Offset} in the {@link Log} where the blob that this index value refers to resides * @param flags the {@link Flags} that needs to be set for the Index Value * @param expiresAtMs the expiration time in ms at which the blob expires * @param operationTimeInMs operation time in ms of the entry * @param serviceId the serviceId that this blob belongs to * @param containerId the containerId that this blob belongs to */ IndexValue(long size, Offset offset, byte flags, long expiresAtMs, long operationTimeInMs, short serviceId, short containerId) { this(size, offset, flags, expiresAtMs, offset.getOffset(), operationTimeInMs, serviceId, containerId); } /** * Constructs IndexValue based on the args passed * @param size the size of the blob that this index value refers to * @param offset the {@link Offset} in the {@link Log} where the blob that this index value refers to resides * @param flags the flags that needs to be set for the Index Value * @param expiresAtMs the expiration time in ms at which the blob expires * @param originalMessageOffset the original message offset where the Put record pertaining to a delete record exists * in the same log segment. Set to {@link #UNKNOWN_ORIGINAL_MESSAGE_OFFSET} otherwise. * @param operationTimeInMs the time in ms at which the operation occurred. * @param serviceId the serviceId that this blob belongs to * @param containerId the containerId that this blob belongs to */ private IndexValue(long size, Offset offset, byte flags, long expiresAtMs, long originalMessageOffset, long operationTimeInMs, short serviceId, short containerId) { this.size = size; this.offset = offset; this.flags = flags; // if expiry in secs > Integer.MAX_VALUE, treat it as permanent blob if (TimeUnit.MILLISECONDS.toSeconds(expiresAtMs) > Integer.MAX_VALUE) { this.expiresAtMs = Utils.Infinite_Time; } else { this.expiresAtMs = Utils.getTimeInMsToTheNearestSec(expiresAtMs); } this.originalMessageOffset = originalMessageOffset; this.operationTimeInMs = Utils.getTimeInMsToTheNearestSec(operationTimeInMs); this.serviceId = serviceId; this.containerId = containerId; version = PersistentIndex.CURRENT_VERSION; } /** * @return the size of the blob that this index value refers to */ long getSize() { return size; } /** * @return the {@link Offset} in the {@link Log} where the blob that this index value refers to resides */ Offset getOffset() { return offset; } /** * @return the {@link Flags} in the Index Value */ byte getFlags() { return flags; } /** * Returns whether the passed in {@code flag} is set or not in the {@link IndexValue} * @param flag the {@link Flags} that needs to be checked if set in the {@link IndexValue} * @return {@code true} if the passed in {@link Flags} is set, {@code false} otherwise */ boolean isFlagSet(Flags flag) { return ((getFlags() & (1 << flag.ordinal())) != 0); } /** * @return the expiration time of the index value in ms */ long getExpiresAtMs() { return expiresAtMs; } /** * @return the original message offset of the {@link IndexValue} */ long getOriginalMessageOffset() { return originalMessageOffset; } /** * @return the operation time in ms of the index value */ long getOperationTimeInMs() { return operationTimeInMs; } /** * @return the serviceId of the {@link IndexValue} */ short getServiceId() { return serviceId; } /** * @return the containerId of the {@link IndexValue} */ short getContainerId() { return containerId; } /** * Sets the {@link Flags} for the {@link IndexValue} * @param flag the {@link Flags} that needs to be set in the {@link IndexValue} */ void setFlag(Flags flag) { flags = (byte) (flags | (1 << flag.ordinal())); } /** * Updates the {@link Offset} of the {@link IndexValue} * @param newOffset the new {@link Offset} to be updated for the {@link IndexValue} */ void setNewOffset(Offset newOffset) { originalMessageOffset = offset.getName().equals(newOffset.getName()) ? offset.getOffset() : UNKNOWN_ORIGINAL_MESSAGE_OFFSET; offset = newOffset; } void clearOriginalMessageOffset() { originalMessageOffset = UNKNOWN_ORIGINAL_MESSAGE_OFFSET; } /** * Sets the size of the {@link IndexValue} * @param size the size that needs to be set for the {@link IndexValue} */ void setNewSize(long size) { this.size = size; } /** * @return the {@link ByteBuffer} representation of this {@link IndexValue} */ ByteBuffer getBytes() { ByteBuffer value; switch (version) { case PersistentIndex.VERSION_0: value = ByteBuffer.allocate(INDEX_VALUE_SIZE_IN_BYTES_V0); value.putLong(size); value.putLong(offset.getOffset()); value.put(flags); value.putLong(expiresAtMs); value.putLong(originalMessageOffset); value.position(0); break; case PersistentIndex.VERSION_1: value = ByteBuffer.allocate(INDEX_VALUE_SIZE_IN_BYTES_V1); value.putLong(size); value.putLong(offset.getOffset()); value.put(flags); value.putInt(expiresAtMs != Utils.Infinite_Time ? (int) (expiresAtMs / Time.MsPerSec) : (int) expiresAtMs); value.putLong(originalMessageOffset); value.putInt(operationTimeInMs != Utils.Infinite_Time ? (int) (operationTimeInMs / Time.MsPerSec) : (int) operationTimeInMs); value.putShort(serviceId); value.putShort(containerId); value.position(0); break; default: throw new IllegalArgumentException("Unsupported version " + version + " for IndexValue "); } return value; } @Override public String toString() { return "Offset: " + offset + ", Size: " + getSize() + ", Deleted: " + isFlagSet(Flags.Delete_Index) + ", ExpiresAtMs: " + getExpiresAtMs() + ", Original Message Offset: " + getOriginalMessageOffset() + ( version == PersistentIndex.VERSION_1 ? (", OperationTimeAtSecs " + getOperationTimeInMs() + ", ServiceId " + getServiceId() + ", ContainerId " + getContainerId()) : ""); } }