All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.runtime.state.gemini.engine.vm.DataPageLRU Maven / Gradle / Ivy

There is a newer version: 1.5.1
Show newest version
/*
 * 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.flink.runtime.state.gemini.engine.vm;

import org.apache.flink.annotation.VisibleForTesting;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.state.gemini.engine.GRegionID;
import org.apache.flink.runtime.state.gemini.engine.page.PageContext;
import org.apache.flink.runtime.state.gemini.engine.page.PageIndex;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static org.apache.flink.util.Preconditions.checkArgument;
import static org.apache.flink.util.Preconditions.checkNotNull;

/**
 * LogicPageLRU.  when Page load from File, and still not enter into Cache. then this page will be put in this small LRU.
 * no thread safe.
 */
public class DataPageLRU {
	private static final Logger LOG = LoggerFactory.getLogger(DataPageLRU.class);
	private final int lruSize;
	private final DataPageLRUFuction function;
	private volatile long totalSize = 0;
	private final ReadWriteLock lock = new ReentrantReadWriteLock();
	private final Map entries;
	private final List headers;
	private final List tailers;
	private final List countsPerSlot;
	private final int startRegion;
	private final int slots;
	private int longestSlot;
	private final boolean evenEvict;
	private CacheStats cacheStats;

	/** Whether to update the linked list when access a entry. */
	private final boolean accessOrder;

	public DataPageLRU(int startRegionId, int endRegionId, int lruSize, DataPageLRUFuction function) {
		this(startRegionId, endRegionId, lruSize, function, true, true, null);
	}

	public DataPageLRU(int startRegionId, int endRegionId, int lruSize, DataPageLRUFuction function, boolean accessOrder, boolean evenEvict, CacheStats cacheStats) {
		this.lruSize = lruSize;
		this.function = function;
		this.accessOrder = accessOrder;
		this.startRegion = startRegionId;
		this.evenEvict = evenEvict;
		this.slots = endRegionId - startRegionId + 1;
		this.longestSlot = 0;

		this.entries = new HashMap(lruSize);

		headers = new ArrayList<>(slots);
		tailers = new ArrayList<>(slots);
		for (int i = 0; i < slots; ++i) {
			headers.add(i, new ListNode(null, null));
			tailers.add(i, new ListNode(null, null));

			headers.get(i).setNext(tailers.get(i));
			headers.get(i).setPrev(tailers.get(i));

			tailers.get(i).setNext(headers.get(i));
			tailers.get(i).setPrev(headers.get(i));
		}
		countsPerSlot = new ArrayList<>(slots);
		for (int i = 0; i < slots; ++i) {
			countsPerSlot.add(i, 0);
		}
		this.cacheStats = cacheStats;
		LOG.info("LRU Cache with size {}, accessMode {}, evenEvict {}, region {} -> {}", lruSize, accessOrder, evenEvict, startRegionId, endRegionId);
	}

	public long getTotalSize() {
		return totalSize;
	}

	public void put(K key, V value) {
		if (cacheStats != null) {
			cacheStats.addPageCacheLRUPutCount();
		}
		lock.writeLock().lock();
		ListNode prev = entries.get(key);
		try {
			if (prev != null) {
				int targetSlot = function.getSlotIndex(value, startRegion);
				int prevSlot = function.getSlotIndex(prev.value, startRegion);
				if (targetSlot != prevSlot) {
					deleteFromList(prev);
					insertIntoHead(targetSlot, prev);
					if (longestSlot == prevSlot && countsPerSlot.get(targetSlot) > countsPerSlot.get(prevSlot)) {
						longestSlot = targetSlot;
					}
				}
				if (prev.value != null && !prev.value.equals(value)) {
					function.removed(prev.value);
				}
				prev.setValue(value);
				return;
			}

			if (entries.size() >= lruSize) {
				ListNode deleteNode = tailers.get(longestSlot).prev;
				deleteFromList(deleteNode);
				entries.remove(deleteNode.key);
				function.removed(deleteNode.value);
				deleteNode.value = null;
				deleteNode = null;
			}
			prev = new ListNode(key, value);
			entries.put(key, prev);

			int targetSlot = function.getSlotIndex(prev.value, startRegion);
			insertIntoHead(targetSlot,  prev);
			// do not to find the longest chain here for better performance.
			// here we make sure that have at least one item to evict next time.
			if (countsPerSlot.get(targetSlot) > countsPerSlot.get(longestSlot)) {
				longestSlot = targetSlot;
			}

			if (evenEvict) {
				// make the other region can be evictable, when current region only the the new added item.
				if (countsPerSlot.get(longestSlot) == 1 && entries.size() > 2) {
					longestSlot = ThreadLocalRandom.current().nextInt(slots);
					while (countsPerSlot.get(longestSlot) == 0) {
						longestSlot = ThreadLocalRandom.current().nextInt(slots);
					}
				}
			}
		} finally {
			lock.writeLock().unlock();
		}
	}

	public int size() {
		return entries.size();
	}

	private void insertIntoHead(int index, ListNode node) {
		ListNode currentHeader = headers.get(index);
		node.setPrev(currentHeader);
		node.setNext(currentHeader.next);
		currentHeader.next.setPrev(node);
		currentHeader.setNext(node);

		totalSize += function.size(node.value);
		countsPerSlot.set(index, countsPerSlot.get(index) + 1);
	}

	private void deleteFromList(ListNode node) {
		node.prev.setNext(node.next);
		node.next.setPrev(node.prev);

		totalSize -= function.size(node.value);
		int slot = function.getSlotIndex(node.value, startRegion);
		countsPerSlot.set(slot, countsPerSlot.get(slot) - 1);
	}

	/**
	 * Returns the entry associated with the given key in specified region, or Null if there is no entry associated with the given key.
	 */
	public V get(K key) {
		Lock curretLock = accessOrder ? lock.writeLock() : lock.readLock();
		curretLock.lock();
		try {
			ListNode listNode = entries.get(key);
			if (listNode != null) {
				if (accessOrder) {
					deleteFromList(listNode);
					insertIntoHead(function.getSlotIndex(listNode.value, startRegion), listNode);
				}
				return listNode.value;
			}
			return null;
		} finally {
			curretLock.unlock();
		}
	}

	public boolean remove(K key) {
		lock.writeLock().lock();
		try {
			ListNode prev = entries.remove(key);
			if (prev == null) {
				return false;
			}

			deleteFromList(prev);
			function.removed(prev.value);
			prev = null;
			return true;
		} finally {
			lock.writeLock().unlock();
		}
	}

	public Tuple2 getHottestPage(GRegionID regionID, PageIndex currentPageIndex) {
		int slotId = regionID.getId() - startRegion;
		lock.readLock().lock();
		try {
			ListNode currentNode = headers.get(slotId).next;
			ListNode tailNode = tailers.get(slotId);
			while (!currentNode.equals(tailNode)) {
				if (!function.canAddIntoMainCache(currentNode.value, currentPageIndex, regionID)) {
					currentNode = currentNode.next;
					continue;
				}

				return Tuple2.of(currentNode.key, currentNode.value);
			}

			return null;
		} finally {
			lock.readLock().unlock();
		}
	}

	@VisibleForTesting
	public Map getEntries() {
		return Collections.unmodifiableMap(entries);
	}

	@VisibleForTesting
	public int getItemNumInSlot(int slot) {
		checkArgument(slot >= 0 && slot < slots);
		int count = 0;
		ListNode currentTailNode = tailers.get(slot).prev;
		while (!headers.get(slot).equals(currentTailNode)) {
			count++;
			currentTailNode = currentTailNode.prev;
		}
		return count;
	}

	public void clear() {
		lock.writeLock().lock();
		try {
			for (ListNode value : entries.values()) {
				totalSize -= function.size(value.value);
				function.removed(value.value);
			}
		} finally {
			lock.writeLock().unlock();
		}
	}

	/**
	 * SizeAble.
	 */
	public interface DataPageLRUFuction {
		int size(T value);

		void removed(T value);

		int getSlotIndex(T value, int offset);

		boolean canAddIntoMainCache(T value, PageIndex pageIndex, GRegionID expectedRegionID);
	}

	/**
	 * Internal node contains external information for a inner value.
	 */
	public static class PageWithContext {
		// null for invalid page context, the data page will not load from lru into main cache.
		private final @Nullable PageContext pageContext;
		private final FutureDataPage futureDataPage;

		public PageWithContext(@Nullable PageContext pageContext, FutureDataPage futureDataPage) {
			this.pageContext = pageContext;
			this.futureDataPage = checkNotNull(futureDataPage);
		}

		public FutureDataPage getFutureDataPage() {
			return futureDataPage;
		}

		@Nullable
		public PageContext getPageContext() {
			return pageContext;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			}
			if (obj == null || obj.getClass() != getClass()) {
				return false;
			}
			PageWithContext other = (PageWithContext) obj;

			return Objects.equals(pageContext, other.pageContext) && Objects.equals(futureDataPage, other.futureDataPage);
		}

		@Override
		public int hashCode() {
			int hashCode = pageContext == null ? 0 : pageContext.hashCode();
			hashCode = hashCode * 31 + futureDataPage.hashCode();
			return hashCode;
		}
	}

	private class ListNode {
		private ListNode prev;
		private ListNode next;
		private final K key;
		private V value;

		public ListNode(final K key, final V value) {
			this.key = key;
			this.value = value;
			this.prev = null;
			this.next = null;
		}

		public void setPrev(ListNode newPrev) {
			this.prev = newPrev;
		}

		public void setNext(ListNode newNext) {
			this.next = newNext;
		}

		private void setValue(V newValue) {
			this.value = newValue;
		}

		@Override
		public int hashCode() {
			return key.hashCode() * 31 + value.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			}
			if (obj == null || obj.getClass() != getClass()) {
				return false;
			}
			ListNode other = (ListNode) obj;
			return Objects.equals(key, other.key) && Objects.equals(value, other.value);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy