20
Oracleホワイト・ペーパー 20 101HadoopデータとOracle パラレル処理の統合

HadoopデータとOracle パラレル処理の統合...HadoopデータとOracleパラレル処理の統合 1 はじめに 多種多様な業種において、ファイル・システムに保存されているデータが急増しています。一般的

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Oracleホワイト・ペーパー

20 10年1月

HadoopデータとOracle

パラレル処理の統合

HadoopデータとOracleパラレル処理の統合

1

はじめに

多種多様な業種において、ファイル・システムに保存されているデータが急増しています。一般的

に、このような莫大なデータには、重要度の低い大量の詳細データと、今後の分析やその他のデー

タソースの補完に役立つ多少の貴重なデータが含まれています。こういったデータがデータベース

の外部に保管されているにもかかわらず、データベース内に保管されているデータとの統合が求め

られる場合があります。このような統合における目標は、ビジネス・ユーザーにとって価値のある

情報を引き出すことです。

このホワイト・ペーパーでは、Hadoopクラスタに保管されたデータへOracleデータベースからアク

セスする方法について詳しく説明します。ここでは、HadoopとHDFSを例として取り上げています

が、その他の分散ストレージ・メカニズムに対しても、同じ方針を適用できます。このホワイト・

ペーパーでは各種のアクセス手法について説明するとともに、このような手法の具体的な実装例を

示します。

HadoopデータとOracleパラレル処理の統合

2

外部Hadoopデータに対するアクセス手法

ファイル・システム上の外部データや外部ファイルに対して、Oracleデータベース内からもっとも簡

単にアクセスするには外部表を使用します。外部表の概要については、こちらを参照してください。

外部表はファイル・システムに保管されたデータを表形式で表すものであり、SQL問合せにおいて透

過的に使用できます。したがって、外部表を使用して、Oracleデータベース内からHDFS(Hadoop File

System)に保管されたデータにアクセスすることも潜在的には可能です。ただし、残念ながら、外

部表のドライバが使用する通常のオペレーティング・システム・コールからHDFSファイルに直接ア

クセスすることはできません。このようなケースは、FUSE(File System in Userspace)プロジェクト

で回避できます。HDFSストアをマウントして通常のファイル・システムのように扱うことができる

FUSEドライバが多数提供されています。このようなドライバを使用してデータベース・インスタン

ス(RACデータベースの場合はすべてのインスタンス)にHDFSをマウントすると、外部表インフラ

ストラクチャを使用して、簡単にHDFSファイルにアクセスできます。

図1.データベース内のマップ・リデュースから外部表経由でのアクセス

図1では、この記事に説明されているとおり、Oracle Database 11gを利用してデータベース内のマップ・

リデュースが実装されています。Oracle Database 11gのパラレル実行フレームワークは、要求される

ほとんどの処理を外部表から直接パラレルで実行する能力を十分に備えています。

HadoopデータとOracleパラレル処理の統合

3

外部表のアプローチが適切でない場合もあります(FUSEが使用できない場合など)。Oracleテーブ

ル・ファンクションは、Hadoopからデータをフェッチするための代替手段を提供しています。添付

の例に、この実行方法の1つが説明されています。簡単に説明すると、DBMS_SCHEDULERフレーム

ワークを使用するテーブル・ファンクションを実装して、Hadoopのマップ・リデュース・ジョブを

送信する外部シェル・スクリプトを非同期に起動します。テーブル・ファンクションとマッパーは、

Oracle Advanced Queuing機能を使用して通信します。Hadoopマッパーが共通キューにデータをエン

キューし、テーブル・ファンクションがそこからデキューを実行します。このテーブル・ファンク

ションはパラレルで実行できるため、追加ロジックを使用して、1つのスレーブのみによって外部

ジョブが発行されるようにしています。

図2.テーブル・ファンクションの利用によるパラレル処理の実行

テーブル・ファンクションはパラレル実行でき、Hadoopのストリーミング・ジョブもOracleの問合せ

コーディネータに支配されることなく、それぞれ異なる並列度でパラレル実行できるため、キュー

によってロードバランシングが実行されます。

HadoopデータとOracleパラレル処理の統合

4

テーブル・ファンクションを利用した例

図2に示したアーキテクチャを実際の例に置き換えたものを次に示します。この例では、Hadoopに保

管されたデータへアクセスするためのテーブル・ファンクションを使用したテンプレートの実装の

みを示していることに注意してください。言うまでもなく、(より優れた)その他の実装方法も存

在します。

次の図は、図2に示した略図を技術的により正確かつ詳細に示した図であり、実際のコードをどこで、

どのように使用するかが説明されています。

図3.マッパー・ジョブの開始とデータの取得

ステップ1では、問合せコーディネータとなるものが特定されます。この例では、同じキー値を持つ

レコードを表に書き込むという単純な仕組みが使用されています。最初の挿入レコードが、プロセス

に対する問合せコーディネータ(QC)の役割を勝ち取ります。QCテーブル・ファンクションの呼出

しは、処理の役割も果たすことに注意してください。

ステップ2では、このテーブル・ファンクションの呼出し(QC)がdbms_scheduler(図3のジョブ・コ

ントローラ)を使用して、非同期ジョブを開始します。このジョブは、Hadoopクラスタ上で同期バッ

シュ・スクリプトを実行します。図3ではランチャーと呼ばれるこのバッシュ・スクリプトによって、

Hadoopクラスタでマッパー・プロセス(ステップ3)が開始されます。

HadoopデータとOracleパラレル処理の統合

5

マッパー・プロセスはデータを処理し、ステップ5でキューへの書込みを実行します。この例では、

クラスタ全体で使用できるキューが使用されています。ここでは、単純にすべての出力が直接キュー

に書き込まれていますが、出力をまとめてからキューに移動すると、より良いパフォーマンスが得

られます。言うまでもなく、パイプやリレーショナル表など、その他の各種配信メカニズムを使用

することもできます。

ステップ6は、データベース内で実行されているテーブル・ファンクションのパラレル呼出しによっ

て実行されるデキュー・プロセスです。これらのパラレル呼出しによってデータが処理されるたび

に、リクエスト元の問合せへとデータが返されます。テーブル・ファンクションはOracleデータと

キューから取得したデータの両方を利用して、2つのソースのデータを1つの結果セットに統合して

エンドユーザーに返します。

図4.プロセスの監視

Hadoop側のプロセス(マッパー)を起動したのち、ジョブ監視プロセスはランチャー・スクリプト

を監視します。マッパーがHadoopクラスタでのデータ処理を終了すると、図4に示すとおり、バッ

シュ・スクリプトが終了します。

ジョブ・モニターはデータベース・スケジューラ・キューを監視し、シェル・スクリプトの終了を

検出します(ステップ7)。ジョブ・モニターはステップ8で残りのデータ要素があるかどうか、デー

タ・キューを確認します。データがキューに含まれる限り、テーブル・ファンクションの呼出しに

よってデータの処理が続行されます(ステップ6)。

HadoopデータとOracleパラレル処理の統合

6

図5.プロセスの終了

テーブル・ファンクションのパラレル呼出しによってすべてのキューに対するデキューが完了する

と、ジョブ・モニターによってキューが終了され(図5のステップ9)、Oracleデータベースでのテー

ブル・ファンクション呼出しが停止します。この時点で、すべてのデータがリクエスト元の問合せ

に渡されています。

HadoopデータとOracleパラレル処理の統合

7

サンプル・コード

図3から図5で実装されたソリューションでは、次のコードが使用されました。すべてのコードは、

Oracle Database 11gと5ノードのHadoopクラスタを使用してテストされました。通常のホワイト・ペー

パーと同じように、これらのスクリプトをテキスト・エディタにコピーし、書式が正しいことを確

認してください。

データ処理向けのテーブル・ファンクション

このスクリプトには、セットアップ・コンポーネントが含まれています。たとえば、スクリプトの

最初の部分には、図3のステップ1に示した仲裁表の作成が含まれています。この例では、一般的な

OEスキーマが使用されています。

connect oe/oe

-- Table to use as locking mechanisim for the hdfs reader as

-- leveraged in Figure 3 step 1

DROP TABLE run_hdfs_read;

CREATE TABLE run_hdfs_read(pk_id number, status varchar2(100),

primary key (pk_id));

-- Object type used for AQ that receives the data

CREATE OR REPLACE TYPE hadoop_row_obj AS OBJECT (

a number,

b number);

/

connect / as sysdba

-- system job to launch external script

-- this job is used to eventually run the bash script

-- described in Figure 3 step 3

CREATE OR REPLACE PROCEDURE launch_hadoop_job_async(in_directory

IN VARCHAR2, id number) IS

cnt number;

BEGIN

begin DBMS_SCHEDULER.DROP_JOB ('ExtScript'||id, TRUE);

HadoopデータとOracleパラレル処理の統合

8

exception when others then null; end;

-- Run a script

DBMS_SCHEDULER.CREATE_JOB (

job_name => 'ExtScript' || id,

job_type => 'EXECUTABLE',

job_action => '/bin/bash',

number_of_arguments => 1

);

DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE ('ExtScript' || id, 1,

in_directory);

DBMS_SCHEDULER.ENABLE('ExtScript' || id);

-- Wait till the job is done. This ensures the hadoop job is

completed

loop

select count(*) into cnt

from DBA_SCHEDULER_JOBS where job_name = 'EXTSCRIPT'||id;

dbms_output.put_line('Scheduler Count is '||cnt);

if (cnt = 0) then

exit;

else

dbms_lock.sleep(5);

end if;

end loop;

-- Wait till the queue is empty and then drop it

-- as shown in Figure 5

-- The TF will get an exception and it will finish quietly

loop

HadoopデータとOracleパラレル処理の統合

9

select sum(c) into cnt

from

(

select enqueued_msgs - dequeued_msgs c

from gv$persistent_queues where queue_name =

'HADOOP_MR_QUEUE'

union all

select num_msgs+spill_msgs c

from gv$buffered_queues where queue_name =

'HADOOP_MR_QUEUE'

union all

select 0 c from dual

);

if (cnt = 0) then

-- Queue is done. stop it.

DBMS_AQADM.STOP_QUEUE ('HADOOP_MR_QUEUE');

DBMS_AQADM.DROP_QUEUE ('HADOOP_MR_QUEUE');

return;

else

-- Wait for a while

dbms_lock.sleep(5);

end if;

end loop;

END;

/

-- Grants needed to make hadoop reader package work

grant execute on launch_hadoop_job_async to oe;

HadoopデータとOracleパラレル処理の統合

10

grant select on v_$session to oe;

grant select on v_$instance to oe;

grant select on v_$px_process to oe;

grant execute on dbms_aqadm to oe;

grant execute on dbms_aq to oe;

connect oe/oe

-- Simple reader package to read a file containing two numbers

CREATE OR REPLACE PACKAGE hdfs_reader IS

-- Return type of pl/sql table function

TYPE return_rows_t IS TABLE OF hadoop_row_obj;

-- Checks if current invocation is serial

FUNCTION is_serial RETURN BOOLEAN;

-- Function to actually launch a Hadoop job

FUNCTION launch_hadoop_job(in_directory IN VARCHAR2, id in out

number) RETURN BOOLEAN;

-- Tf to read from Hadoop

-- This is the main processing code reading from the queue in

-- Figure 3 step 6. It also contains the code to insert into

-- the table in Figure 3 step 1

FUNCTION read_from_hdfs_file(pcur IN SYS_REFCURSOR, in_directory

IN VARCHAR2) RETURN return_rows_t

PIPELINED PARALLEL_ENABLE(PARTITION pcur BY ANY);

END;

/

HadoopデータとOracleパラレル処理の統合

11

CREATE OR REPLACE PACKAGE BODY hdfs_reader IS

-- Checks if current process is a px_process

FUNCTION is_serial RETURN BOOLEAN IS

c NUMBER;

BEGIN

SELECT COUNT (*) into c FROM v$px_process WHERE sid =

SYS_CONTEXT('USERENV','SESSIONID');

IF c <> 0 THEN

RETURN false;

ELSE

RETURN true;

END IF;

exception when others then

RAISE;

END;

FUNCTION launch_hadoop_job(in_directory IN VARCHAR2, id IN OUT

NUMBER) RETURN BOOLEAN IS

PRAGMA AUTONOMOUS_TRANSACTION;

instance_id NUMBER;

jname varchar2(4000);

BEGIN

if is_serial then

-- Get id by mixing instance # and session id

id := SYS_CONTEXT('USERENV', 'SESSIONID');

SELECT instance_number INTO instance_id FROM v$instance;

id := instance_id * 100000 + id;

else

-- Get id of the QC

HadoopデータとOracleパラレル処理の統合

12

SELECT ownerid into id from v$session where sid =

SYS_CONTEXT('USERENV', 'SESSIONID');

end if;

-- Create a row to 'lock' it so only one person does the job

-- schedule. Everyone else will get an exception

-- This is in Figure 3 step 1

INSERT INTO run_hdfs_read VALUES(id, 'RUNNING');

jname := 'Launch_hadoop_job_async';

-- Launch a job to start the hadoop job

DBMS_SCHEDULER.CREATE_JOB (

job_name => jname,

job_type => 'STORED_PROCEDURE',

job_action => 'sys.launch_hadoop_job_async',

number_of_arguments => 2

);

DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE (jname, 1, in_directory);

DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE (jname, 2, CAST (id AS

VARCHAR2));

DBMS_SCHEDULER.ENABLE('Launch_hadoop_job_async');

COMMIT;

RETURN true;

EXCEPTION

-- one of my siblings launched the job. Get out quitely

WHEN dup_val_on_index THEN

dbms_output.put_line('dup value exception');

RETURN false;

WHEN OTHERs THEN

RAISE;

END;

HadoopデータとOracleパラレル処理の統合

13

FUNCTION read_from_hdfs_file(pcur IN SYS_REFCURSOR, in_directory

IN VARCHAR2) RETURN return_rows_t

PIPELINED PARALLEL_ENABLE(PARTITION pcur BY ANY)

IS

PRAGMA AUTONOMOUS_TRANSACTION;

cleanup BOOLEAN;

payload hadoop_row_obj;

id NUMBER;

dopt dbms_aq.dequeue_options_t;

mprop dbms_aq.message_properties_t;

msgid raw(100);

BEGIN

-- Launch a job to kick off the hadoop job

cleanup := launch_hadoop_job(in_directory, id);

dopt.visibility := DBMS_AQ.IMMEDIATE;

dopt.delivery_mode := DBMS_AQ.BUFFERED;

loop

payload := NULL;

-- Get next row

DBMS_AQ.DEQUEUE('HADOOP_MR_QUEUE', dopt, mprop, payload,

msgid);

commit;

pipe row(payload);

end loop;

exception when others then

if cleanup then

delete run_hdfs_read where pk_id = id;

commit;

end if;

HadoopデータとOracleパラレル処理の統合

14

END;

END;

/

バッシュ・スクリプト

この短いスクリプトは、図3のステップ3および4に示したとおり、データベース外のコントローラです。

これは、Hadoopマッパーが実行されている限りシステム上に残る同期ステップです。

#!/bin/bash

cd -HADOOP_HOME-

A="/net/scratch/java/jdk1.6.0_16/bin/java -classpath

/home/hadoop:/home/hadoop/ojdbc6.jar StreamingEq"

bin/hadoop fs -rmr output

bin/hadoop jar ./contrib/streaming/hadoop-0.20.0-streaming.jar -

input input/nolist.txt -output output -mapper "$A" -jobconf

mapred.reduce.tasks=0

Javaマッパー・スクリプト

この例では、Hadoopクラスタ上で実行されるマッパー・プロセスとして、小さく単純なプロセスを

作成していますが、もちろん、より総合的なマッパーを作成することも可能です。このプロセスは

文字列を2つの数字に変換し、これらの数字を1行ごとにキューに書込みます。

// Simplified mapper example for Hadoop cluster

import java.sql.*;

//import oracle.jdbc.*;

//import oracle.sql.*;

import oracle.jdbc.pool.*;

//import java.util.Arrays;

//import oracle.sql.ARRAY;

//import oracle.sql.ArrayDescriptor;

HadoopデータとOracleパラレル処理の統合

15

public class StreamingEq {

public static void main (String args[]) throws Exception

{

// connect through driver

OracleDataSource ods = new OracleDataSource();

Connection conn = null;

Statement stmt = null;

ResultSet rset = null;

java.io.BufferedReader stdin;

String line;

int countArr = 0, i;

oracle.sql.ARRAY sqlArray;

oracle.sql.ArrayDescriptor arrDesc;

try {

ods.setURL("jdbc:oracle:thin:oe/oe@$ORACLE_INSTANCE

");

conn = ods.getConnection();

}

catch (Exception e){

System.out.println("Got exception " + e);

iif (rset != null) rset.close();

iif (conn != null) conn.close();

System.exit(0);

return ;

}

System.out.println("connection works conn " + conn);

stdin = new java.io.BufferedReader(new

java.io.InputStreamReader(System.in));

HadoopデータとOracleパラレル処理の統合

16

String query = "declare dopt

dbms_aq.enqueue_options_t; mprop dbms_aq.message_properties_t;

msgid raw(100); begin dopt.visibility := DBMS_AQ.IMMEDIATE;

dopt.delivery_mode := DBMS_AQ.BUFFERED;

dbms_Aq.enqueue('HADOOP_MR_QUEUE', dopt, mprop, hadoop_row_obj(?,

?), msgid); commit; end;";

CallableStatement pstmt = null;

try {

pstmt = conn.prepareCall(query);

}

catch (Exception e){

System.out.println("Got exception " + e);

if (rset != null) rset.close();

if (conn != null) conn.close();

}

while ((line = stdin.readLine()) != null) {

if (line.replaceAll(" ", "").length() < 2)

continue;

String [] data = line.split(",");

if (data.length != 2)

continue;

countArr++;

pstmt.setInt(1, Integer.parseInt(data[0]));

pstmt.setInt(2, Integer.parseInt(data[1]));

pstmt.executeUpdate();

}

if (conn != null) conn.close();

System.exit(0);

return;

}

HadoopデータとOracleパラレル処理の統合

17

}

データ要求問合せ

テーブル・ファンクション処理を利用した上述のシステムでselectを実行する例を次に示します。

-- Set up phase for the data queue

execute DBMS_AQADM.CREATE_QUEUE_TABLE('HADOOP_MR_QUEUE',

'HADOOP_ROW_OBJ');

execute DBMS_AQADM.CREATE_QUEUE('HADOOP_MR_QUEUE',

'HADOOP_MR_QUEUE');

execute DBMS_AQADM.START_QUEUE ('HADOOP_MR_QUEUE');

-- Query being executed by an end user or BI tool

-- Note the hint is not a real hint, but a comment

-- to clarify why we use the cursor

select max(a), avg(b)

from table(hdfs_reader.read_from_hdfs_file

(cursor

(select /*+ FAKE cursor to kick start parallelism */ *

from orders), '/home/hadoop/eq_test4.sh'));

HadoopデータとOracleパラレル処理の統合

18

結論

このホワイト・ペーパーでは簡単な例を使用して、HadoopシステムとOracle Database 11gの統合が簡

単に実行できることを示しました。

本書で説明したアプローチを採用すると、HadoopからOracle問合せに対してデータを直接ストリーミ

ングできます。これによって、ローカル・ファイル・システムへのデータ・フェッチとOracle表への

マテリアライズを実行することなく、SQL問合せにアクセスすることが可能になります。

HadoopデータとOracle

パラレル処理の統合

2010年1月

著者:Shinkanth Shanker、Alan Choi、

Jean-Pierre Dijcks

Oracle Corporation

World Headquarters

500 Oracle Parkway

Redwood Shores, CA 94065

U.S.A.

海外からのお問い合わせ窓口:

電話:+1.650.506.7000

ファクシミリ:+1.650.506.7200

oracle.com

Copyright © 2010, Oracle and/or its affiliates. All rights reserved.

本文書は情報提供のみを目的として提供されており、ここに記載される内容は予告なく変更されることがあります。本文書は一切間違

いがないことを保証するものではなく、さらに、口述による明示または法律による黙示を問わず、特定の目的に対する商品性もしくは

適合性についての黙示的な保証を含み、いかなる他の保証や条件も提供するものではありません。オラクルは本文書に関するいかなる

法的責任も明確に否認し、本文書によって直接的または間接的に確立される契約義務はないものとします。本文書はオラクル社の書面

による許可を前もって得ることなく、いかなる目的のためにも、電子または印刷を含むいかなる形式や手段によっても再作成または送

信することはできません。

Oracleは米国Oracle Corporationおよびその子会社、関連会社の登録商標です。その他の名称はそれぞれの会社の商標です。

0109