/**
* 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.hadoop.hive.llap.io.metadata;
import org.apache.hadoop.hive.llap.cache.LlapCacheableBuffer;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hadoop.hive.common.io.DiskRange;
import org.apache.hadoop.hive.common.io.DiskRangeList;
import org.apache.hadoop.hive.common.io.DataCache.BooleanRef;
import org.apache.hadoop.hive.common.io.DataCache.DiskRangeListFactory;
import org.apache.hadoop.hive.llap.cache.LlapOomDebugDump;
import org.apache.hadoop.hive.llap.cache.LowLevelCachePolicy;
import org.apache.hadoop.hive.llap.cache.MemoryManager;
import org.apache.hadoop.hive.llap.cache.LowLevelCache.Priority;
import org.apache.hadoop.hive.ql.io.orc.encoded.OrcBatchKey;
public class OrcMetadataCache implements LlapOomDebugDump {
private final ConcurrentHashMap<Object, OrcFileMetadata> metadata = new ConcurrentHashMap<>();
private final ConcurrentHashMap<OrcBatchKey, OrcStripeMetadata> stripeMetadata =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<Object, OrcFileEstimateErrors> estimateErrors;
private final MemoryManager memoryManager;
private final LowLevelCachePolicy policy;
public OrcMetadataCache(MemoryManager memoryManager, LowLevelCachePolicy policy,
boolean useEstimateCache) {
this.memoryManager = memoryManager;
this.policy = policy;
this.estimateErrors = useEstimateCache
? new ConcurrentHashMap<Object, OrcFileEstimateErrors>() : null;
}
public OrcFileMetadata putFileMetadata(OrcFileMetadata metaData) {
long memUsage = metaData.getMemoryUsage();
memoryManager.reserveMemory(memUsage);
OrcFileMetadata val = metadata.putIfAbsent(metaData.getFileKey(), metaData);
// See OrcFileMetadata; it is always unlocked, so we just "touch" it here to simulate use.
return touchOnPut(metaData, val, memUsage);
}
public OrcStripeMetadata putStripeMetadata(OrcStripeMetadata metaData) {
long memUsage = metaData.getMemoryUsage();
memoryManager.reserveMemory(memUsage);
OrcStripeMetadata val = stripeMetadata.putIfAbsent(metaData.getKey(), metaData);
// See OrcStripeMetadata; it is always unlocked, so we just "touch" it here to simulate use.
return touchOnPut(metaData, val, memUsage);
}
private <T extends LlapCacheableBuffer> T touchOnPut(T newVal, T oldVal, long memUsage) {
if (oldVal == null) {
oldVal = newVal;
policy.cache(oldVal, Priority.HIGH);
} else {
memoryManager.releaseMemory(memUsage);
policy.notifyLock(oldVal);
}
policy.notifyUnlock(oldVal);
return oldVal;
}
public void putIncompleteCbs(Object fileKey, DiskRange[] ranges, long baseOffset) {
if (estimateErrors == null) return;
OrcFileEstimateErrors errorData = estimateErrors.get(fileKey);
boolean isNew = false;
// We should technically update memory usage if updating the old object, but we don't do it
// for now; there is no mechanism to properly notify the cache policy/etc. wrt parallel evicts.
if (errorData == null) {
errorData = new OrcFileEstimateErrors(fileKey);
for (DiskRange range : ranges) {
errorData.addError(range.getOffset(), range.getLength(), baseOffset);
}
long memUsage = errorData.estimateMemoryUsage();
memoryManager.reserveMemory(memUsage);
OrcFileEstimateErrors old = estimateErrors.putIfAbsent(fileKey, errorData);
if (old != null) {
errorData = old;
memoryManager.releaseMemory(memUsage);
policy.notifyLock(errorData);
} else {
isNew = true;
policy.cache(errorData, Priority.NORMAL);
}
}
if (!isNew) {
for (DiskRange range : ranges) {
errorData.addError(range.getOffset(), range.getLength(), baseOffset);
}
}
policy.notifyUnlock(errorData);
}
public OrcStripeMetadata getStripeMetadata(OrcBatchKey stripeKey) throws IOException {
return touchOnGet(stripeMetadata.get(stripeKey));
}
public OrcFileMetadata getFileMetadata(Object fileKey) throws IOException {
return touchOnGet(metadata.get(fileKey));
}
private <T extends LlapCacheableBuffer> T touchOnGet(T result) {
if (result != null) {
policy.notifyLock(result);
policy.notifyUnlock(result); // Never locked for eviction; Java object.
}
return result;
}
public DiskRangeList getIncompleteCbs(Object fileKey, DiskRangeList ranges, long baseOffset,
DiskRangeListFactory factory, BooleanRef gotAllData) {
if (estimateErrors == null) return ranges;
OrcFileEstimateErrors errors = estimateErrors.get(fileKey);
if (errors == null) return ranges;
return errors.getIncompleteCbs(ranges, baseOffset, factory, gotAllData);
}
public void notifyEvicted(OrcFileMetadata buffer) {
metadata.remove(buffer.getFileKey());
// See OrcFileMetadata - we don't clear the object, it will be GCed when released by users.
}
public void notifyEvicted(OrcStripeMetadata buffer) {
stripeMetadata.remove(buffer.getKey());
// See OrcStripeMetadata - we don't clear the object, it will be GCed when released by users.
}
public void notifyEvicted(OrcFileEstimateErrors buffer) {
estimateErrors.remove(buffer.getFileKey());
}
@Override
public String debugDumpForOom() {
StringBuilder sb = new StringBuilder();
debugDumpShort(sb);
return sb.toString();
}
@Override
public void debugDumpShort(StringBuilder sb) {
sb.append("\nORC metadata cache state: ").append(metadata.size()).append(" files, ")
.append(stripeMetadata.size()).append(" stripes, ").append(estimateErrors.size())
.append(" files w/ORC estimate");
}
}