blob: 1e4eee0d66de6d001afdf41abaf24fabaed0f6f8 [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.hadoop.hdds.scm.container.states;
import com.google.common.base.Preconditions;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.exceptions.SCMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Map;
import java.util.NavigableSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import static org.apache.hadoop.hdds.scm.exceptions.SCMException.ResultCodes.FAILED_TO_CHANGE_CONTAINER_STATE;
/**
* Each Attribute that we manage for a container is maintained as a map.
* <p>
* Currently we manage the following attributes for a container.
* <p>
* 1. StateMap - LifeCycleState -> Set of ContainerIDs
* 2. TypeMap - ReplicationType -> Set of ContainerIDs
* 3. OwnerMap - OwnerNames -> Set of ContainerIDs
* 4. FactorMap - ReplicationFactor -> Set of ContainerIDs
* <p>
* This means that for a cluster size of 750 PB -- we will have around 150
* Million containers, if we assume 5GB average container size.
* <p>
* That implies that these maps will take around 2/3 GB of RAM which will be
* pinned down in the SCM. This is deemed acceptable since we can tune the
* container size --say we make it 10GB average size, then we can deal with a
* cluster size of 1.5 exa bytes with the same metadata in SCMs memory.
* <p>
* Please note: **This class is not thread safe**. This used to be thread safe,
* while bench marking we found that ContainerStateMap would be taking 5
* locks for a single container insert. If we remove locks in this class,
* then we are able to perform about 540K operations per second, with the
* locks in this class it goes down to 246K operations per second. Hence we
* are going to rely on ContainerStateMap locks to maintain consistency of
* data in these classes too, since ContainerAttribute is only used by
* ContainerStateMap class.
*/
public class ContainerAttribute<T> {
private static final Logger LOG =
LoggerFactory.getLogger(ContainerAttribute.class);
private final Map<T, NavigableSet<ContainerID>> attributeMap;
private static final NavigableSet<ContainerID> EMPTY_SET = Collections
.unmodifiableNavigableSet(new ConcurrentSkipListSet<>());
/**
* Creates a Container Attribute map from an existing Map.
*
* @param attributeMap - AttributeMap
*/
public ContainerAttribute(Map<T, NavigableSet<ContainerID>> attributeMap) {
this.attributeMap = attributeMap;
}
/**
* Create an empty Container Attribute map.
*/
public ContainerAttribute() {
this.attributeMap = new ConcurrentHashMap<>();
}
/**
* Insert the value in the Attribute map, keep the original value if it exists
* already.
*
* @param key - The key to the set where the ContainerID should exist.
* @param value - Actual Container ID.
* @throws SCMException - on Error
*/
public boolean insert(T key, ContainerID value) throws SCMException {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(value);
attributeMap.computeIfAbsent(key, any ->
new ConcurrentSkipListSet<>()).add(value);
return true;
}
/**
* Returns true if have this bucket in the attribute map.
*
* @param key - Key to lookup
* @return true if we have the key
*/
public boolean hasKey(T key) {
Preconditions.checkNotNull(key);
return this.attributeMap.containsKey(key);
}
/**
* Returns true if we have the key and the containerID in the bucket.
*
* @param key - Key to the bucket
* @param id - container ID that we want to lookup
* @return true or false
*/
public boolean hasContainerID(T key, ContainerID id) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(id);
return this.attributeMap.containsKey(key) &&
this.attributeMap.get(key).contains(id);
}
/**
* Returns true if we have the key and the containerID in the bucket.
*
* @param key - Key to the bucket
* @param id - container ID that we want to lookup
* @return true or false
*/
public boolean hasContainerID(T key, int id) {
return hasContainerID(key, ContainerID.valueOf(id));
}
/**
* Clears all entries for this key type.
*
* @param key - Key that identifies the Set.
*/
public void clearSet(T key) {
Preconditions.checkNotNull(key);
if (attributeMap.containsKey(key)) {
attributeMap.get(key).clear();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("key: {} does not exist in the attributeMap", key);
}
}
}
/**
* Removes a container ID from the set pointed by the key.
*
* @param key - key to identify the set.
* @param value - Container ID
*/
public boolean remove(T key, ContainerID value) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(value);
if (attributeMap.containsKey(key)) {
if (!attributeMap.get(key).remove(value)) {
if (LOG.isDebugEnabled()) {
LOG.debug("ContainerID: {} does not exist in the set pointed by " +
"key:{}", value, key);
}
return false;
}
return true;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("key: {} does not exist in the attributeMap", key);
}
return false;
}
}
/**
* Returns the collection that maps to the given key.
*
* @param key - Key to the bucket.
* @return Underlying Set in immutable form.
*/
public NavigableSet<ContainerID> getCollection(T key) {
Preconditions.checkNotNull(key);
if (this.attributeMap.containsKey(key)) {
return Collections.unmodifiableNavigableSet(this.attributeMap.get(key));
}
if (LOG.isDebugEnabled()) {
LOG.debug("No such Key. Key {}", key);
}
return EMPTY_SET;
}
/**
* Moves a ContainerID from one bucket to another.
*
* @param currentKey - Current Key
* @param newKey - newKey
* @param value - ContainerID
* @throws SCMException on Error
*/
public void update(T currentKey, T newKey, ContainerID value)
throws SCMException {
Preconditions.checkNotNull(currentKey);
Preconditions.checkNotNull(newKey);
// Return if container attribute not changed
if (currentKey == newKey) {
return;
}
boolean removed = false;
try {
removed = remove(currentKey, value);
if (!removed) {
throw new SCMException("Unable to find key in the current key bucket",
FAILED_TO_CHANGE_CONTAINER_STATE);
}
insert(newKey, value);
} catch (SCMException ex) {
// if we removed the key, insert it back to original bucket, since the
// next insert failed.
LOG.error("error in update.", ex);
if (removed) {
insert(currentKey, value);
if (LOG.isTraceEnabled()) {
LOG.trace("reinserted the removed key. {}", currentKey);
}
}
throw ex;
}
}
}