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

io.mapsmessaging.storage.impl.file.PartitionStorage Maven / Gradle / Ivy

There is a newer version: 2.4.10
Show newest version
/*
 *   Copyright [2020 - 2022]   [Matthew Buckton]
 *
 *    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.mapsmessaging.storage.impl.file;

import io.mapsmessaging.storage.BaseExpiredHandler;
import io.mapsmessaging.storage.ExpiredMonitor;
import io.mapsmessaging.storage.ExpiredStorableHandler;
import io.mapsmessaging.storage.Statistics;
import io.mapsmessaging.storage.Storable;
import io.mapsmessaging.storage.Storage;
import io.mapsmessaging.storage.StorageStatistics;
import io.mapsmessaging.storage.impl.expired.ExpireStorableTaskManager;
import io.mapsmessaging.storage.impl.file.partition.IndexGet;
import io.mapsmessaging.storage.impl.file.partition.IndexRecord;
import io.mapsmessaging.storage.impl.file.partition.IndexStorage;
import io.mapsmessaging.storage.impl.file.tasks.ArchiveMonitorTask;
import io.mapsmessaging.storage.impl.file.tasks.DeletePartitionTask;
import io.mapsmessaging.storage.impl.file.tasks.FileTask;
import io.mapsmessaging.utilities.collections.NaturalOrderedLongList;
import io.mapsmessaging.utilities.collections.NaturalOrderedLongQueue;
import io.mapsmessaging.utilities.collections.bitset.BitSetFactory;
import io.mapsmessaging.utilities.collections.bitset.BitSetFactoryImpl;
import io.mapsmessaging.utilities.threads.tasks.TaskScheduler;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class PartitionStorage implements Storage, ExpiredMonitor {

  private static final String PARTITION_FILE_NAME = "partition_";

  private final ExpiredStorableHandler expiredHandler;

  @Getter
  private final TaskQueue taskScheduler;

  private final int itemCount;
  private final ExpireStorableTaskManager expiredMonitor;
  private final PartitionStorageConfig config;

  private final List> partitions;
  private final String fileName;
  private final String rootDirectory;
  private final long archiveIdleTime;

  private final LongAdder reads;
  private final LongAdder writes;
  private final LongAdder deletes;

  private final LongAdder readTimes;
  private final LongAdder writeTimes;

  private final LongAdder byteWrites;
  private final LongAdder byteReads;

  private boolean shutdown;
  private boolean paused;
  private long partitionCounter;
  private long lastKeyStored;
  private long lastAccess;

  public PartitionStorage(PartitionStorageConfig config) throws IOException{
    this.config = config;
    partitions = new ArrayList<>();
    taskScheduler = config.getTaskQueue();
    rootDirectory = config.getFileName();
    this.expiredHandler = Objects.requireNonNullElseGet(config.getExpiredHandler(), () -> new BaseExpiredHandler<>(this));
    this.itemCount = config.getItemCount();
    this.fileName = config.getFileName() + File.separator + PARTITION_FILE_NAME;
    archiveIdleTime = config.getArchiveIdleTime();
    partitionCounter = 0;
    shutdown = false;
    File location = new File(config.getFileName());
    expiredMonitor = new ExpireStorableTaskManager<>(this, taskScheduler, config.getExpiredEventPoll());
    if (location.exists()) {
      reload(location);
    } else {
      location.mkdir();
      locateOrCreatePartition(0); // Force the creation of the key file
    }
    reads = new LongAdder();
    writes = new LongAdder();
    readTimes = new LongAdder();
    writeTimes = new LongAdder();
    deletes = new LongAdder();
    byteWrites = new LongAdder();
    byteReads = new LongAdder();
    lastKeyStored = -2;
    lastAccess = System.currentTimeMillis();
  }

  @Override
  public String getName() {
    return rootDirectory;
  }

  @Override
  public void shutdown() throws IOException {
    shutdown = true;
    expiredMonitor.close();

    while (taskScheduler.hasTasks()) {
      taskScheduler.executeTasks();
    }
    taskScheduler.abortAll(); // We are about to delete the partition, any tasks can be cancelled now
  }


  @Override
  public void close() throws IOException {
    if (paused) {
      resume(); // need to resume it to set state successfully
    }
    expiredMonitor.close();
    for (IndexStorage partition : partitions) {
      partition.close();
    }
    partitions.clear();
  }

  @Override
  public void delete() throws IOException {
    if (!shutdown) {
      shutdown();
    }
    if (paused) {
      resume();
    }
    for (IndexStorage partition : partitions) {
      partition.delete();
    }
    partitions.clear();
    File file = new File(rootDirectory);
    String[] children = file.list();
    if (children != null) {
      for (String child : children) {
        File t = new File(child);
        Files.deleteIfExists(t.toPath());
      }
    }
    children = file.list();
    if (children == null || children.length == 0) {
      Files.deleteIfExists(file.toPath());
    }
  }

  @Override
  public boolean supportPause() {
    return true;
  }

  @Override
  public void pause() throws IOException {
    if (!paused) {
      paused = true;
      for (IndexStorage partition : partitions) {
        partition.pause();
      }
      expiredMonitor.pause();
    }
  }

  @Override
  public void resume() throws IOException {
    if (paused) {
      paused = false;
      for (IndexStorage partition : partitions) {
        partition.resume();
      }
      expiredMonitor.resume();
    }

  }

  @Override
  public void add(@NotNull T object) throws IOException {
    if (paused) {
      resume();
    }
    lastAccess = System.currentTimeMillis();
    long time = System.currentTimeMillis();
    IndexStorage partition = locateOrCreatePartition(object.getKey());
    IndexRecord indexRecord = partition.add(object);
    if (partition.isFull()) {
      partition.setEnd(object.getKey());
    }
    expiredMonitor.added(object);
    byteReads.add(IndexRecord.HEADER_SIZE); // We read the header to check for duplicates
    byteWrites.add(indexRecord.getLength());
    writes.increment();
    writeTimes.add((System.currentTimeMillis() - time));
    if (getLastKey() < object.getKey()) {
      lastKeyStored = object.getKey();
    }
  }

  @Override
  public boolean remove(long key) throws IOException {
    if (paused) {
      resume();
    }
    lastAccess = System.currentTimeMillis();

    IndexStorage partition = locatePartition(key);
    if (partition != null && partition.remove(key)) {
      deletes.increment();
      if (partition.isEmpty() && partitions.size() > 1) {
        partitions.remove(partition);
        submit(new DeletePartitionTask<>(partition));
      }
      byteReads.add(IndexRecord.HEADER_SIZE); // We read it first
      byteWrites.add(IndexRecord.HEADER_SIZE); // We then write a block of zeros
      return true;
    }
    return false;
  }

  @Override
  public @Nullable T get(long key) throws IOException {
    if (paused) {
      resume();
    }
    lastAccess = System.currentTimeMillis();

    long time = System.currentTimeMillis();
    try {
      IndexStorage partition = locatePartition(key);
      if (partition != null) {
        IndexGet retrieved = partition.get(key);
        if (retrieved != null) {
          reads.increment();
          byteReads.add(retrieved.getIndexRecord().getLength());
          return retrieved.getObject();
        }
      }
      return null;
    } finally {
      readTimes.add((System.currentTimeMillis() - time));
    }
  }

  @Override
  public @NotNull List getKeys() {
    List keyList = new NaturalOrderedLongList();
    for (IndexStorage partition : partitions) {
      keyList.addAll(partition.getKeys());
    }
    return keyList;
  }

  @Override
  public boolean contains(long key) {
    for (IndexStorage partition : partitions) {
      if(partition.contains(key)){
        return true;
      }
    }
    return false;
  }

  @Override
  public long size() {
    long size = 0;
    for (IndexStorage partition : partitions) {
      size += partition.size();
    }
    return size;
  }

  @Override
  public long getLastKey() {
    if(lastKeyStored == -2){
      lastKeyStored = reloadLastKeyStore();
    }
    return lastKeyStored;
  }

  @Override
  public long getLastAccess() {
    return lastAccess;
  }

  @Override
  public void updateLastAccess() {
    lastAccess = System.currentTimeMillis();
  }

  public long length() throws IOException {
    if (paused) {
      resume();
    }

    long length = 0;
    for (IndexStorage partition : partitions) {
      length += partition.length();
    }
    return length;
  }

  public long emptySpace() {
    long emptySpace = 0;
    for (IndexStorage partition : partitions) {
      emptySpace += partition.emptySpace();
    }
    return emptySpace;
  }

  @Override
  public boolean isEmpty() {
    for (IndexStorage partition : partitions) {
      if (!partition.isEmpty()) {
        return false;
      }
    }
    return true;
  }

  public void scanForExpired() throws IOException {
    if (!paused) {
      try (BitSetFactory bitSetFactory = new BitSetFactoryImpl(8192)) {
        Queue expiredList = new NaturalOrderedLongQueue(0, bitSetFactory);
        for (IndexStorage partition : partitions) {
          partition.scanForExpired(expiredList);
        }
        if (!expiredList.isEmpty()) {
          expiredHandler.expired(expiredList);
          expiredMonitor.schedulePoll();
        }
      }
    }
  }

  public void scanForArchiveMigration() throws IOException {
    if(archiveIdleTime > 0) {
      long archiveThreshold = System.currentTimeMillis() - archiveIdleTime;
      for(int x=0;x partition = partitions.get(x);
        if (!partition.isArchived() && partition.getLastAccess() < archiveThreshold) {
          partition.archive();
        }
      }
    }
  }

  @Override
  public @NotNull Collection keepOnly(@NotNull Collection listToKeep) {
    for (IndexStorage partition : partitions) {
      listToKeep = partition.keepOnly(listToKeep);
    }
    return listToKeep;
  }

  @Override
  public int removeAll(@NotNull Collection listToRemove) {
    int counter = 0;
    for (IndexStorage partition : partitions) {
      counter += partition.removeAll(listToRemove);
    }
    return counter;
  }

  @Override
  public void setExecutor(TaskScheduler scheduler) {
    taskScheduler.setTaskScheduler(scheduler);
    taskScheduler.scheduleAtFixedRate(new ArchiveMonitorTask(this), 10, TimeUnit.SECONDS);
  }

  @Override
  public boolean executeTasks() throws IOException {
    return taskScheduler.executeTasks();
  }

  public @NotNull Statistics getStatistics() {
    long length;
    try {
      length = length();
    } catch (IOException e) {
      length = -1;
    }

    return new StorageStatistics(
        reads.sumThenReset(),
        writes.sumThenReset(),
        deletes.sumThenReset(),
        byteReads.sumThenReset(),
        byteWrites.sumThenReset(),
        readTimes.sumThenReset(),
        writeTimes.sumThenReset(),
        length,
        emptySpace(),
        partitions.size()
    );
  }

  private long reloadLastKeyStore() {
    if (!partitions.isEmpty()) {
      return (partitions.get(partitions.size() - 1).getLastKey());
    }
    return 0;
  }

  private @Nullable IndexStorage locatePartition(long key) {
    for (IndexStorage partition : partitions) {
      if (partition.getStart() <= key && key <= partition.getEnd()) {
        return partition;
      }
    }
    return null;
  }

  private @NotNull IndexStorage locateOrCreatePartition(long key) throws IOException {
    IndexStorage partition = scanForPartition(key);
    if (partition == null) {
      String partitionName = fileName + partitionCounter++;
      long start = 0;
      if (!partitions.isEmpty()) {
        start = partitions.get(partitions.size() - 1).getEnd() + 1;
      }
      if (key < start || key > (start + itemCount)) {
        start = key;
      }
      partition = new IndexStorage<>(config, partitionName, start, taskScheduler);
      partitions.add(partition);
      partitions.sort(Comparator.comparingLong(IndexStorage::getStart));
    }
    return partition;
  }

  private IndexStorage scanForPartition(long key) throws IOException {
    List> empty = new ArrayList<>();
    try {
      for (IndexStorage partition : partitions) {
        if (partition.getStart() <= key && key <= partition.getEnd()) {
          return partition;
        }
        if (partition.isEmpty()) {
          empty.add(partition);
        }
      }
    } finally {
      if (!empty.isEmpty()) {
        for (IndexStorage remove : empty) {
          partitions.remove(remove);
          taskScheduler.submit(new DeletePartitionTask<>(remove));
        }
      }
    }
    return null;
  }


  private void reload(File location) throws IOException {
    if (location.isDirectory()) {
      String[] childFiles = location.list();
      if (childFiles != null) {
        AtomicBoolean hasExpired = new AtomicBoolean(false);
        AtomicReference exception = new AtomicReference<>();
        Arrays.stream(childFiles).parallel().forEach(test -> {
          try {
            hasExpired.set(loadStore(test) || hasExpired.get());
          } catch (IOException e) {
            exception.set(e);
          }
        });
        if(exception.get() != null){
          throw exception.get();
        }
        if (hasExpired.get()) {
          expiredMonitor.schedulePoll();
        }
      }
    }
    partitions.sort(Comparator.comparingLong(IndexStorage::getStart));
    scanForEmpty();
  }

  private void submit(FileTask task) throws IOException {
    taskScheduler.submit(task);
  }

  private void scanForEmpty() throws IOException {
    List> emptyReloads = new ArrayList<>();
    partitions.stream().parallel().forEach(tIndexStorage -> {
      if(tIndexStorage.isEmpty()){
        emptyReloads.add(tIndexStorage);
      }
    });

    if (partitions.size() > 1) {
      // OK we have them simply remove them and schedule delete task
      for (IndexStorage storage : emptyReloads) {
        partitions.remove(storage);
        submit(new DeletePartitionTask<>(storage));
        if (partitions.size() == 1) {
          break;
        }
      }
    }
  }

  private boolean loadStore(String test) throws IOException {
    if (test.startsWith(PARTITION_FILE_NAME) && test.endsWith("index")) {
      String loadName = test.substring(PARTITION_FILE_NAME.length(), test.length() - "_index".length());
      IndexStorage indexStorage = new IndexStorage<>(config,fileName + loadName,  0, taskScheduler);
      synchronized (partitions) {
        partitions.add(indexStorage);
        int partNumber = extractPartitionNumber(loadName);
        if (partNumber > partitionCounter) {
          partitionCounter = partNumber;
        }
      }
      return (indexStorage.hasExpired());
    }
    return false;
  }

  private int extractPartitionNumber(String name) {
    return Integer.parseInt(name.trim());
  }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy