| /* |
| * 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.persistence.defragmentation; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteException; |
| import org.apache.ignite.IgniteLogger; |
| import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; |
| import org.apache.ignite.internal.processors.cache.persistence.file.FileIOFactory; |
| import org.apache.ignite.internal.util.typedef.internal.S; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| |
| import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; |
| import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; |
| import static java.nio.file.StandardOpenOption.CREATE_NEW; |
| import static java.nio.file.StandardOpenOption.WRITE; |
| import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.FILE_SUFFIX; |
| import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.INDEX_FILE_NAME; |
| import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.INDEX_FILE_PREFIX; |
| import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.PART_FILE_PREFIX; |
| import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.PART_FILE_TEMPLATE; |
| import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.TMP_SUFFIX; |
| |
| /** |
| * Everything related to file management during defragmentation process. |
| */ |
| public class DefragmentationFileUtils { |
| /** Prefix for link mapping files. */ |
| private static final String DFRG_LINK_MAPPING_FILE_PREFIX = PART_FILE_PREFIX + "map-"; |
| |
| /** Link mapping file template. */ |
| private static final String DFRG_LINK_MAPPING_FILE_TEMPLATE = DFRG_LINK_MAPPING_FILE_PREFIX + "%d" + FILE_SUFFIX; |
| |
| /** Defragmentation complation marker file name. */ |
| private static final String DFRG_COMPLETION_MARKER_FILE_NAME = "dfrg-completion-marker"; |
| |
| /** Name of defragmentated index partition file. */ |
| private static final String DFRG_INDEX_FILE_NAME = INDEX_FILE_PREFIX + "-dfrg" + FILE_SUFFIX; |
| |
| /** Name of defragmentated index partition temporary file. */ |
| private static final String DFRG_INDEX_TMP_FILE_NAME = DFRG_INDEX_FILE_NAME + TMP_SUFFIX; |
| |
| /** Prefix for defragmented partition files. */ |
| private static final String DFRG_PARTITION_FILE_PREFIX = PART_FILE_PREFIX + "dfrg-"; |
| |
| /** Defragmented partition file template. */ |
| private static final String DFRG_PARTITION_FILE_TEMPLATE = DFRG_PARTITION_FILE_PREFIX + "%d" + FILE_SUFFIX; |
| |
| /** Defragmented partition temp file template. */ |
| private static final String DFRG_PARTITION_TMP_FILE_TEMPLATE = DFRG_PARTITION_FILE_TEMPLATE + TMP_SUFFIX; |
| |
| /** |
| * Performs cleanup of work dir before initializing file page stores. |
| * Will finish batch renaming if defragmentation was completed or delete garbage if it wasn't. |
| * |
| * @param workDir Cache group working directory. |
| * @param log Logger to write messages. |
| * @throws IgniteCheckedException If {@link IOException} occurred. |
| */ |
| public static void beforeInitPageStores(File workDir, IgniteLogger log) throws IgniteCheckedException { |
| try { |
| batchRenameDefragmentedCacheGroupPartitions(workDir, log); |
| |
| U.delete(defragmentationCompletionMarkerFile(workDir)); |
| |
| deleteLeftovers(workDir); |
| } |
| catch (IgniteException e) { |
| throw new IgniteCheckedException(e); |
| } |
| } |
| |
| /** |
| * Deletes all defragmentation related file from work directory, except for completion marker. |
| * |
| * @param workDir Cache group working directory. |
| */ |
| public static void deleteLeftovers(File workDir) { |
| for (File file : workDir.listFiles()) { |
| String fileName = file.getName(); |
| |
| if ( |
| fileName.startsWith(DFRG_PARTITION_FILE_PREFIX) |
| || fileName.startsWith(DFRG_INDEX_FILE_NAME) |
| || fileName.startsWith(DFRG_LINK_MAPPING_FILE_PREFIX) |
| ) |
| U.delete(file); |
| } |
| } |
| |
| /** |
| * Checks whether cache group defragmentation completed or not. Completes it if all that's left is renaming. |
| * |
| * @param workDir Cache group working directory. |
| * @param grpId Cache group Id of cache group belonging to the given working directory. |
| * @param log Logger to write messages. |
| * @return {@code true} if given cache group is already defragmented. |
| * @throws IgniteException If {@link IOException} occurred. |
| * |
| * @see DefragmentationFileUtils#defragmentationCompletionMarkerFile(File) |
| */ |
| public static boolean skipAlreadyDefragmentedCacheGroup(File workDir, int grpId, IgniteLogger log) throws IgniteException { |
| File completionMarkerFile = defragmentationCompletionMarkerFile(workDir); |
| |
| if (completionMarkerFile.exists()) { |
| if (log.isInfoEnabled()) { |
| log.info(S.toString( |
| "Skipping already defragmented page group", |
| "grpId", grpId, false, |
| "markerFileName", completionMarkerFile.getName(), false, |
| "workDir", workDir.getAbsolutePath(), false |
| )); |
| } |
| |
| batchRenameDefragmentedCacheGroupPartitions(workDir, log); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Checks whether partition has already been defragmented or not. Cleans corrupted data if previous failed |
| * defragmentation attempt was found. |
| * |
| * @param workDir Cache group working directory. |
| * @param grpId Cache group Id of cache group belonging to the given working directory. |
| * @param partId Partition index to check. |
| * @param log Logger to write messages. |
| * @return {@code true} if given partition is already defragmented. |
| * @throws IgniteException If {@link IOException} occurred. |
| * |
| * @see DefragmentationFileUtils#defragmentedPartTmpFile(File, int) |
| * @see DefragmentationFileUtils#defragmentedPartFile(File, int) |
| * @see DefragmentationFileUtils#defragmentedPartMappingFile(File, int) |
| */ |
| public static boolean skipAlreadyDefragmentedPartition(File workDir, int grpId, int partId, IgniteLogger log) throws IgniteException { |
| File defragmentedPartFile = defragmentedPartFile(workDir, partId); |
| File defragmentedPartMappingFile = defragmentedPartMappingFile(workDir, partId); |
| |
| if (defragmentedPartFile.exists() && defragmentedPartMappingFile.exists()) { |
| if (log.isInfoEnabled()) { |
| log.info(S.toString( |
| "Skipping already defragmented partition", |
| "grpId", grpId, false, |
| "partId", partId, false, |
| "partFileName", defragmentedPartFile.getName(), false, |
| "mappingFileName", defragmentedPartMappingFile.getName(), false, |
| "workDir", workDir.getAbsolutePath(), false |
| )); |
| } |
| |
| return true; |
| } |
| |
| File defragmentedPartTmpFile = defragmentedPartTmpFile(workDir, partId); |
| |
| try { |
| Files.deleteIfExists(defragmentedPartTmpFile.toPath()); |
| |
| Files.deleteIfExists(defragmentedPartFile.toPath()); |
| |
| Files.deleteIfExists(defragmentedPartMappingFile.toPath()); |
| } |
| catch (IOException e) { |
| throw new IgniteException(e); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Failure-tolerant batch rename of defragmented partition files. |
| * |
| * Deletes all link mapping files old partition and index files, renaming defragmentated files in the process. Can |
| * be run on the same folder multiple times if failed for some reason. |
| * |
| * Does something only if completion marker is present in the folder. This marker won't be deleted in the end. |
| * Deletion of the marker must be done outside of defragmentation mode to prevent cache groups to be defragmentated |
| * several times in case of failures. |
| * |
| * @param workDir Cache group working directory. |
| * @param log Logger to write messages. |
| * @throws IgniteException If {@link IOException} occurred. |
| * |
| * @see DefragmentationFileUtils#writeDefragmentationCompletionMarker(FileIOFactory, File, IgniteLogger) |
| */ |
| public static void batchRenameDefragmentedCacheGroupPartitions(File workDir, IgniteLogger log) throws IgniteException { |
| File completionMarkerFile = defragmentationCompletionMarkerFile(workDir); |
| |
| if (!completionMarkerFile.exists()) |
| return; |
| |
| try { |
| for (File mappingFile : workDir.listFiles((dir, name) -> name.startsWith(DFRG_LINK_MAPPING_FILE_PREFIX))) |
| Files.delete(mappingFile.toPath()); |
| |
| for (File partFile : workDir.listFiles((dir, name) -> name.startsWith(DFRG_PARTITION_FILE_PREFIX))) { |
| int partId = extractPartId(partFile.getName()); |
| |
| File oldPartFile = new File(workDir, String.format(PART_FILE_TEMPLATE, partId)); |
| |
| Files.move(partFile.toPath(), oldPartFile.toPath(), ATOMIC_MOVE, REPLACE_EXISTING); |
| } |
| |
| File idxFile = new File(workDir, DFRG_INDEX_FILE_NAME); |
| |
| if (idxFile.exists()) { |
| File oldIdxFile = new File(workDir, INDEX_FILE_NAME); |
| |
| Files.move(idxFile.toPath(), oldIdxFile.toPath(), ATOMIC_MOVE, REPLACE_EXISTING); |
| } |
| } |
| catch (IOException e) { |
| throw new IgniteException(e); |
| } |
| } |
| |
| /** |
| * Extracts partition number from file names like {@code part-dfrg-%d.bin}. |
| * |
| * @param dfrgPartFileName Defragmented partition file name. |
| * @return Partition index. |
| * |
| * @see DefragmentationFileUtils#defragmentedPartFile(File, int) |
| */ |
| private static int extractPartId(String dfrgPartFileName) { |
| assert dfrgPartFileName.startsWith(DFRG_PARTITION_FILE_PREFIX) : dfrgPartFileName; |
| assert dfrgPartFileName.endsWith(FILE_SUFFIX) : dfrgPartFileName; |
| |
| String partIdStr = dfrgPartFileName.substring( |
| DFRG_PARTITION_FILE_PREFIX.length(), |
| dfrgPartFileName.length() - FILE_SUFFIX.length() |
| ); |
| |
| return Integer.parseInt(partIdStr); |
| } |
| |
| /** |
| * Return file named {@code index-dfrg.bin.tmp} in given folder. It will be used for storing defragmented index |
| * partition during the process. |
| * |
| * @param workDir Cache group working directory. |
| * @return File. |
| * |
| * @see DefragmentationFileUtils#defragmentedIndexFile(File) |
| */ |
| public static File defragmentedIndexTmpFile(File workDir) { |
| return new File(workDir, DFRG_INDEX_TMP_FILE_NAME); |
| } |
| |
| /** |
| * Return file named {@code index-dfrg.bin} in given folder. It will be used for storing defragmented index |
| * partition when the process is over. |
| * |
| * @param workDir Cache group working directory. |
| * @return File. |
| * |
| * @see DefragmentationFileUtils#defragmentedIndexTmpFile(File) |
| */ |
| public static File defragmentedIndexFile(File workDir) { |
| return new File(workDir, DFRG_INDEX_FILE_NAME); |
| } |
| |
| /** |
| * Rename temporary index defragmentation file to a finalized one. |
| * |
| * @param workDir Cache group working directory. |
| * @throws IgniteException If {@link IOException} occurred. |
| * |
| * @see DefragmentationFileUtils#defragmentedIndexTmpFile(File) |
| * @see DefragmentationFileUtils#defragmentedIndexFile(File) |
| */ |
| public static void renameTempIndexFile(File workDir) throws IgniteException { |
| File defragmentedIdxTmpFile = defragmentedIndexTmpFile(workDir); |
| File defragmentedIdxFile = defragmentedIndexFile(workDir); |
| |
| try { |
| Files.move(defragmentedIdxTmpFile.toPath(), defragmentedIdxFile.toPath(), ATOMIC_MOVE); |
| } |
| catch (IOException e) { |
| throw new IgniteException(e); |
| } |
| } |
| |
| /** |
| * Return file named {@code part-dfrg-%d.bin.tmp} in given folder. It will be used for storing defragmented data |
| * partition during the process. |
| * |
| * @param workDir Cache group working directory. |
| * @param partId Partition index, will be substituted into file name. |
| * @return File. |
| * |
| * @see DefragmentationFileUtils#defragmentedPartFile(File, int) |
| */ |
| public static File defragmentedPartTmpFile(File workDir, int partId) { |
| return new File(workDir, String.format(DFRG_PARTITION_TMP_FILE_TEMPLATE, partId)); |
| } |
| |
| /** |
| * Return file named {@code part-dfrg-%d.bin} in given folder. It will be used for storing defragmented data |
| * partition when the process is over. |
| * |
| * @param workDir Cache group working directory. |
| * @param partId Partition index, will be substituted into file name. |
| * @return File. |
| * |
| * @see DefragmentationFileUtils#defragmentedPartTmpFile(File, int) |
| */ |
| public static File defragmentedPartFile(File workDir, int partId) { |
| return new File(workDir, String.format(DFRG_PARTITION_FILE_TEMPLATE, partId)); |
| } |
| |
| /** |
| * Rename temporary partition defragmentation file to a finalized one. |
| * |
| * @param workDir Cache group working directory. |
| * @param partId Partition index. |
| * @throws IgniteException If {@link IOException} occurred. |
| * |
| * @see DefragmentationFileUtils#defragmentedPartTmpFile(File, int) |
| * @see DefragmentationFileUtils#defragmentedPartFile(File, int) |
| */ |
| public static void renameTempPartitionFile(File workDir, int partId) throws IgniteException { |
| File defragmentedPartTmpFile = defragmentedPartTmpFile(workDir, partId); |
| File defragmentedPartFile = defragmentedPartFile(workDir, partId); |
| |
| assert !defragmentedPartFile.exists() : defragmentedPartFile; |
| |
| try { |
| Files.move(defragmentedPartTmpFile.toPath(), defragmentedPartFile.toPath(), ATOMIC_MOVE); |
| } |
| catch (IOException e) { |
| throw new IgniteException(e); |
| } |
| } |
| |
| /** |
| * Return file named {@code part-map-%d.bin} in given folder. It will be used for storing defragmention links |
| * mapping for given partition during and after defragmentation process. No temporary counterpart is required here. |
| * |
| * @param workDir Cache group working directory. |
| * @param partId Partition index, will be substituted into file name. |
| * @return File. |
| * |
| * @see LinkMap |
| */ |
| public static File defragmentedPartMappingFile(File workDir, int partId) { |
| return new File(workDir, String.format(DFRG_LINK_MAPPING_FILE_TEMPLATE, partId)); |
| } |
| |
| /** |
| * Return defragmentation completion marker file. This file can only be created when all partitions and index are |
| * defragmented and renamed from their original {@code *.tmp} versions. Presence of this file signals that no data |
| * will be lost if original partitions are deleted and batch rename process can be safely initiated. |
| * |
| * @param workDir Cache group working directory. |
| * @return File. |
| * |
| * @see DefragmentationFileUtils#writeDefragmentationCompletionMarker(FileIOFactory, File, IgniteLogger) |
| * @see DefragmentationFileUtils#batchRenameDefragmentedCacheGroupPartitions(File, IgniteLogger) |
| */ |
| public static File defragmentationCompletionMarkerFile(File workDir) { |
| return new File(workDir, DFRG_COMPLETION_MARKER_FILE_NAME); |
| } |
| |
| /** |
| * Creates empty completion marker file in given directory. |
| * |
| * @param ioFactory File IO factory. |
| * @param workDir Cache group working directory. |
| * @param log Logger to write messages. |
| * @throws IgniteException If {@link IOException} occurred. |
| * |
| * @see DefragmentationFileUtils#defragmentationCompletionMarkerFile(File) |
| */ |
| public static void writeDefragmentationCompletionMarker( |
| FileIOFactory ioFactory, |
| File workDir, |
| IgniteLogger log |
| ) throws IgniteException { |
| File completionMarker = defragmentationCompletionMarkerFile(workDir); |
| |
| try (FileIO io = ioFactory.create(completionMarker, CREATE_NEW, WRITE)) { |
| io.force(true); |
| } |
| catch (IOException e) { |
| throw new IgniteException(e); |
| } |
| } |
| } |