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

oracle.kv.impl.tif.CheckpointManager Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

The newest version!
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.tif;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import oracle.kv.impl.tif.esclient.esResponse.GetResponse;
import oracle.kv.impl.tif.esclient.jsonContent.ESJsonBuilder;
import oracle.kv.impl.tif.esclient.restClient.utils.ESRestClientUtil;
import oracle.kv.impl.topo.PartitionId;

import com.fasterxml.jackson.core.JsonGenerator;
import com.sleepycat.je.utilint.VLSN;

/**
 * Object to manage and send checkpoint TIF state to ES cluster.
 */
class CheckpointManager {

    /* Index properties for the checkpoint index.
     * TODO: make these values parameters.
     */
    static final Map indexProperties;
    static {
        indexProperties = new HashMap<>();
        indexProperties.put(ElasticsearchHandler.SHARDS_PROPERTY, "1");
        indexProperties.put(ElasticsearchHandler.REPLICAS_PROPERTY, "1");
    }

    /* for logging */
    private final Logger logger;
    private final SimpleDateFormat df =
        new SimpleDateFormat("EEE," + " d MMM yyyy HH:mm:ss Z");

    /* ES client and admin client used to do ES operations */
    private final ElasticsearchHandler esHandler;

    /* name of ES index to store checkpoint state */
    private final String esCkptIndexName;
    private final String esCkptIndexType;
    /* per replication group key to store the checkpoint state in ES */
    private final String esCheckpointKey;

    /* parent TIF of the checkpoint manager */
    private final TextIndexFeeder feeder;

    /* schedule handle and thread pool to schedule timed tasks */
    private ScheduledExecutorService scheduledThreadPool;
    private ScheduledFuture schedulerHandle;

    /* checkpoint interval in seconds */
    private long ckptIntervalSecs;

    /* number of checkpoints have been made since manager starts */
    private long numCheckpointDone;
    private long lastCheckpointTime;

    /*
     * Checkpoint Manager depends upon proper communication
     * with ES and hence throws IOException.
     *
     * Checkpoint indices and mappings are created on ES
     * during construction of this object.
     *
     */
    CheckpointManager(TextIndexFeeder feeder,
                      String esCkptIndexName,
                      String esCkptIndexType,
                      String esCheckpointKey,
                      ElasticsearchHandler esHandler,
                      long checkpointIntervalMs,
                      Logger logger)
                              throws IllegalStateException, IOException {
        this.feeder = feeder;
        this.esCkptIndexName = esCkptIndexName;
        this.esCkptIndexType = esCkptIndexType;
        this.esCheckpointKey = esCheckpointKey;
        this.esHandler = esHandler;
        this.logger = logger;

        schedulerHandle = null;
        numCheckpointDone = 0;
        lastCheckpointTime = 0;

        /* initialized as constant but can be adjusted by setter API */
        ckptIntervalSecs = checkpointIntervalMs / 1000;
        ensureCkptESIndexAndMapping();
    }

    /* only for test, create manager without TIF */
    CheckpointManager(String indexName,
                      String indexType,
                      ElasticsearchHandler handler,
                      Logger l) throws IllegalStateException, IOException {

        this(null, indexName, indexType, TextIndexFeeder.CHECKPOINT_KEY_PREFIX,
             handler, 120000, l);
    }

    /**
     * Stops checkpoint manager, cancel scheduled checkpoint if exists
     */
    void stop() {

        if (schedulerHandle != null && !schedulerHandle.isCancelled()) {
            schedulerHandle.cancel(false);
            schedulerHandle = null;
        }

        scheduledThreadPool.shutdown();
        while (!scheduledThreadPool.isTerminated()) {
            /* wait for all tasks to finish */
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Starts checkpoint activity
     */
    void startCheckpoint() {
        /* periodically repeating checkpoint */
        startCheckpoint(true);
    }

    /**
     * Starts checkpoint activity
     *
     * @param repeatCheckpoint  true if repeating checkpoint periodically,
     *                          false if once-time checkpoint
     */
    void startCheckpoint(boolean repeatCheckpoint) {
        scheduledThreadPool = Executors.newScheduledThreadPool(1);

        /* schedule the first checkpoint */
        final CheckpointThread firstCkpt =
            new CheckpointThread(repeatCheckpoint);
        schedulerHandle = scheduledThreadPool.schedule(firstCkpt,
                                                       getCkptIntervalSecs(),
                                                       TimeUnit.SECONDS);
        final long next =
            System.currentTimeMillis() + getCkptIntervalSecs() * 1000;
        logger.log(Level.INFO,
                   "The first checkpoint to ES index {0} scheduled {1} " +
                   "seconds later at time {2}",
                   new Object[]{esCkptIndexName, getCkptIntervalSecs(),
                       df.format(new Date(next))});
    }

    /**
     * Cancels all future checkpoints
     */
    void cancelCheckpoint() {

        /* if checkpoint is in-progress, allow it to finish */
        if (schedulerHandle != null) {
            schedulerHandle.cancel(false);
            logger.log(Level.INFO,
                       "Future checkpoint cancelled for ES index {0}.",
                       esCkptIndexName);
        }
    }

    /**
     * Gets the checkpoint interval in seconds
     *
     * @return interval in seconds
     */
    long getCkptIntervalSecs() {
        return ckptIntervalSecs;
    }

    /**
     * Sets a new checkpoint interval in seconds. The new interval
     * will be effective when scheduling the next checkpoint
     *
     * @param interval checkpoint interval in seconds
     */
    void setCkptIntervalSecs(long interval) {
        ckptIntervalSecs = interval;
    }

    /**
     * Fetches a checkpoint state from ES index
     *
     * @return checkpoint state, null if no checkpoint state is found in ES
     * index.
     */
    CheckpointState fetchCkptFromES() {

        try {
            final GetResponse response =
                esHandler.get(esCkptIndexName, esCkptIndexType,
                              esCheckpointKey);

            /* For SR27091 - test the response for null; to avoid NPE. */
            if (response == null) {
                if (logger.isLoggable(Level.INFO)) {
                    logger.info(" ");
                    logger.info("CheckpointManager.fetchCkptFromES: " +
                                "checkpoint request resulted in null " +
                                "response from ElasticsearchHandler");
                    logger.info(" ");
                }
                return null;
            }

            final Map rawState = response.sourceAsMap();
            if (!response.isFound() || rawState == null) {
                return null;
            }
            return new CheckpointState(rawState);
        } catch (Exception e) {
            if (logger.isLoggable(Level.WARNING)) {
                logger.warning(" ");
                logger.log(Level.WARNING,
                    "CheckpointManager.fetchCkptFromES: checkpoint request " +
                    "resulted in exception from ElasticsearchHandler " +
                    "[exception=" + e + ", cause=" +e.getCause() + "]", e);
                logger.warning(" ");
            }
            return null;
        }
    }

    /**
     * Deletes a checkpoint state from ES index
     */
    void deleteCheckpointFromES() throws IllegalStateException {

        try {
            esHandler.del(esCkptIndexName, esCkptIndexType, esCheckpointKey);
        } catch (IOException e) {
            logger.severe("Failed to delete checkpoint due to:" + e);
            throw new IllegalStateException(e);
        }

    }

    /**
     * Gets number of successful checkpoints to ES
     *
     * @return number of successful checkpoints to ES
     */
    long getNumCheckpointDone() {
        return numCheckpointDone;
    }

    /**
     * Gets the time of the last successful checkpoint
     *
     * @return the time of the last successful checkpoint
     */
    Date getLastCheckpointTime() {
        return new Date(lastCheckpointTime);
    }

    /**
     * Commits a checkpoint to ES index
     *
     * @param state state of checkpoint to commit
     */
    void doCheckpoint(CheckpointState state) {

        if (state == null) {
            /* no checkpoint to commit */
            return;
        }

        try {
            /* The returned IndexResponse will indicate only success.  Failures
             * at this interface are reported by exception.
             */
            esHandler.index(makePutOpFromCkpt(state));
            numCheckpointDone++;
            lastCheckpointTime = System.currentTimeMillis();
        } catch (Exception e) {
            /*
             * It is undocumented what types of exceptions may be thrown
             * here.  The most likely issue is that ES is unavailable,
             * which should be a temporary situation.  If the checkpoint
             * fails repeatedly, it is likely that other indexing
             * operations are also failing. For now, log the failure but
             * don't log the stack trace (which may un-necessarily alarm
             * or confuse the user); and hope for success the next time
             * around.
             */
            logger.log(Level.WARNING,
                       "Checkpoint failed, reason: " + e.getMessage());
        }
    }

    /*
     * Returns a put operation containing a JSON representation of a checkpoint
     * state suitable for indexing in ES.
     */
    private IndexOperation makePutOpFromCkpt(CheckpointState state) {

        try {
            /* create a document with all checkpoint field */
            final ESJsonBuilder document = ESJsonBuilder.builder();
            document.startStructure();

            for (Map.Entry entry :
                state.getFieldsNameValue().entrySet()) {

                final String k = entry.getKey();
                final String v = entry.getValue();

                document.field(k, v);
            }

            document.endStructure();
            /* create an input op from document and key */
            return new IndexOperation(esCkptIndexName,
                                      esCkptIndexType,
                                      esCheckpointKey,
                                      document.byteArray(),
                                      IndexOperation.OperationType.PUT);
        } catch (IOException e) {
            throw new IllegalStateException(
                          "Unable to serialize ES document " +
                          "for checkpoint: " + toString());
        }
    }

    /* Ensures the checkpoint ES index and mapping exist */
    private void ensureCkptESIndexAndMapping()
        throws IllegalStateException,
        IOException {
        /* create a new ckpt ES index if it does not exists */
        if (!esHandler.existESIndex(esCkptIndexName)) {
            esHandler.createESIndex(esCkptIndexName, indexProperties);
        }

        /* create a ckpt ES index mapping if it does not exists */
        final JsonGenerator mappingSpec = buildCkptMappingSpec();
        if (!esHandler.existESIndexMapping(esCkptIndexName, esCkptIndexType)) {
            esHandler.createESIndexMapping(esCkptIndexName, esCkptIndexType,
                                           mappingSpec);
        } else {
            /* if a mapping already exists, verify it first */
            final String existingMapping =
                    esHandler.getESIndexMapping(esCkptIndexName,
                                                esCkptIndexType);
            if (ESRestClientUtil.isMappingResponseEqual(existingMapping,
                                                        mappingSpec,
                                                        esCkptIndexName,
                                                        esCkptIndexType)) {
                /* same mapping already exists, done */
                logger.log(Level.INFO,
                           "Same checkpoint mapping found in ES index {0}, " +
                           "use it.",
                           esCkptIndexName);
            } else {
                /*
                 * existing mapping is different, since unable to delete
                 * a mapping, we simply delete the whole ES index and create a
                 * new one, and create new mapping.
                 */
                logger.log(Level.INFO,
                           "Different checkpoint mapping found in ES index " +
                           "{0}, delete it and create a new index and mapping.",
                           esCkptIndexName);
                esHandler.deleteESIndex(esCkptIndexName);
                esHandler.createESIndex(esCkptIndexName, indexProperties);
                esHandler.createESIndexMapping(esCkptIndexName,
                                               esCkptIndexType, mappingSpec);
            }
        }

        assert (esHandler.existESIndexMapping(esCkptIndexName,
                                              esCkptIndexType));
        logger.log(Level.INFO,
                   "ES index for checkpoint {0} and mapping {1} are ready.",
                   new Object[]{esCkptIndexName, esCkptIndexType});
    }

    /* Creates a JSON to describe an ES type mapping for checkpoint state */
    private JsonGenerator buildCkptMappingSpec() {

        try {
            final ESJsonBuilder mapping = ESJsonBuilder.builder();

            mapping.startStructure().field("dynamic", "false")
                   .startStructure("properties");

            final Set allFields =
                new CheckpointState().getFieldsNameValue().keySet();
            for (String key : allFields) {
                mapping.startStructure(key)
                       /* for checkpoint state, no need to index any field. */
                       .field("index", "no")
                       /* all fields are string type */
                       .field("type", "string")
                       .endStructure();
            }
            mapping.endStructure(); // properties end
            mapping.endStructure(); // mapping end

            return mapping.jsonGenarator();
        } catch (IOException e) {
            throw new IllegalStateException
            ("Unable to serialize ES mapping for checkpoint index " +
             esCkptIndexName, e);
        }
    }

    /* get a dummy checkpoint state for test only */
    private CheckpointState getDummyCkptStateForTest() {
        final CheckpointState state = new CheckpointState();
        state.setCheckpointTimeStamp(System.currentTimeMillis());
        state.setCheckpointVLSN(new VLSN(1000));
        state.setSrcRepNode("rg1-rn1");
        state.setRepGroupUUID(UUID.fromString
            ("5e40b8a9-cbff-460d-b7b0-618447426c5d"));
        state.setGroupName("rg1");


        final Set comp = new HashSet<>();
        comp.add(new PartitionId(1));
        comp.add(new PartitionId(100));
        comp.add(new PartitionId(12345));
        state.setCompleteTransParts(comp);

        return state;
    }

    /*
     * Checkpoint thread to conduct checkpoint
     */
    private class CheckpointThread implements Runnable {
        private boolean scheduleNext;

        public CheckpointThread() {
            scheduleNext = true;
        }

        /* for test only */
        public CheckpointThread(boolean next) {
            this();
            scheduleNext = next;
        }

        @Override
        public void run() {

            final long start = System.currentTimeMillis();
            logger.log(Level.FINE,
                       "Do checkpoint to ES index {0} at time {1}.",
                       new Object[]{esCkptIndexName,
                           df.format(new Date(start))});

            /* get checkpoint from tif and commit to ES */
            if (feeder != null) {
                doCheckpoint(feeder.prepareCheckpointState());
            } else {
                /* for test only, no tif */
                doCheckpoint(getDummyCkptStateForTest());
            }

            /* scheduled next checkpoint thread */
            if (scheduleNext) {
                final CheckpointThread nextCkpt = new CheckpointThread();

                schedulerHandle =
                    scheduledThreadPool.schedule(nextCkpt,
                                                 getCkptIntervalSecs(),
                                                 TimeUnit.SECONDS);

                final long next = System.currentTimeMillis() +
                            (1000 * getCkptIntervalSecs());
                logger.log(Level.FINE,
                           "Next checkpoint to {0} is scheduled {1} seconds " +
                           "later at time {2}.",
                           new Object[]{esCkptIndexName, getCkptIntervalSecs(),
                               df.format(next) });
            }

            logger.log(Level.FINE,
                       "Finish checkpoint to ES index {0}, time elapsed: " +
                       "{1} ms",
                       new Object[]{esCkptIndexName,
                           (System.currentTimeMillis() - start)});
        }

        @Override
        public String toString() {
            return "Thread of checkpoint to ES index " + esCkptIndexName;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy