/* * 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.db; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import com.google.common.collect.Iterables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; public class MeteredFlusher implements Runnable { private static final Logger logger = LoggerFactory.getLogger(MeteredFlusher.class); public void run() { long totalMemtableBytesAllowed = DatabaseDescriptor.getTotalMemtableSpaceInMB() * 1048576L; // first, find how much memory non-active memtables are using long flushingBytes = Memtable.activelyMeasuring == null ? 0 : Memtable.activelyMeasuring.getMemtableThreadSafe().getLiveSize(); flushingBytes += countFlushingBytes(); if (flushingBytes > 0) logger.debug("Currently flushing {} bytes of {} max", flushingBytes, totalMemtableBytesAllowed); // next, flush CFs using more than 1 / (maximum number of memtables it could have in the pipeline) // of the total size allotted. Then, flush other CFs in order of size if necessary. long liveBytes = 0; try { long totalMemtableBytesUnused = totalMemtableBytesAllowed - flushingBytes; for (ColumnFamilyStore cfs : ColumnFamilyStore.all()) { long size = cfs.getTotalMemtableLiveSize(); int maxInFlight = (int) Math.ceil((double) (1 // live memtable + 1 // potentially a flushed memtable being counted by jamm + DatabaseDescriptor.getFlushWriters() + DatabaseDescriptor.getFlushQueueSize()) / (1 + cfs.indexManager.getIndexesBackedByCfs().size())); if (cfs.getCompactionStrategy().isAffectedByMeteredFlusher() && totalMemtableBytesUnused > 0 && size > totalMemtableBytesUnused / maxInFlight) { logger.info("flushing high-traffic column family {} (estimated {} bytes)", cfs, size); cfs.forceFlush(); } else { liveBytes += size; } } if (flushingBytes + liveBytes <= totalMemtableBytesAllowed) return; logger.info("estimated {} live and {} flushing bytes used by all memtables", liveBytes, flushingBytes); // sort memtables by size List<ColumnFamilyStore> sorted = new ArrayList<ColumnFamilyStore>(); Iterables.addAll(sorted, ColumnFamilyStore.all()); Collections.sort(sorted, new Comparator<ColumnFamilyStore>() { public int compare(ColumnFamilyStore o1, ColumnFamilyStore o2) { long size1 = o1.getTotalMemtableLiveSize(); long size2 = o2.getTotalMemtableLiveSize(); if (size1 < size2) return -1; if (size1 > size2) return 1; return 0; } }); // flush largest first until we get below our threshold. // although it looks like liveBytes + flushingBytes will stay a constant, it will not if flushes finish // while we loop, which is especially likely to happen if the flush queue fills up (so further forceFlush calls block) while (!sorted.isEmpty()) { flushingBytes = countFlushingBytes(); if (liveBytes + flushingBytes <= totalMemtableBytesAllowed) break; ColumnFamilyStore cfs = sorted.remove(sorted.size() - 1); if (cfs.getCompactionStrategy().isAffectedByMeteredFlusher()) { long size = cfs.getTotalMemtableLiveSize(); if (size == 0) break; logger.info("flushing {} to free up {} bytes", cfs, size); liveBytes -= size; cfs.forceFlush(); } } } finally { logger.trace("memtable memory usage is {} bytes with {} live", liveBytes + flushingBytes, liveBytes); } } private long countFlushingBytes() { long flushingBytes = 0; for (ColumnFamilyStore cfs : ColumnFamilyStore.all()) { for (Memtable memtable : cfs.getMemtablesPendingFlush()) flushingBytes += memtable.getLiveSize(); } return flushingBytes; } }