com.nec.strudel.tkvs.store.tokumx.TokumxTransaction.java Source code

Java tutorial

Introduction

Here is the source code for com.nec.strudel.tkvs.store.tokumx.TokumxTransaction.java

Source

/*******************************************************************************
 * Copyright 2015, 2016 Junichi Tatemura
 *
 * 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 com.nec.strudel.tkvs.store.tokumx;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;

import com.mongodb.BasicDBObject;
import com.mongodb.BulkWriteOperation;
import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.nec.strudel.tkvs.BackoffPolicy;
import com.nec.strudel.tkvs.Key;
import com.nec.strudel.tkvs.Record;
import com.nec.strudel.tkvs.RetryException;
import com.nec.strudel.tkvs.SimpleRecord;
import com.nec.strudel.tkvs.TkvStoreException;
import com.nec.strudel.tkvs.impl.CollectionBuffer;
import com.nec.strudel.tkvs.impl.KeyValueReader;
import com.nec.strudel.tkvs.impl.TransactionBaseImpl;
import com.nec.strudel.tkvs.impl.TransactionProfiler;
import com.nec.strudel.tkvs.store.tokumx.TokumxStore.TokumxDbServer;

/**
 *
 * @author tatemura, Zheng Li (initial version)
 *
 */
public class TokumxTransaction extends TransactionBaseImpl {
    private static final Logger LOGGER = Logger.getLogger(TokumxTransaction.class);
    public static final long INIT_WAIT = 1000;
    public static final int MONGO_RETRY = 10;
    public static final long MAX_WAIT_SEC = 10;
    public static final long MAX_TOTAL_SEC = 30;
    public static final BackoffPolicy MONGO_BACKOFF = BackoffPolicy.builder().maxTrial(MONGO_RETRY)
            .initWaitMs(INIT_WAIT).startBackoff(0) // backoff from the first retry
            .maxWaitMs(TimeUnit.SECONDS.toMillis(MAX_WAIT_SEC)).maxTotalMs(TimeUnit.SECONDS.toMillis(MAX_TOTAL_SEC))
            .build();
    private final BulkWriteOperation bulkwriter;
    //private final DBCollection coll;
    private final DB db;
    private final String gName;
    private final TransactionProfiler prof;
    private final TokumxReader kvs;

    public TokumxTransaction(String gName, Key gKey, String dbname, DB db, DBCollection coll,
            TransactionProfiler prof) {
        this(gName, gKey, dbname, db, new TokumxReader(coll), coll, prof);
    }

    public TokumxTransaction(String gName, Key gKey, String dbname, DB db, TokumxReader kvs, DBCollection coll,
            TransactionProfiler prof) {
        super(gName, gKey, kvs, prof);
        db.requestStart();
        this.db = db;
        //this.coll = coll;
        this.kvs = kvs;
        this.gName = gName;
        this.prof = prof;
        this.bulkwriter = coll.initializeOrderedBulkOperation();

        DBObject cmd = new BasicDBObject("beginTransaction", 1).append("isolation", "serializable");
        //Default is MVCC snapshot isolation level.
        //       DBObject cmd = new BasicDBObject("beginTransaction", 1);
        CommandResult res = runCommand(db, cmd);
        if (!res.ok()) {
            LOGGER.warn("failed to begin transaction: " + res.getErrorMessage());
        }
    }

    @SuppressWarnings("deprecation")
    CommandResult runCommand(final DB db, final DBObject cmd) {
        try {
            return MONGO_BACKOFF.call(new Callable<CommandResult>() {
                @Override
                public CommandResult call() throws Exception {
                    return db.command(cmd);
                }
            }, MongoException.Network.class);
        } catch (InterruptedException e) {
            throw new TkvStoreException("Tokumx Transaction failed", e);
        } catch (RetryException e) {
            throw new TkvStoreException("Tokumx Transaction failed", e);
        }
    }

    @Override
    public boolean commit() {
        prof.commitStart(gName);
        if (kvs.isAborted()) {
            prof.commitFail(gName);
            return false;
        }
        boolean noop = true;
        for (CollectionBuffer b : buffers()) {
            Map<Key, Record> writes = b.getWrites();
            String name = b.getName();
            for (Map.Entry<Key, Record> e : writes.entrySet()) {
                Key key = e.getKey();
                Record r = e.getValue();
                String docName = key.toStringKey(name);
                //for put
                if (r != null) {
                    //bulk or update? Tested: almost the same performance
                    if (noop) {
                        noop = false;
                    }
                    bulkwriter.find(new BasicDBObject(TokumxDbServer.DOCNAME, docName)).upsert().update(
                            new BasicDBObject("$set", new BasicDBObject(TokumxDbServer.VALUENAME, r.toBytes())));
                    //               coll.update(new
                    //                      BasicDBObject(TokumxDbServer.docname, docName),
                    //                  new BasicDBObject("$set",
                    //                       new BasicDBObject(TokumxDbServer.valuename,
                    //                       SerDeUtil.toBytes(r))),
                    //                     true,
                    //                     false);

                } else { // for delete
                    if (noop) {
                        noop = false;
                    }
                    bulkwriter.find(new BasicDBObject(TokumxDbServer.DOCNAME, docName)).remove();
                    //coll.remove(new BasicDBObject(
                    //TokumxDbServer.docname, docName));
                }
            }
        }
        if (!noop) {
            try {
                bulkwriter.execute();
            } catch (com.mongodb.BulkWriteException e) {
                //This is tested to happen when
                //running multi-row transactions,
                //error message is: lock can not be granted
                LOGGER.warn("transaction failed due to BulkWriteException: " + e.getMessage());
                prof.commitFail(gName);
                return false;
            } catch (@SuppressWarnings("deprecation") MongoException.Network e) {
                LOGGER.warn("transaction failed due to mongo network exception: " + e.getMessage());
                prof.commitFail(gName);
                return false;
            } catch (MongoException e) {
                LOGGER.warn("transaction failed due to mongo exception: " + e.getMessage());
                prof.commitFail(gName);
                return false;

            }
        }

        DBObject cmd = new BasicDBObject("commitTransaction", 1);
        CommandResult res = runCommand(db, cmd);
        if (res.ok()) {
            prof.commitSuccess(gName);
            db.requestDone();
            return true;
        } else {
            prof.commitFail(gName);
            LOGGER.info("commit failed :" + res.getErrorMessage());
            cmd = new BasicDBObject("rollbackTransaction", 1);
            runCommand(db, cmd);
            db.requestDone();
            return false;
        }
    }

    static class TokumxReader implements KeyValueReader {
        private final DBCollection coll;
        private boolean aborted = false;

        TokumxReader(DBCollection coll) {
            this.coll = coll;
        }

        @SuppressWarnings("deprecation")
        @Override
        public Record get(String name, Key key) {
            final DBObject objkey = new BasicDBObject(TokumxDbServer.DOCNAME, key.toStringKey(name));
            DBObject obj;
            try {
                obj = MONGO_BACKOFF.call(new Callable<DBObject>() {
                    @Override
                    public DBObject call() throws Exception {
                        return get(objkey);
                    }
                }, MongoException.Network.class);
            } catch (InterruptedException e) {
                throw new TkvStoreException("get failed", e);
            } catch (RetryException e) {
                if (!e.isRetryExpired()) {
                    /**
                     * failed by exception other than
                     * MongoException.Network
                     */
                    Throwable t = e.getCause();
                    if (t instanceof MongoException) {
                        /**
                         * It is often the case that it failed to
                         * acquire the lock -> transaction should fail.
                         */
                        LOGGER.warn("get failed (transaction will abort: " + t.getMessage());
                        aborted = true;
                        return null;

                    }
                }
                throw new TkvStoreException("get failed", e);
            }

            if (obj != null && obj.containsField(TokumxDbServer.VALUENAME)) {
                return SimpleRecord.create((byte[]) obj.get(TokumxDbServer.VALUENAME));
            } else {
                return null;
            }
        }

        DBObject get(DBObject key) {
            DBCursor cur = coll.find(key).limit(1);
            if (cur.hasNext()) {
                return cur.next();
            } else {
                return null;
            }

        }

        public boolean isAborted() {
            return aborted;
        }
    }
}