Thursday, 29 August 2013


Пост 19. Pushdown запросов в Hadoop из Oracle RDBMS или как заставить hadoop  выполнять тяжелые запросы на своей стороне.

Доброго времени суток!
В предыдущем посте я описывал способ, с помощью которого можно прочитать данные, лежащие на HDFS, через внешнюю таблицу Oracle. Важно понимать, что в этом случае hadoop используется только как хранилище фалов (HDFS) и мы никак не задействуем мощь MapReduce.
То есть если мы пытаемся сделать join двух таблиц, он будет выполняться на стороне СУБД (поток данных будет благополучно передан на головы базы в PGA, где и будет происходить объединение). А что же делать, если мне хочется сделать JOIN двух таблиц на стороне Hadoop (использовать всю мощь MapReduce). Для этого надо сделать несколько шаманских действий:
       1)      Создать shell скрипт (назовем его 7.hive_pushdown.sh), который положим на сервер СУБД:

#!/bin/bash
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/usr/lib/hive/lib/*
export ORACLE_HOME=/u01/app/oracle/product/11.2.0/dbhome_1
export ORACLE_SID=orcl
source /home/oracle/.bash_profile
export BASH=/bin/bash
HADOOP_CLASSPATH=':/u01/app/oracle/product/11.2.0/db_home1/jdbc/lib/ojdbc6.jar:/u01/app/oracle/product/11.2.0/db/jdbc/lib/ojdbc6.jar:/usr/lib/hive/lib/hive-metastore-0.10.0-cdh4.3.0.jar:/usr/lib/hive/lib/hive-metastore-0.10.0-cdh4.3.0.jar:/u01/app/oracle/product/11.2.0/db/jlib/oraclepki.jar:/usr/lib/hbase/hbase-0.94.6-cdh4.3.0-security.jar:/usr/lib/hive/lib/libthrift-0.9.0.jar:/usr/lib/hive/lib/libfb303-0.9.0.jar:/usr/lib/hive/lib/*:/usr/lib/hadoop/lib/*:/usr/lib/hadoop/lib/*'
export HADOOP_CLASSPATH=/u01/connectors/oraloader/jlib/*:/usr/lib/hive/conf:/u01/connectors/orahdfs/jlib/*:/u01/app/oracle/product/11.2.0/dbhome_1/jdbc/lib/*:/u01/nosql/kv-2.0.26/lib/kvstore.jar:/usr/lib/hive/lib/*:/usr/lib/hive/lib/*
export HADOOP_CONF_DIR=/home/oracle/hadoop-conf
export JAVA_HOME=/usr/java/latest/
export NLS_LANG=AMERICAN_AMERICA.AL32UTF8
export PATH=/u01/app/oracle/product/11.2.0/dbhome_1/bin:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/oracle/bin
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/usr/lib/hive/lib/*
export OSCH_HOME=/u01/connectors/orahdfs

/usr/bin/hive -e 'drop table new.test_results';
str1="create table new.test_results as ";
str2=$*;
str3=$str1$str2
echo $str3
/usr/bin/hive -e "$str3";
sqlplus -s demouser/welcome1 <<+EOF
drop table demouser.test_results;
+EOF
/usr/bin/hadoop jar $OSCH_HOME/jlib/orahdfs.jar \
       oracle.hadoop.exttab.ExternalTable \
       -conf /home/oracle/scripts/conf/7.create_table_from_hive_push.xml \
       -createTable

На вход этой программе подается SQL который надо выполнить. Скрипт выполняет его (предварительно удалив временную таблицу hive, если она осталась с прошлого раза):

/usr/bin/hive -e 'drop table new.test_results';
str1="create table new.test_results as ";
str2=$*;
str3=$str1$str2
echo $str3
/usr/bin/hive -e "$str3";

После чего лезет в СУБД Oracle и удаляет таблицу результатов (так же с прошлого раза):

sqlplus -s demouser/welcome1 <<+EOF
drop table demouser.test_results;
+EOF

После чего создает внешнюю таблицу в Oracle c по шаблону только что созданной  hive таблицы:

/usr/bin/hadoop jar $OSCH_HOME/jlib/orahdfs.jar \
       oracle.hadoop.exttab.ExternalTable \
       -conf /home/oracle/scripts/conf/7.create_table_from_hive_push.xml \
       -createTable

Конфиг (точнее пример), необходимый OSCH прилагается наже:

<?xml version="1.0"?>
 <configuration>
  <property>
    <name>hive.metastore.uris</name>
    <value>thrift://localhost:9083</value>
  </property>
    <property>
      <name>oracle.hadoop.exttab.tableName</name>
      <value>test_results</value>
    </property>
    <property>
      <name>oracle.hadoop.exttab.locationFileCount</name>
      <value>4</value>
    </property>
    <property>
      <name>oracle.hadoop.exttab.hive.databaseName</name>
      <value>new</value>
    </property>
    <property>
      <name>oracle.hadoop.exttab.sourceType</name>
      <value>hive</value>
    </property>
    <property>
      <name>oracle.hadoop.exttab.hive.tableName</name>
      <value>test_results</value>
    </property>
    <property>
      <name>oracle.hadoop.exttab.defaultDirectory</name>
      <value>HADOOP_DIR</value>
    </property>
    <property>
      <name>oracle.hadoop.connection.user</name>
      <value>demouser</value>
    </property>
    <property>
      <name>oracle.hadoop.connection.wallet_location</name>
      <value>/u01/app/oracle/product/11.2.0/dbhome_1/network/admin/</value>
    </property>

    <property>
      <name>oracle.hadoop.connection.tnsEntryName</name>
      <value>orcl</value>
    </property>

    <property>
      <name>oracle.hadoop.connection.tns_admin</name>
      <value>/u01/app/oracle/product/11.2.0/dbhome_1/network/admin/</value>
    </property>

</configuration>

Это делается один раз. Потом используется.

     2)      Создать в СУБД Oracle Java процедуру для вызова внешних процедур операционной системы (тоже делается только один раз ):

create or replace and compile java source named "OsUtils" as

import java.io.IOException;
import java.io.InputStream;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

class StreamGobbler
  extends Thread {

  InputStream is;
  String type;

  StreamGobbler (InputStream is, String type) {
    this.is = is;
    this.type = type;
  }

  public void run () {
    try {
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      String line;
      while ((line = br.readLine()) != null) {
        OsUtils.result.append(line).append("\n");
      }
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }
}

public class OsUtils {

  public static StringBuffer result;

  public static int runCommand (String command)
    throws Throwable {

    result = new StringBuffer();

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec(command);
    // Any error message?
    StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR");
    // Any output?
    StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT");
    // Kick them off.
    errorGobbler.start();
    outputGobbler.start();
    // Any error?
    proc.waitFor();

    System.out.println(result.toString());
    return proc.exitValue();
  }
}
/

create or replace function run_os_command (p_cmd in varchar2) return number as
  language java name 'OsUtils.runCommand (java.lang.String) return int';
/

Назначим права:

exec dbms_java.grant_permission('DEMOUSER', 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '' );
/
exec dbms_java.grant_permission('DEMOUSER', 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '' );
/
exec dbms_java.grant_permission('DEMOUSER', 'SYS:java.io.FilePermission', '<<ALL FILES>>', 'execute' );
/
commit;
/
exit;

     3)      Используем предыдущие 2 механизма для вызова ad-hoc запроса и выполнения его на стороне Hadoop (вызываем из базы данных Oracle):

set serveroutput on;
declare
i number;
v_sql varchar2(256):='select max(a_num) from new.tab_on_hadoop';
str   varchar2(1024);
begin   
                str:='/home/oracle/scripts /7.hive_pushdown.sh '||v_sql;
                dbms_output.enable(100000);
                i:=run_os_command(str);
                dbms_output.put_line(i);
end;
/
select * from  "DEMOUSER"."TEST_RESULTS";
/
exit;

Вот как то так. Описанный выше метод не истина в последней инстанции это только один из способов совместного применения СУБД и Hadoop. Минусы описанного выше способа: при каждом запросе удаляется и создается таблица в Oracle и Hadoop (а это изменение в словаре данных, что далеко не бесплатно). Вторым минусом является однопоточность данного решения. Только один пользователь может выполнять такой запрос. По поводу удаления если у вас предполагается один и тот же запрос, который будет многократно выполняться, можно использовать Oracle temporary table и каждый раз делать туда вставку.

Все вышенаписанное лишь выглядит страшно. Попробуйте и вы сами поймете весь профит который можно получить от «слонов». Если есть вопросы – welcome!

2 comments:

  1. у тебя есть особое мнение по поводу использования HBase в связке с Oracle ?

    ER

    ReplyDelete
  2. Доброго времени суток!

    С hbase oracle RDBMS не скрещивал.
    Пробовал только Oracle NoSQL Database. Про это можно прочитать здесь:
    http://oracle-nosql.blogspot.co.uk/2013/02/16.html

    ReplyDelete