/* * 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.service; import static org.apache.cassandra.cql3.QueryProcessor.processInternal; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.dht.Token; import org.apache.cassandra.utils.ByteBufferUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ScheduledRangeTransferExecutorService { private static final Logger LOG = LoggerFactory.getLogger(ScheduledRangeTransferExecutorService.class); private static final int INTERVAL = 10; private ScheduledExecutorService scheduler; public void setup() { if (DatabaseDescriptor.getNumTokens() == 1) { LOG.warn("Cannot start range transfer scheduler: endpoint is not virtual nodes-enabled"); return; } scheduler = Executors.newSingleThreadScheduledExecutor(new RangeTransferThreadFactory()); scheduler.scheduleWithFixedDelay(new RangeTransfer(), 0, INTERVAL, TimeUnit.SECONDS); LOG.info("Enabling scheduled transfers of token ranges"); } public void tearDown() { if (scheduler == null) { LOG.warn("Unabled to shutdown; Scheduler never enabled"); return; } LOG.info("Shutting down range transfer scheduler"); scheduler.shutdownNow(); } } class RangeTransfer implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(RangeTransfer.class); public void run() { UntypedResultSet res = processInternal("SELECT * FROM system." + SystemKeyspace.RANGE_XFERS_CF); if (res.size() < 1) { LOG.debug("No queued ranges to transfer"); return; } if (!isReady()) return; UntypedResultSet.Row row = res.iterator().next(); Date requestedAt = row.getTimestamp("requested_at"); ByteBuffer tokenBytes = row.getBytes("token_bytes"); Token token = StorageService.getPartitioner().getTokenFactory().fromByteArray(tokenBytes); LOG.info("Initiating transfer of {} (scheduled at {})", token, requestedAt.toString()); try { StorageService.instance.relocateTokens(Collections.singleton(token)); } catch (Exception e) { LOG.error("Error removing {}: {}", token, e); } finally { LOG.debug("Removing queued entry for transfer of {}", token); processInternal(String.format("DELETE FROM system.%s WHERE token_bytes = '%s'", SystemKeyspace.RANGE_XFERS_CF, ByteBufferUtil.bytesToHex(tokenBytes))); } } private boolean isReady() { int targetTokens = DatabaseDescriptor.getNumTokens(); int highMark = (int)Math.ceil(targetTokens + (targetTokens * .10)); int actualTokens = StorageService.instance.getTokens().size(); if (actualTokens >= highMark) { LOG.warn("Pausing until token count stabilizes (target={}, actual={})", targetTokens, actualTokens); return false; } return true; } } class RangeTransferThreadFactory implements ThreadFactory { private AtomicInteger count = new AtomicInteger(0); public Thread newThread(Runnable r) { Thread rangeXferThread = new Thread(r); rangeXferThread.setName(String.format("ScheduledRangeXfers:%d", count.getAndIncrement())); return rangeXferThread; } }