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

org.apache.flink.runtime.state.gemini.engine.page.PageIndexHashImpl 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.page;

import org.apache.flink.runtime.state.gemini.engine.GConfiguration;
import org.apache.flink.runtime.state.gemini.engine.GRegionContext;
import org.apache.flink.runtime.state.gemini.engine.exceptions.GeminiRuntimeException;
import org.apache.flink.runtime.state.gemini.engine.snapshot.RegionSnapshot;
import org.apache.flink.runtime.state.gemini.engine.snapshot.SnapshotMetaFile;
import org.apache.flink.util.MathUtils;

import org.apache.flink.shaded.guava18.com.google.common.collect.Iterators;

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

import javax.annotation.Nullable;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

/**
 * PageIndexHashImpl.
 */
public class PageIndexHashImpl implements PageIndex {
	private static final Logger LOG = LoggerFactory.getLogger(PageIndexHashImpl.class);
	public static final LogicChainedPage WAIT_SPLITTING = new LogicChainedPageImpl(PageStatus.Init);
	public static final LogicChainedPage NO_PAGE = null;
	private final int baseBucketNum;
	private volatile int curBucketNum;
	private final PageStore pageStore;
	private final PageStoreStats pageStoreStats;

	private volatile LogicChainedPage[] pageIndex;

	private final int logicPageChainLenDefault;

	public PageIndexHashImpl(
		GConfiguration config,
		PageStore pageStore,
		PageStoreStats pageStoreStats) {
		int configBucktNum = config.getPageIndexBucketLenDefault();
		checkArgument((configBucktNum & configBucktNum - 1) == 0, "curBucketNum should be a power of 2.");
		baseBucketNum = configBucktNum;
		curBucketNum = configBucktNum;
		pageIndex = new LogicChainedPage[configBucktNum];
		this.pageStore = pageStore;
		this.pageStoreStats = pageStoreStats;
		this.logicPageChainLenDefault = config.getLogicTableDefaultChainLen();
		this.pageStoreStats.setIndexCapacity(curBucketNum);
	}

	public PageIndexHashImpl(
		LogicChainedPage[] pageIndex,
		PageStore pageStore,
		PageStoreStats pageStoreStats,
		int baseBucketNum,
		int curBucketNum,
		int logicPageChainLenDefault) {
		this.baseBucketNum = baseBucketNum;
		this.curBucketNum = curBucketNum;
		this.pageIndex = pageIndex;
		this.pageStore = pageStore;
		this.pageStoreStats = pageStoreStats;
		this.logicPageChainLenDefault = logicPageChainLenDefault;
		this.pageStoreStats.setIndexCapacity(curBucketNum);
	}

	private PageIndexHashImpl(PageIndexHashImpl pageIndexHash, Map allAddReferenceDataPage) {
		this.baseBucketNum = pageIndexHash.baseBucketNum;
		this.curBucketNum = pageIndexHash.curBucketNum;
		this.pageStore = pageIndexHash.pageStore;
		this.pageStoreStats = pageIndexHash.pageStoreStats;
		this.logicPageChainLenDefault = pageIndexHash.logicPageChainLenDefault;

		this.pageIndex = new LogicChainedPage[pageIndexHash.pageIndex.length];
		for (int i = 0; i < pageIndex.length; i++) {
			LogicChainedPage page = pageIndexHash.pageIndex[i];
			if (page != null) {
				this.pageIndex[i] = page.deepCopy(allAddReferenceDataPage);
			}
		}
	}

	@Override
	public PageIndexContext getPageIndexContext(K key, boolean createIfMiss) {
		int hash = MathUtils.bitMix(key.hashCode());
		int checkBucketNum = this.curBucketNum;
		int curIndex = hash & (checkBucketNum - 1);
		if (createIfMiss) {
			//no need collision detection.
			return internalGetPageIndexContext(hash, curIndex, checkBucketNum, true);
		}
		//no deadly loop threaten.
		while (true) {
			//1. first get the reference of page, this is important.
			PageIndexContextHashImpl result = (PageIndexContextHashImpl) internalGetPageIndexContext(hash,
				curIndex,
				checkBucketNum,
				false);
			//thread safe collision detection
			//2. if spilling happen during this operation, redo
			if (checkBucketNum != this.curBucketNum) {
				checkBucketNum = this.curBucketNum;
				curIndex = hash & (checkBucketNum - 1);
				continue;
			}
			//3. spill have happened, but page split maybe is not finished, so we check whether need to redo.
			//and we check curBucketNum but not index, because index maybe same, but curBucketNum will be change when needing to recursiveGetPageContext.
			if (result.getCurBucketNum() != this.curBucketNum) {
				PageIndexContextHashImpl checkResult = (PageIndexContextHashImpl) internalGetPageIndexContext(hash,
					hash & (this.curBucketNum - 1),
					this.curBucketNum,
					false);
				if (result.getCurIndex() == checkResult.getCurIndex()) {
					// if the index does not change,it's safe. because we fist get the reference of page.
					return result;
				}
				//other way is to check the previous index, now we easily to redo it. because spilling is a short volatile status.
				continue;

			} else {
				//even though spill happen at this time, it's safe because we have reference of this page which makes sure to have right data.
				return result;
			}
		}
	}

	private PageIndexContext internalGetPageIndexContext(
		int hash, int curIndex, int checkBucketNum, boolean createIfMiss) {

		PageIndexContext result = pageIndex[curIndex] == WAIT_SPLITTING
			? recursiveGetPageContext(hash, checkBucketNum)
			: PageIndexContextHashImpl.of(checkBucketNum, curIndex, pageIndex[curIndex], false);

		if (result.getPageID() != NO_PAGE || !createIfMiss) {
			return result;
		} else {
			pageIndex[curIndex] = newLogicChainedPage();
			this.pageStoreStats.addLogicPageCount(1);
			this.pageStoreStats.addLogicPageChainCapacity(logicPageChainLenDefault);
			return PageIndexContextHashImpl.of(checkBucketNum, curIndex, pageIndex[curIndex], false);
		}
	}

	private PageIndexContext recursiveGetPageContext(int hash, int checkBucketNum) {
		checkBucketNum = checkBucketNum >> 1;
		int index;
		while (checkBucketNum >= this.baseBucketNum) {
			index = hash & (checkBucketNum - 1);
			if (pageIndex[index] != WAIT_SPLITTING) {

				return PageIndexContextHashImpl.of(checkBucketNum, index, pageIndex[index], true);
			}
			checkBucketNum = checkBucketNum >> 1;
		}
		throw new GeminiRuntimeException("Internal Bug!");
	}

	@Override
	public void expand() {
		//prevent from replicated expand operator by mechanism.
		LogicChainedPage[] pageIndexNew = new LogicChainedPage[curBucketNum << 1];
		System.arraycopy(pageIndex, 0, pageIndexNew, 0, pageIndex.length);
		Arrays.fill(pageIndexNew, pageIndex.length, pageIndexNew.length, WAIT_SPLITTING);
		this.pageIndex = pageIndexNew;
		curBucketNum = pageIndexNew.length;
		this.pageStoreStats.setIndexCapacity(curBucketNum);
	}

	@Override
	public void shrink() {
		//TODO
		this.pageStoreStats.setIndexCapacity(curBucketNum);
	}

	/** restore will do in {@link Builder}. */
	@Override
	public void snapshot(
		@Nullable RegionSnapshot localRegionSnapshot,
		RegionSnapshot dfsRegionSnapshot) throws IOException {
		snapshotPageIndexMeta(localRegionSnapshot);
		snapshotPageIndexMeta(dfsRegionSnapshot);
		for (int i = 0; i < pageIndex.length; ++i) {
			LogicChainedPage logicChainedPage = pageIndex[i];
			if (logicChainedPage == null) {
				writeBoolean(localRegionSnapshot, dfsRegionSnapshot, true);
			} else {
				writeBoolean(localRegionSnapshot, dfsRegionSnapshot, false);
				if (logicChainedPage == WAIT_SPLITTING) {
					writeBoolean(localRegionSnapshot, dfsRegionSnapshot, true);
				} else {
					writeBoolean(localRegionSnapshot, dfsRegionSnapshot, false);
					logicChainedPage.snapshot(localRegionSnapshot, dfsRegionSnapshot);
				}
			}
		}
	}

	private void writeBoolean(
		RegionSnapshot regionSnapshot1,
		RegionSnapshot regionSnapshot2,
		boolean value) throws IOException {
		if (regionSnapshot1 != null) {
			regionSnapshot1.getWriter().writeBoolean(value);
		}
		if (regionSnapshot2 != null) {
			regionSnapshot2.getWriter().writeBoolean(value);
		}
	}

	private void snapshotPageIndexMeta(RegionSnapshot regionSnapshot) throws IOException {
		if (regionSnapshot == null) {
			return;
		}
		SnapshotMetaFile.Writer writer = regionSnapshot.getWriter();
		writer.writeInt(baseBucketNum);
		writer.writeInt(curBucketNum);
		writer.writeInt(logicPageChainLenDefault);
		writer.writeInt(pageIndex.length);
	}

	@Override
	public boolean updateLogicPageStatus(
		int pageID, PageStatus expectedStatus, PageStatus targetStatus) {
		return pageIndex[pageID].setPageStatus(expectedStatus, targetStatus);
	}

	@Override
	public PageIndex deepCopy(Map allAddReferenceDataPage) {
		return new PageIndexHashImpl<>(this, allAddReferenceDataPage);
	}

	@Override
	public void removeLogicPage(int baseLogicPageID) {
		pageIndex[baseLogicPageID] = NO_PAGE;
		this.pageStoreStats.addLogicPageCount(-1);
	}

	@Override
	public LogicChainedPage getLogicPage(int baseLogicPageID) {
		return pageIndex[baseLogicPageID];
	}

	@Override
	public void updateLogicPage(
		int baseLogicPageID, LogicChainedPage destLogicPage) {
		pageIndex[baseLogicPageID] = destLogicPage;
	}

	@Override
	public LogicChainedPage newLogicChainedPage() {
		return new LogicChainedPageImpl(PageStatus.Normal, logicPageChainLenDefault);
	}

	@Override
	public Iterator pageIterator() {
		List> iteratorLists = new ArrayList<>();
		for (LogicChainedPage logicChainedPage : pageIndex) {
			if (logicChainedPage != null) {
				iteratorLists.add(logicChainedPage.pageIterator());
			}
		}

		return Iterators.concat(iteratorLists.iterator());
	}

	@Override
	public int getIndexCapacity() {
		return curBucketNum;
	}

	@Override
	public LogicChainedPage[] getPageIndex() {
		return pageIndex;
	}

	/**
	 * get page's min bucketNum need to split.
	 *
	 * @param curBucketNum this is a record of bucketNum when finding the index.
	 * @param curIndex     the index refer to data.
	 * @return what's bucketNum when this page have finished split.
	 */
	public int getBucketNumASPageFinishSplit(int curBucketNum, int curIndex) {
		int checkBucketNum;
		while (true) {
			if (getLogicPage(curIndex) == WAIT_SPLITTING) {
				throw new GeminiRuntimeException("Gemini Internal Bug, want to split a invalid page");
			}
			checkBucketNum = curBucketNum >> 1;
			if (checkBucketNum <= curIndex) {
				//it means this page must been splitted.
				break;
			}
			if (getLogicPage(curIndex + checkBucketNum) == WAIT_SPLITTING) {
				curBucketNum = checkBucketNum;
			} else {
				break;
			}
		}
		return curBucketNum;
	}

	/**
	 * Builder.
	 */
	public static class Builder {
		private final int baseBucketNum;
		private final int curBucketNum;
		private final int logicPageChainLenDefault;
		private final LogicChainedPage[] pageIndex;
		private final GRegionContext regionContext;

		public Builder(SnapshotMetaFile.Reader reader, GRegionContext context) throws IOException {
			// restore all the needed things
			this.baseBucketNum = reader.readInt();
			this.curBucketNum = reader.readInt();
			this.logicPageChainLenDefault = reader.readInt();
			this.regionContext = context;

			int pageIndexLength = reader.readInt();
			pageIndex = new LogicChainedPage[pageIndexLength];
			for (int i = 0; i < pageIndexLength; i++) {
				boolean emptyPage = reader.readBoolean();
				LogicChainedPage logicChainedPage;
				if (emptyPage) {
					logicChainedPage = null;
				} else {
					boolean waitSplitting = reader.readBoolean();
					if (waitSplitting) {
						// set status to wait_splitting .
						logicChainedPage = WAIT_SPLITTING;
					} else {
						// restored page status will be Normal
						logicChainedPage = new LogicChainedPageImpl(PageStatus.Normal);
						logicChainedPage.restore(reader, this.regionContext.getPageStoreStats());
						this.regionContext.getPageStoreStats().addLogicPageCount(1);
					}
				}
				pageIndex[i] = logicChainedPage;
			}
		}

		public PageIndex build() {
			// TODO: #SR how to pass in the pageStore and pageStoreStats.
			return new PageIndexHashImpl(pageIndex, null, regionContext.getPageStoreStats(), baseBucketNum, curBucketNum, logicPageChainLenDefault);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy