/*
* 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.ignite.internal.processors.cache.database.tree.io;
import java.nio.ByteBuffer;
import org.apache.ignite.internal.pagemem.PageIdUtils;
import org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler;
/**
* We use dedicated page for tracking pages updates.
* Also we divide such 'tracking' pages on two half, first is used for check that page was changed or not
* (during incremental snapshot), second - to accumulate changed for next snapshot.
*
* You cannot test change for not started snapshot! because it will cause of preparation for snapshot.
*
* Implementation. For each page there is own bit in both half. Tracking page is used for tracking N page after it.
* N depends on page size (how many bytes we can use for tracking).
*
*
* +-----------------------------------------+-----------------------------------------+
* | left half | right half |
* +---------+----------+----+------------------------------------+----+------------------------------------+
* | HEADER | Last |size| |size| |
* | |SnapshotId|2b. | tracking bits |2b. | tracking bits |
* +---------+----------+----+------------------------------------+----+------------------------------------+
*
*/
public class TrackingPageIO extends PageIO {
/** */
public static final IOVersions<TrackingPageIO> VERSIONS = new IOVersions<>(
new TrackingPageIO(1)
);
/** Last snapshot offset. */
public static final int LAST_SNAPSHOT_TAG_OFFSET = COMMON_HEADER_END;
/** Size field offset. */
public static final int SIZE_FIELD_OFFSET = LAST_SNAPSHOT_TAG_OFFSET + 8;
/** 'Size' field size. */
public static final int SIZE_FIELD_SIZE = 2;
/** Bitmap offset. */
public static final int BITMAP_OFFSET = SIZE_FIELD_OFFSET + SIZE_FIELD_SIZE;
/** Count of extra page. */
public static final int COUNT_OF_EXTRA_PAGE = 1;
/**
* @param ver Page format version.
*/
protected TrackingPageIO(int ver) {
super(PageIO.T_PAGE_UPDATE_TRACKING, ver);
}
/**
* Will mark pageId as changed for next (!) snapshotId
*
* @param buf Buffer.
* @param pageId Page id.
* @param nextSnapshotTag tag of next snapshot.
* @param pageSize Page size.
*/
public boolean markChanged(ByteBuffer buf, long pageId, long nextSnapshotTag, long lastSuccessfulSnapshotTag, int pageSize) {
validateSnapshotId(buf, nextSnapshotTag, lastSuccessfulSnapshotTag, pageSize);
int cntOfPage = countOfPageToTrack(pageSize);
int idxToUpdate = (PageIdUtils.pageIndex(pageId) - COUNT_OF_EXTRA_PAGE) % cntOfPage;
int sizeOff = useLeftHalf(nextSnapshotTag) ? SIZE_FIELD_OFFSET : BITMAP_OFFSET + (cntOfPage >> 3);
int idx = sizeOff + SIZE_FIELD_SIZE + (idxToUpdate >> 3);
byte byteToUpdate = buf.get(idx);
int updateTemplate = 1 << (idxToUpdate & 0b111);
byte newVal = (byte) (byteToUpdate | updateTemplate);
if (byteToUpdate == newVal)
return false;
buf.put(idx, newVal);
short newSize = (short)(buf.getShort(sizeOff) + 1);
buf.putShort(sizeOff, newSize);
assert newSize == countOfChangedPage(buf, nextSnapshotTag, pageSize);
return true;
}
/**
* @param buf Buffer.
* @param nextSnapshotTag Next snapshot id.
* @param lastSuccessfulSnapshotId Last successful snapshot id.
* @param pageSize Page size.
*/
private void validateSnapshotId(ByteBuffer buf, long nextSnapshotTag, long lastSuccessfulSnapshotId, int pageSize) {
assert nextSnapshotTag != lastSuccessfulSnapshotId : "nextSnapshotTag = " + nextSnapshotTag +
", lastSuccessfulSnapshotId = " + lastSuccessfulSnapshotId;
long last = getLastSnapshotTag(buf);
assert last <= nextSnapshotTag : "last = " + last + ", nextSnapshotTag = " + nextSnapshotTag;
if (nextSnapshotTag == last) //everything is ok
return;
int cntOfPage = countOfPageToTrack(pageSize);
if (last <= lastSuccessfulSnapshotId) { //we can drop our data
buf.putLong(LAST_SNAPSHOT_TAG_OFFSET, nextSnapshotTag);
PageHandler.zeroMemory(buf, SIZE_FIELD_OFFSET, buf.capacity() - SIZE_FIELD_OFFSET);
} else { //we can't drop data, it is still necessary for incremental snapshots
int len = cntOfPage >> 3;
int sizeOff = useLeftHalf(nextSnapshotTag) ? SIZE_FIELD_OFFSET : BITMAP_OFFSET + len;
int sizeOff2 = !useLeftHalf(nextSnapshotTag) ? SIZE_FIELD_OFFSET : BITMAP_OFFSET + len;
if (last - lastSuccessfulSnapshotId == 1) { //we should keep only data in last half
//new data will be written in the same half, we should move old data to another half
if ((nextSnapshotTag - last) % 2 == 0)
PageHandler.copyMemory(buf, buf, sizeOff, sizeOff2, len + SIZE_FIELD_SIZE);
} else { //last - lastSuccessfulSnapshotId > 1, e.g. we should merge two half in one
int newSize = 0;
int i = 0;
for (; i < len - 8; i += 8) {
long newVal = buf.getLong(sizeOff + SIZE_FIELD_SIZE + i) | buf.getLong(sizeOff2 + SIZE_FIELD_SIZE + i);
newSize += Long.bitCount(newVal);
buf.putLong(sizeOff2 + SIZE_FIELD_SIZE + i, newVal);
}
for (; i < len; i ++) {
byte newVal = (byte) (buf.get(sizeOff + SIZE_FIELD_SIZE + i) | buf.get(sizeOff2 + SIZE_FIELD_SIZE + i));
newSize += Integer.bitCount(newVal & 0xFF);
buf.put(sizeOff2 + SIZE_FIELD_SIZE + i, newVal);
}
buf.putShort(sizeOff2, (short)newSize);
}
buf.putLong(LAST_SNAPSHOT_TAG_OFFSET, nextSnapshotTag);
PageHandler.zeroMemory(buf, sizeOff, len + SIZE_FIELD_SIZE);
}
}
/**
* @param buf Buffer.
*/
long getLastSnapshotTag(ByteBuffer buf) {
return buf.getLong(LAST_SNAPSHOT_TAG_OFFSET);
}
/**
* Check that pageId was marked as changed between previous snapshot finish and current snapshot start.
*
* @param buf Buffer.
* @param pageId Page id.
* @param curSnapshotTag Snapshot tag.
* @param pageSize Page size.
*/
public boolean wasChanged(ByteBuffer buf, long pageId, long curSnapshotTag, long lastSuccessfulSnapshotTag, int pageSize) {
validateSnapshotId(buf, curSnapshotTag + 1, lastSuccessfulSnapshotTag, pageSize);
if (countOfChangedPage(buf, curSnapshotTag, pageSize) < 1)
return false;
int cntOfPage = countOfPageToTrack(pageSize);
int idxToTest = (PageIdUtils.pageIndex(pageId) - COUNT_OF_EXTRA_PAGE) % cntOfPage;
byte byteToTest;
if (useLeftHalf(curSnapshotTag))
byteToTest = buf.get(BITMAP_OFFSET + (idxToTest >> 3));
else
byteToTest = buf.get(BITMAP_OFFSET + SIZE_FIELD_SIZE + ((idxToTest + cntOfPage) >> 3));
int testTemplate = 1 << (idxToTest & 0b111);
return ((byteToTest & testTemplate) ^ testTemplate) == 0;
}
/**
* @param buf Buffer.
* @param snapshotTag Snapshot tag.
* @param pageSize Page size.
*
* @return count of pages which were marked as change for given snapshotTag
*/
public short countOfChangedPage(ByteBuffer buf, long snapshotTag, int pageSize) {
long dif = getLastSnapshotTag(buf) - snapshotTag;
if (dif != 0 && dif != 1)
return -1;
if (useLeftHalf(snapshotTag))
return buf.getShort(SIZE_FIELD_OFFSET);
else
return buf.getShort(BITMAP_OFFSET + (countOfPageToTrack(pageSize) >> 3));
}
/**
* @param snapshotTag Snapshot id.
*
* @return true if snapshotTag is odd, otherwise - false
*/
boolean useLeftHalf(long snapshotTag) {
return (snapshotTag & 0b1) == 0;
}
/**
* @param pageId Page id.
* @param pageSize Page size.
* @return pageId of tracking page which set pageId belongs to
*/
public long trackingPageFor(long pageId, int pageSize) {
assert PageIdUtils.pageIndex(pageId) > 0;
int pageIdx = ((PageIdUtils.pageIndex(pageId) - COUNT_OF_EXTRA_PAGE) /
countOfPageToTrack(pageSize)) * countOfPageToTrack(pageSize) + COUNT_OF_EXTRA_PAGE;
long trackingPageId = PageIdUtils.pageId(PageIdUtils.partId(pageId), PageIdUtils.flag(pageId), pageIdx);
assert PageIdUtils.pageIndex(trackingPageId) <= PageIdUtils.pageIndex(pageId);
return trackingPageId;
}
/**
* @param pageSize Page size.
*
* @return how many page we can track with 1 page
*/
public int countOfPageToTrack(int pageSize) {
return ((pageSize - SIZE_FIELD_OFFSET) / 2 - SIZE_FIELD_SIZE) << 3;
}
/**
* @param buf Buffer.
* @param start Start.
* @param curSnapshotTag Snapshot id.
* @param pageSize Page size.
* @return set pageId if it was changed or next closest one, if there is no changed page null will be returned
*/
public Long findNextChangedPage(ByteBuffer buf, long start, long curSnapshotTag, long lastSuccessfulSnapshotTag, int pageSize) {
validateSnapshotId(buf, curSnapshotTag + 1, lastSuccessfulSnapshotTag, pageSize);
int cntOfPage = countOfPageToTrack(pageSize);
long trackingPage = trackingPageFor(start, pageSize);
if (start == trackingPage)
return trackingPage;
if (countOfChangedPage(buf, curSnapshotTag, pageSize) <= 0)
return null;
int idxToStartTest = (PageIdUtils.pageIndex(start) - COUNT_OF_EXTRA_PAGE) % cntOfPage;
int zeroIdx = useLeftHalf(curSnapshotTag)? BITMAP_OFFSET : BITMAP_OFFSET + SIZE_FIELD_SIZE + (cntOfPage >> 3);
int startIdx = zeroIdx + (idxToStartTest >> 3);
int idx = startIdx;
int stopIdx = zeroIdx + (cntOfPage >> 3);
while (idx < stopIdx) {
byte byteToTest = buf.get(idx);
if (byteToTest != 0) {
int foundSetBit;
if ((foundSetBit = foundSetBit(byteToTest, idx == startIdx ? (idxToStartTest & 0b111) : 0)) != -1) {
long foundPageId = PageIdUtils.pageId(
PageIdUtils.partId(start),
PageIdUtils.flag(start),
PageIdUtils.pageIndex(trackingPage) + ((idx - zeroIdx) << 3) + foundSetBit);
assert wasChanged(buf, foundPageId, curSnapshotTag, lastSuccessfulSnapshotTag, pageSize);
assert trackingPageFor(foundPageId, pageSize) == trackingPage;
return foundPageId;
}
}
idx++;
}
return null;
}
/**
* @param byteToTest Byte to test.
* @param firstBitToTest First bit to test.
*/
private static int foundSetBit(byte byteToTest, int firstBitToTest) {
assert firstBitToTest < 8;
for (int i = firstBitToTest; i < 8; i++) {
int testTemplate = 1 << i;
if (((byteToTest & testTemplate) ^ testTemplate) == 0)
return i;
}
return -1;
}
}