/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * 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. * See the License for the specific language governing permissions and * limitations under the License. */ package com.linkedin.pinot.routing; import com.google.common.base.Splitter; import com.linkedin.pinot.common.config.RealtimeTableConfig; import com.linkedin.pinot.common.metadata.ZKMetadataProvider; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.I0Itec.zkclient.IZkDataListener; import org.apache.commons.configuration.Configuration; import org.apache.helix.ZNRecord; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Routing table selector that uses the "routing.llc.percentage" (between 0-100) custom config in metadata to determine * the appropriate ratio of queries to route to LLC. */ public class TableConfigRoutingTableSelector implements RoutingTableSelector, IZkDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(TableConfigRoutingTableSelector.class); private static final String LLC_ROUTING_PERCENTAGE_CONFIG_KEY = "routing.llc.percentage"; private static final Random RANDOM = new Random(); private static final Executor LLC_CONFIG_FETCHER = Executors.newSingleThreadExecutor(); private static final ConcurrentHashMap<String, String> CONFIG_FETCHES_IN_PROGRESS = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Float> _tableRoutingRatioMap = new ConcurrentHashMap<>(); private ZkHelixPropertyStore<ZNRecord> _propertyStore; @Override public boolean shouldUseLLCRouting(String realtimeTableName) { // Randomly route to LLC based on the configured ratio return RANDOM.nextFloat() < _tableRoutingRatioMap.get(realtimeTableName); } private float getLlcRatio(String realtimeTableName) { RealtimeTableConfig tableConfig = (RealtimeTableConfig) ZKMetadataProvider.getRealtimeTableConfig(_propertyStore, realtimeTableName); if (tableConfig == null) { LOGGER.warn("Failed to fetch table config for table {}", realtimeTableName); return 0.0f; } Map<String, String> customConfigs = tableConfig.getCustomConfigs().getCustomConfigs(); if (customConfigs.containsKey(LLC_ROUTING_PERCENTAGE_CONFIG_KEY)) { String routingPercentageString = customConfigs.get(LLC_ROUTING_PERCENTAGE_CONFIG_KEY); float routingPercentage; try { routingPercentage = Float.parseFloat(routingPercentageString); } catch (NumberFormatException e) { LOGGER.warn("Couldn't parse {} as a valid LLC routing percentage, should be a number between 0-100", e); return 0.0f; } if (routingPercentage < 0.0f || 100.0f < routingPercentage) { routingPercentage = Math.min(Math.max(routingPercentage, 0.0f), 100.0f); LOGGER.warn("LLC routing percentage ({}) is outside of [0;100], percentage was clamped to {}.", routingPercentageString, routingPercentage); } return routingPercentage; } return 0.0f; } @Override public void init(Configuration configuration, ZkHelixPropertyStore<ZNRecord> propertyStore) { _propertyStore = propertyStore; } @Override public void registerTable(String realtimeTableName) { String tablePropertyStorePath = ZKMetadataProvider.constructPropertyStorePathForResourceConfig(realtimeTableName); _propertyStore.subscribeDataChanges(tablePropertyStorePath, this); _tableRoutingRatioMap.put(realtimeTableName, getLlcRatio(realtimeTableName) / 100.0f); } @Override public void handleDataChange(String dataPath, Object data) throws Exception { final List<String> zkPathParts = Splitter.on('/').splitToList(dataPath); String realtimeTableName = zkPathParts.get(zkPathParts.size() - 1); // Update the ratio in place _tableRoutingRatioMap.put(realtimeTableName, getLlcRatio(realtimeTableName) / 100.0f); } @Override public void handleDataDeleted(String dataPath) throws Exception { // Ignore for now } }