org.apache.hadoop.raid.DBUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.raid.DBUtils.java

Source

/**
 * 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.hadoop.raid;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.raid.DBConnectionFactory;
import org.apache.hadoop.util.ReflectionUtils;

/**
 * Utilities for writing hooks.
 */
public class DBUtils {
    static final private Log LOG = LogFactory.getLog(DBUtils.class);

    // The default value is to retry 20 times with maximum retry interval
    // 60 seconds. The expectation is about 22 minutes. After 7 retries, it
    // reaches 60 seconds.
    static final int DEFAULT_SQL_NUM_RETRIES = 20;
    static final int DEFAULT_RETRY_MAX_INTERVAL_SEC = 60;
    public static final String DB_MAX_RETRY = "hdfs.raid.db.max_retries";
    public static final int DEFAULT_DB_MAX_RETRY = 30;
    public static final String RAID_DB_CONNECTION_FACTORY_CLASS_KEY = "hdfs.raid.db.connection.factory.class";
    public static final int RETRY_MAX_INTERVAL_SEC = 60;
    public static final List<Object> EMPTY_SQL_PARAMS = new ArrayList<Object>();
    public static Random rand = new Random();
    public static int numDBOpenObjects = 0;
    public static Map<Boolean, String> defaultUrls = Collections.synchronizedMap(new HashMap<Boolean, String>());

    public static Long selectCount(DBConnectionFactory connectionFactory, String sql, List<Object> sqlParams,
            int sqlNumRetries, String tblName) throws IOException {
        List<List<Object>> results = DBUtils.runInsertSelect(connectionFactory, sql, sqlParams, true, sqlNumRetries,
                DBUtils.RETRY_MAX_INTERVAL_SEC, false, false);
        Long count = null;
        // should be the first field in the first element
        if (results != null && !results.isEmpty() && results.get(0).get(0) != null) {
            count = (Long) results.get(0).get(0);
        }
        if (count == null) {
            throw new IOException("You cannot select from " + tblName);
        }
        return count;
    }

    public static DBConnectionFactory getDBConnectionFactory(Configuration conf) throws IOException {
        Class<?> DBFactoryClass = null;
        DBFactoryClass = conf.getClass(DBUtils.RAID_DB_CONNECTION_FACTORY_CLASS_KEY,
                SimpleDBConnectionFactory.class);
        if (DBFactoryClass == null) {
            throw new IOException(
                    "Connection factory key: " + DBUtils.RAID_DB_CONNECTION_FACTORY_CLASS_KEY + " is not set!");
        }
        DBConnectionFactory connectionFactory = (DBConnectionFactory) ReflectionUtils.newInstance(DBFactoryClass,
                conf);
        connectionFactory.initialize(conf);
        return connectionFactory;
    }

    public static int getSqlNumRetry(Configuration conf) {
        return conf.getInt(DB_MAX_RETRY, DEFAULT_DB_MAX_RETRY);
    }

    public static void runInsert(DBConnectionFactory connectionFactory, String sql, List<Object> sqlParams,
            int numRetries) throws IOException {
        runInsertSelect(connectionFactory, sql, sqlParams, true, numRetries, DEFAULT_RETRY_MAX_INTERVAL_SEC, true,
                false);
    }

    public static List<List<Object>> getResults(ResultSet result) throws SQLException {
        List<List<Object>> results = new ArrayList<List<Object>>();
        int numColumns = result.getMetaData().getColumnCount();
        while (result.next()) {
            List<Object> row = new ArrayList<Object>();
            results.add(row);
            for (int index = 1; index <= numColumns; index++) {
                row.add(result.getObject(index));
            }
        }
        return results;
    }

    public static void close(ResultSet generatedKeys, PreparedStatement[] pstmts, Connection conn) {
        if (generatedKeys != null) {
            try {
                generatedKeys.close();
            } catch (Exception e) {
                LOG.warn("Error to close ResultSet", e);
            } finally {
                try {
                    if (!generatedKeys.isClosed()) {
                        LOG.warn("ResultSet is not closed");
                        DBUtils.numDBOpenObjects++;
                    }
                } catch (Exception ignore) {
                    DBUtils.numDBOpenObjects++;
                }
            }
        }
        if (pstmts != null && pstmts.length > 0) {
            for (PreparedStatement pstmt : pstmts) {
                if (pstmt == null) {
                    continue;
                }
                try {
                    pstmt.close();
                } catch (Exception e) {
                    LOG.warn("Error to close PreparedStatement", e);
                } finally {
                    try {
                        if (!pstmt.isClosed()) {
                            LOG.warn("PreparedStatement is not closed");
                            DBUtils.numDBOpenObjects++;
                        }
                    } catch (Exception ignore) {
                        DBUtils.numDBOpenObjects++;
                    }
                }
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                LOG.warn("Error to close Connection", e);
            } finally {
                try {
                    if (!conn.isClosed()) {
                        LOG.warn("Connection is not closed");
                        DBUtils.numDBOpenObjects++;
                    }
                } catch (Exception ignore) {
                    DBUtils.numDBOpenObjects++;
                }
            }
        }
    }

    // In the case of a select returns a list of lists, where each inner list represents a row
    // returned by the query.  In the case of an insert, returns null.
    public static List<List<Object>> runInsertSelect(DBConnectionFactory connectionFactory, String sql,
            List<Object> sqlParams, boolean isWrite, int numRetries, int retryMaxInternalSec, boolean insert,
            boolean getGeneratedKeys) throws IOException {
        int waitMS = 3000; // wait for at least 3s before next retry.
        for (int i = 0; i < numRetries; ++i) {
            Connection conn = null;
            ResultSet generatedKeys = null;
            PreparedStatement pstmt = null;
            String url = null;
            try {
                try {
                    url = connectionFactory.getUrl(isWrite);
                } catch (IOException ioe) {
                    LOG.warn("Cannot get DB URL, fall back to the default one", ioe);
                    url = defaultUrls.get(isWrite);
                    if (url == null) {
                        throw ioe;
                    }
                }
                LOG.info("Attepting connection with URL " + url);
                conn = connectionFactory.getConnection(url);
                defaultUrls.put(isWrite, url);
                pstmt = getPreparedStatement(conn, sql, sqlParams, getGeneratedKeys);
                if (insert) {
                    int recordsUpdated = pstmt.executeUpdate();
                    LOG.info("rows inserted: " + recordsUpdated + " sql: " + sql);
                    List<List<Object>> results = null;
                    if (getGeneratedKeys) {
                        generatedKeys = pstmt.getGeneratedKeys();
                        results = getResults(generatedKeys);
                    }
                    Thread.sleep(connectionFactory.getDBOpsSleepTime() + rand.nextInt(1000));
                    return results;
                } else {
                    generatedKeys = pstmt.executeQuery();
                    List<List<Object>> results = getResults(generatedKeys);
                    pstmt.clearBatch();
                    LOG.info("rows selected: " + results.size() + " sql: " + sql);
                    Thread.sleep(connectionFactory.getDBOpsSleepTime() + rand.nextInt(1000));
                    return results;
                }
            } catch (Exception e) {
                // We should catch a better exception than Exception, but since
                // DBConnectionUrlFactory.getUrl() defines throws Exception, it's hard
                // for us to figure out the complete set it can throw. We follow
                // DBConnectionUrlFactory.getUrl()'s definition to catch Exception.
                // It shouldn't be a big problem as after numRetries, we anyway exit.
                LOG.info("Exception " + e + ". Will retry " + (numRetries - i) + " times.");
                // Introducing a random factor to the wait time before another retry.
                // The wait time is dependent on # of failures and a random factor.
                // At the first time of getting a SQLException, the wait time
                // is a random number between [0,300] msec. If the first retry
                // still fails, we will wait 300 msec grace period before the 2nd retry.
                // Also at the second retry, the waiting window is expanded to 600 msec
                // alleviating the request rate from the server. Similarly the 3rd retry
                // will wait 600 msec grace period before retry and the waiting window
                // is
                // expanded to 1200 msec.

                waitMS += waitMS;
                if (waitMS > retryMaxInternalSec * 1000) {
                    waitMS = retryMaxInternalSec * 1000;
                }
                double waitTime = waitMS + waitMS * rand.nextDouble();
                if (i + 1 == numRetries) {
                    LOG.error("Still got Exception after " + numRetries + "  retries.", e);
                    throw new IOException(e);
                }
                try {
                    Thread.sleep((long) waitTime);
                } catch (InterruptedException ie) {
                    throw new IOException(ie);
                }
            } finally {
                DBUtils.close(generatedKeys, new PreparedStatement[] { pstmt }, conn);
            }
        }
        return null;
    }

    public static PreparedStatement getPreparedStatement(Connection conn, String sql, List<Object> sqlParams,
            boolean getGeneratedKeys) throws IOException, SQLException {
        PreparedStatement pstmt = getGeneratedKeys ? conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
                : conn.prepareStatement(sql);
        int pos = 1;
        for (Object param : sqlParams) {
            if (param instanceof Integer) {
                pstmt.setInt(pos++, ((Integer) param).intValue());
            } else {
                pstmt.setString(pos++, (String) param);
            }
        }
        return pstmt;
    }

}