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

io.atomix.raft.storage.RaftStorage Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015-present Open Networking Foundation
 * Copyright © 2020 camunda services GmbH ([email protected])
 *
 * Licensed 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 io.atomix.raft.storage;

import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import io.atomix.raft.storage.log.RaftLog;
import io.atomix.raft.storage.log.RaftLogFlusher;
import io.atomix.raft.storage.system.MetaStore;
import io.atomix.utils.concurrent.ThreadContext;
import io.atomix.utils.concurrent.ThreadContextFactory;
import io.camunda.zeebe.snapshots.PersistedSnapshotStore;
import io.camunda.zeebe.snapshots.ReceivableSnapshotStore;
import io.camunda.zeebe.util.FileUtil;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;

/**
 * Immutable log configuration and {@link RaftLog} factory.
 *
 * 

This class provides a factory for {@link RaftLog} objects. {@code Storage} objects are * immutable and can be created only via the {@link RaftStorage.Builder}. To create a new {@code * Storage.Builder}, use the static {@link #builder()} factory method: * *

{@code
 * Storage storage = Storage.builder()
 *   .withDirectory(new File("logs"))
 *   .withStorageLevel(StorageLevel.DISK)
 *   .build();
 *
 * }
* * @see RaftLog */ public final class RaftStorage { private final String prefix; private final int partitionId; private final File directory; private final int maxSegmentSize; private final long freeDiskSpace; private final ReceivableSnapshotStore persistedSnapshotStore; private final int journalIndexDensity; private final boolean preallocateSegmentFiles; private final RaftLogFlusher.Factory flusherFactory; private RaftStorage( final String prefix, final int partitionId, final File directory, final int maxSegmentSize, final long freeDiskSpace, final RaftLogFlusher.Factory flusherFactory, final ReceivableSnapshotStore persistedSnapshotStore, final int journalIndexDensity, final boolean preallocateSegmentFiles) { this.prefix = prefix; this.partitionId = partitionId; this.directory = directory; this.maxSegmentSize = maxSegmentSize; this.freeDiskSpace = freeDiskSpace; this.flusherFactory = flusherFactory; this.persistedSnapshotStore = persistedSnapshotStore; this.journalIndexDensity = journalIndexDensity; this.preallocateSegmentFiles = preallocateSegmentFiles; try { FileUtil.ensureDirectoryExists(directory.toPath()); } catch (final IOException e) { throw new UncheckedIOException( String.format("Failed to create partition's directory %s", directory.toPath()), e); } } /** * Returns a new storage builder. * * @return A new storage builder. */ public static Builder builder() { return new Builder(); } /** * Returns the storage filename prefix. * * @return The storage filename prefix. */ public String prefix() { return prefix; } /** * Attempts to acquire a lock on the storage directory. * * @param id the ID with which to lock the directory * @return indicates whether the lock was successfully acquired */ public boolean lock(final String id) { final File lockFile = new File(directory, String.format(".%s.lock", prefix)); final File tempLockFile = new File(directory, String.format(".%s.lock.tmp", id)); try { if (!lockFile.exists()) { // Create and update the file atomically Files.writeString( tempLockFile.toPath(), id, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE, StandardOpenOption.SYNC); // If two nodes tries to acquire lock, move will fail with FileAlreadyExistsException FileUtil.moveDurably( tempLockFile.toPath(), lockFile.toPath(), StandardCopyOption.ATOMIC_MOVE); } // Read the lock file again to ensure that contents matches the local id final String lock = Files.readString(lockFile.toPath()); return lock != null && lock.equals(id); } catch (final FileAlreadyExistsException e) { return false; } catch (final IOException e) { throw new StorageException("Failed to acquire storage lock", e); } } /** * Opens a new {@link MetaStore}, recovering metadata from disk if it exists. * *

The meta store will be loaded from disk, or if missing, a new meta store will be created. * * @return The metastore. */ public MetaStore openMetaStore() { try { return new MetaStore(this); } catch (final IOException e) { throw new StorageException("Failed to open metastore", e); } } /** * Returns the {@link PersistedSnapshotStore}. * * @return The snapshot store. */ public ReceivableSnapshotStore getPersistedSnapshotStore() { return persistedSnapshotStore; } /** * Opens a new {@link RaftLog}, recovering the log from disk if it exists. * *

When a log is opened, the log will attempt to load segments from the storage {@link * #directory()} according to the provided log {@code name}. If segments for the given log name * are present on disk, segments will be loaded and indexes will be rebuilt from disk. If no * segments are found, an empty log will be created. * *

When log files are loaded from disk, the file names are expected to be based on the provided * log {@code name}. * * @return The opened log. */ public RaftLog openLog(final MetaStore metaStore, final ThreadContextFactory threadFactory) { return RaftLog.builder() .withName(prefix) .withPartitionId(partitionId) .withDirectory(directory) .withMaxSegmentSize(maxSegmentSize) .withFreeDiskSpace(freeDiskSpace) .withJournalIndexDensity(journalIndexDensity) .withPreallocateSegmentFiles(preallocateSegmentFiles) .withMetaStore(metaStore) .withFlusher(flusherFactory.createFlusher(threadFactory)) .build(); } @Override public String toString() { return toStringHelper(this).add("directory", directory()).toString(); } /** * Returns the storage directory. * *

The storage directory is the directory to which all {@link RaftLog}s write files. Segment * files for multiple logs may be stored in the storage directory, and files for each log instance * will be identified by the {@code name} provided when the log is {@link #openLog(MetaStore, * ThreadContext) opened}. * * @return The storage directory. */ public File directory() { return directory; } /** The ID of the partition associated with this storage. */ public int partitionId() { return partitionId; } /** * Builds a {@link RaftStorage} configuration. * *

The storage builder provides simplifies building more complex {@link RaftStorage} * configurations. To create a storage builder, use the {@link #builder()} factory method. Set * properties of the configured {@code Storage} object with the various {@code with*} methods. * Once the storage has been configured, call {@link #build()} to build the object. * *

{@code
   * Storage storage = Storage.builder()
   *   .withDirectory(new File("logs"))
   *   .withPersistenceLevel(PersistenceLevel.DISK)
   *   .build();
   *
   * }
*/ public static final class Builder implements io.atomix.utils.Builder { private static final String DEFAULT_PREFIX = "atomix"; private static final String DEFAULT_DIRECTORY = System.getProperty("atomix.data", System.getProperty("user.dir")); private static final int DEFAULT_MAX_SEGMENT_SIZE = 1024 * 1024 * 32; private static final long DEFAULT_FREE_DISK_SPACE = 1024L * 1024 * 1024; private static final RaftLogFlusher.Factory DEFAULT_FLUSHER_FACTORY = RaftLogFlusher.Factory::direct; private static final int DEFAULT_JOURNAL_INDEX_DENSITY = 100; private static final boolean DEFAULT_PREALLOCATE_SEGMENT_FILES = true; // impossible value to make it clear it's unset and there's an error private static final int DEFAULT_PARTITION_ID = -1; private String prefix = DEFAULT_PREFIX; private File directory = new File(DEFAULT_DIRECTORY); private int maxSegmentSize = DEFAULT_MAX_SEGMENT_SIZE; private long freeDiskSpace = DEFAULT_FREE_DISK_SPACE; private RaftLogFlusher.Factory flusherFactory = DEFAULT_FLUSHER_FACTORY; private ReceivableSnapshotStore persistedSnapshotStore; private int journalIndexDensity = DEFAULT_JOURNAL_INDEX_DENSITY; private boolean preallocateSegmentFiles = DEFAULT_PREALLOCATE_SEGMENT_FILES; private int partitionId = DEFAULT_PARTITION_ID; private Builder() {} /** * Sets the storage prefix. * * @param prefix The storage prefix. * @return The storage builder. */ public Builder withPrefix(final String prefix) { this.prefix = checkNotNull(prefix, "prefix cannot be null"); return this; } /** * Sets the log directory, returning the builder for method chaining. * *

The log will write segment files into the provided directory. If multiple {@link * RaftStorage} objects are located on the same machine, they write logs to different * directories. * * @param directory The log directory. * @return The storage builder. * @throws NullPointerException If the {@code directory} is {@code null} */ public Builder withDirectory(final File directory) { this.directory = checkNotNull(directory, "directory"); return this; } /** * Sets the maximum segment size in bytes, returning the builder for method chaining. * *

The maximum segment size dictates when logs should roll over to new segments. As entries * are written to a segment of the log, once the size of the segment surpasses the configured * maximum segment size, the log will create a new segment and append new entries to that * segment. * *

By default, the maximum segment size is {@code 1024 * 1024 * 32}. * * @param maxSegmentSize The maximum segment size in bytes. * @return The storage builder. * @throws IllegalArgumentException If the {@code maxSegmentSize} is not positive */ public Builder withMaxSegmentSize(final int maxSegmentSize) { this.maxSegmentSize = maxSegmentSize; return this; } /** * Sets the percentage of free disk space that must be preserved before log compaction is * forced. * * @param freeDiskSpace the free disk percentage * @return the Raft log builder */ public Builder withFreeDiskSpace(final long freeDiskSpace) { checkArgument(freeDiskSpace >= 0, "freeDiskSpace must be positive"); this.freeDiskSpace = freeDiskSpace; return this; } /** * Sets the {@link RaftLogFlusher.Factory} to create a new flushing strategy for the {@link * RaftLog} when {@link #openLog(MetaStore, ThreadContext)} is called. * * @param flusherFactory factory to create the flushing strategy for the {@link RaftLog} * @return the storage builder. */ public Builder withFlusherFactory(final RaftLogFlusher.Factory flusherFactory) { this.flusherFactory = flusherFactory; return this; } /** * Sets the snapshot store to use for remote snapshot installation. * * @param persistedSnapshotStore the snapshot store for this Raft * @return the storage builder */ public Builder withSnapshotStore(final ReceivableSnapshotStore persistedSnapshotStore) { this.persistedSnapshotStore = persistedSnapshotStore; return this; } public Builder withJournalIndexDensity(final int journalIndexDensity) { this.journalIndexDensity = journalIndexDensity; return this; } /** * Sets whether segment files are pre-allocated at creation. If true, segment files are * pre-allocated to the maximum segment size (see {@link #withMaxSegmentSize(int)}}) at creation * before any writes happen. * * @param preallocateSegmentFiles true to preallocate files, false otherwise * @return this builder for chaining */ public Builder withPreallocateSegmentFiles(final boolean preallocateSegmentFiles) { this.preallocateSegmentFiles = preallocateSegmentFiles; return this; } /** * The ID of the partition on which this storage resides. * * @param partitionId the storage's partition ID * @return this builder for chaining */ public Builder withPartitionId(final int partitionId) { this.partitionId = partitionId; return this; } /** * Builds the {@link RaftStorage} object. * * @return The built storage configuration. */ @Override public RaftStorage build() { return new RaftStorage( prefix, partitionId, directory, maxSegmentSize, freeDiskSpace, flusherFactory, persistedSnapshotStore, journalIndexDensity, preallocateSegmentFiles); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy