X

A blog about Oracle Technology Network Japan

Recent Posts

クイズに挑戦:コア関数型インタフェースの使用:Consumer(上級者向け)

.button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. " ); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); } ※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Using Core Functional Interfaces: Consumer (Advanced)"を翻訳したものです。 上級プログラマーにも難解なコンシューマ・インタフェース 著者:Simon Roberts、Mikalai Zaikin  2020年4月6日  その他の設問はこちらから   Predicate、Consumer、Function、Supplierなどのコア関数型インタフェースを使いたいと思います。この設問では、コンシューマに注目します。 次のコード部分について: Stream.of(1).peek( ((Consumer<Integer>)(i1)->{i1 = i1 + 1;}) .andThen((i2)->{i2 = i2 + 2;})) .forEach(System.out::print); コンソールには何が出力されますか。1つ選んでください。 A. 1 B. 2 C. 3 D. 4   解答:Stream.peek(...)メソッドのAPIドキュメントには、元のストリームの要素からなるストリームを返しつつ、それぞれの要素が結果ストリームで使われる際に、指定された操作が実行されると記載されています。 このメソッドでは、引数としてConsumerを受け取ります。その目的は、各要素をConsumerに渡す際に元のストリームを変更しないことです。このメソッドは、デバッグに役立てるためにストリーム内の項目を確認(出力など)することを目的として使用する場合が多いでしょう。 この設問の要点は、Consumerでの実行結果と、その結果がストリームのデータに影響を与えるかどうかです。ストリームのあらゆる操作において、一般的に、ストリーム内の要素の変更は避けることが最善です。好まれる動作は、変更を反映した新しい要素を作成し、その要素をストリームとともに次の操作に渡すことです。このような推奨事項があるにもかかわらず、通常のJava構文でこの推奨事項に違反し、ストリーム・データが変更されてしまう動作になる可能性があるという状況も数多く存在します。 java.util.function.Consumerインタフェースの抽象メソッドのシグネチャは次のとおりです。 void accept(T t); このメソッドでは、戻りタイプがvoidと宣言されているため、データを変更できる方法は2つしかありません。1つ目として、メソッドの引数が変更可能な参照タイプ(StringBuilderは該当しますが、Stringは該当しません)の場合、引数で参照されているオブジェクトの内容を変更できます。2つ目として、変更可能なオブジェクトへの外部参照がこのメソッドで使われている場合、オブジェクトの内容を変更できます。 この設問のConsumerを実装しているコードに外部参照がないことは明らかであるため、おそらくメソッドの引数以外は変更できません。引数を変更できるのは、引数が変更可能なオブジェクトである場合に限られることから、次に確認する必要があるのは、引数の型です。引数はプリミティブのintのように見えるかもしれませんが、実際はオブジェクトであり、具体的に言えば、オートボクシングされたIntegerオブジェクトです。注意すべき点は、ストリームのソースがStream.ofファクトリ・メソッドであることです。このメソッドではオブジェクトのストリームが作成されるため、intはボクシングする必要があります。このストリームがIntStream.ofを使って作成されていたとすれば、プリミティブのストリームになっていました。 次にわかることは、Integerオブジェクトは不変であり、最初に作成されたオブジェクトが以下の形式のコードによって変更されることはない点です。新しいIntegerが作成され、i1が参照する値は、新しいIntegerを参照するように変更されます。 Integer i1 = 1; i1 = i1 + 1; これは、String型を使っている次のコードとまったく同様です。 String s = "Hello"; s = s + " world! "; ここから、この場合、peekが返すストリームは、元の変更されていない1つのオブジェクトで構成されなければならないことがわかります。したがって、出力は1であり、選択肢Aが正解となり、選択肢B、C、Dは誤りとなります。 Consumerのコードをもう少し詳しく見てみるのも興味深いでしょう。動作を説明しやすくするために、重要な要素を取り出して並べてみます。 final int ONE = 1; final int TWO = 2; Consumer<Integer> c1 = (i1)->{i1 = i1 + ONE;}; Consumer<Integer> c2 = (i2)->{i2 = i2 + TWO;}; Consumer<Integer> cFinal = c1.andThen(c2); cFinal.accept(Integer.valueOf(1)); Consumerインタフェースには、defaultメソッドとしてandThen(Consumer c2)が存在します。このメソッドでは、元の2つのConsumerの動作を組み合わせた新しいConsumerが作成されます。cFinalで定義された動作が呼び出されると、c1にその呼出しの引数(ストリームをボクシングしたInteger(1))が渡されます。これによって新しくInteger(2)が作成されますが、voidメソッドが終了したときに即座にスコープ外になります。 その後、cFinalの操作ではもともとのInteger(1)を受け取り、それを引数としてc2を呼び出します。c2が呼び出されると、TWOが加算され、新しく別のInteger(3)が作成されますが、このオブジェクトもvoidメソッドが終了したときにスコープ外になります。その結果、前述のように、メソッドローカルなIntegerオブジェクトはいずれもストリームには含まれないため、オブジェクトを作成したメソッドが完了した途端に、両方ともガベージ・コレクションの候補になります。 この構文では、Consumerのラムダの本体で、リテラル定数1および2ではなく、final変数ONEおよびTWOを加算するように変更した点に注意してください。もともとのコードで使用されている形式では、同じであるように見える2か所のコードを見て、実際に同じものだと思ってしまうことが非常に起こりがちです。長い形式でコードを表現することで、その違いがわかりやすくなります。実際のコードで同じような状況になった場合は、このようにする方がよいかもしれません。 正解は選択肢Aです。    Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Using Core Functional Interfaces: Consumer (Advanced)"を翻訳したものです。 上級プログラマーにも難解なコンシューマ・インタフェース 著者:Simon Roberts、Mikalai Zaikin  2020年4月6日  その他の設問はこちらから   Pr...

Micronaut.ioとJavaによるHTML5 Server-Sent Events

※本記事は、Eric J. Brunoによる"HTML5 Server-Sent Events with Micronaut.io and Java"を翻訳したものです。 シンプルで信頼できるメッセージ・サービスを構築する 著者:Eric J. Bruno 2020年4月6日   筆者は最近、マイクロサービス・フレームワークのクラウド側実装としてMicronautを選択した、エンド・ツー・エンドIoTプロジェクトに関わりました。Micronautでは、KafkaとRabbitMQ、そして2つのHTML5メッセージ・パラダイムであるServer-Sent Events(SSE)とWebSocketを組込みでサポートしています。しかし、パブリッシュ/サブスクライブやキューベースのメッセージングなどを目的に、これらを有効に使用するためには、多少の手間がかかります。本記事では、MicronautのSSEサポートを使用して構築する、シンプルで信頼できるメッセージ・システムについて考えます。 プロジェクトのWebサイトに記載されているように、MicronautはJVMをベースとした最新のフルスタック・フレームワークで、簡単にテストできるモジュール式のマイクロサービスやサーバーレス・アプリケーションを構築するためのものです。Micronautでは、依存性注入やアスペクト指向プログラミング、事前コンパイルを使用して、超高速な起動、優れたスループット、低メモリ・オーバーヘッドを実現しています。そのため、Micronautは、インスタンスが短時間でスピンアップおよびスピンダウンされるクラウドベースのマイクロサービスで、優れた選択肢となっています。 Micronautの紹介としては、まずJonas Havers氏の記事「Micronautでマイクロサービスを構築する」をご覧になってから、細かい索引が付いたユーザー・ガイドをざっとお読みください。 本記事のすべてのソース・コードは、こちらからダウンロードすることができます。このファイルには、次に示す3つの主要なプロジェクトが含まれています。 MessageServer:QueueクラスとTopicControllerクラスを含むMicronaut SSEサーバーで、Micronautの組込みSSEサポートを利用する TemperatureSender:温度測定デバイスをシミュレートしたもので、Micronaut SSEを使って温度の測定値を送信する Thermometer:JavaScriptを使用したWebアプリケーションで、Micronautサーバーをリスニングして最新の温度を受け取る ダウンロード・ファイルには、独自のSSELibraryに加え、サンプル・クライアントであるQueueSenderおよびQueueReceiverの2つも含まれています。 それでは早速、SSEメッセージング・サポートの説明から始めます。   Server-Sent Eventsの概要 HTML5 SSEは、ブラウザ(または任意の実装アプリケーション)がHTTPまたはHTTPSでサーバーからアップデートを受け取れるようにするサーバー・プッシュ・テクノロジーです。SSEは、ブラウザの外部、つまり任意の言語で書かれたアプリケーション間でも動作します。SSEには別個のサーバーは必要ありません。HTTPおよびHTTPSで動作し、ファイアウォールも使用でき、シンプルです。 HTML5 SSEメッセージングでは、2つの主要なコンポーネントを使用しています。1つはテキストベースのメッセージをシンプルなプロトコルで送信するtext/event-stream MIMEタイプであり、もう1つはメッセージを受信するイベント・リスナーを持つEventSourceインタフェースです。 SSEの詳細については、W3CのHTML5仕様をご覧ください。サンプル実装に関しては、筆者による記事「HTML5 Server-Sent Events and Examples」もご覧いただけます。   Micronaut.ioによるSSEプログラミング SSEの第一歩として、Micronautの@Controller属性を使ってMicronautのControllerクラス(実質的にはHTTPリスナー)を作ってみます。このプロジェクトでは、2つのControllerクラスを作りました。1つはキューベースのメッセージング用、もう1つはトピックベース(パブリッシュ/サブスクライブ)のメッセージング用です。 @Controller("/messageserver/api/") public class QueueController extends Messenger { ... } @Controller("/messageserver/api") public class TopicController extends Messenger { … } 図1に示すように、Micronautベースの実装は送信アプリケーションと受信アプリケーションの間に位置します。   図1:送信アプリと受信アプリの間に位置するMicronaut 各Controllerに、URIパス/messageserver/apiを指定しています。このパスはMicronautサーバーのベースURIと連結されます。Messengerベース・クラスについては、後ほど説明します。ここでは、イベントの送受信を行う一部のコードに注目します。リスト1に示すのは、イベントを受信して@Get RESTエンドポイントを設定するコードです。このコードでは、エンドポイントの名前(この場合はQueueまたはTopic)と、リスニングするキューの名前を指定しています。 リスト1:キューに格納されたSSEメッセージを受信するRESTエンドポイント @Get("/queue/{name}") public Publisher<Event<String>> index(Optional<String> name) { // Determine queue to listen to Queue dest = getQueue( name.get() ); return Flowable.generate(() -> 0, (i, emitter) -> { // Get the message first... Message msg = dest.getNextMessage(); String data = new String( msg.getData() ); // Then deliver it… emitter.onNext( Event.of(data) ); // Finally delete it after delivery... dest.deleteMessage( msg.getId() ); }); } @Getアノテーションでは、以下がHTTP GET呼出しハンドラであること、そしてURLの一部としてqueueおよびキュー名が含まれることを示しています。エンドポイントが呼び出されたとき、getQueueでは、与えられた名前を使ってHashMap内にあるQueue宛先オブジェクトを検索します。見つからない場合は、新しいQueueオブジェクトを作成し、与えられた名前を使ってHashMapに挿入します。 次に、Flowableエミッタを使ってEventオブジェクトを生成するリアクティブ・ストリーム・パブリッシャが利用できるようになったときに、そこからメッセージが送信されます。メッセージは、最大1つのサーバーに配信されるまで、キュー・パラダイムによって保存されます。その結果、それぞれのメッセージが配信された後、キューに格納されたメッセージはエミッタによって削除されます。 アプリケーションから宛先にメッセージを送信する際には、HTTP POSTを使います(リスト2参照)。MicronautのPOSTハンドラ(@Postアノテーションの付加により示されています)では、最初に宛先を名前で検索します。 リスト2:配信のために宛先キューに向けてメッセージを送信するHTTP POSTメソッド // Content-Type: text/event-stream @Consumes(MediaType.TEXT_EVENT_STREAM) @Post("/queue/publish") public HttpResponse queue( Session session, HttpRequest<?> request, @Body String data ) { try { HttpParameters params = request.getParameters(); String queueName = params.getFirst("name").orElse(null); Queue dest = getQueue(queueName); return processSend(dest, data); } catch ( Exception e ) { e.printStackTrace(); } return HttpResponse.status(HttpStatus.UNAUTHORIZED, "Not authenticated"); } @Consumesアノテーションでは、POSTがContent-Type HTTPヘッダー・フィールドとしてHTTP MIMEタイプtext/event-streamを受け取れることを示しています。宛先の名前は、HTTPパラメータとして渡すことが想定されています。宛先オブジェクトを取得したら、メッセージは、ライブの受信者に向けてルーティングされます。受信者が存在しない場合、メッセージは永続化されます。   SSEメッセージの送信(クライアントのコード) アプリケーションからHTML5 Server-Sent Eventsを送信するために、SSEDataPublisherヘルパー・クラス(ダウンロードのsselibraryパッケージに含まれています)を使うことができます。このクラスは、キューベースのメッセージでもトピックベースのメッセージでも等しく良好に機能します。ここに含まれている実質的なメソッドは1つだけで、sendMessageという名前です。このメソッドでは、メッセージの永続化と配信を処理するメッセージ・サーバーのURL、メッセージのペイロード、セキュリティ向上のための認証コード(省略可能)を受け取ります。 メッセージのペイロードは、HTML5 SSE仕様に従い、次に示す3つのフィールドを含むテキスト文字列の形式とする必要があります。 イベント・タイプ(ハートビートやメッセージなど)、例:event: message ミリ秒単位でのリトライ間隔、例:retry:30000 データ本体、例:data: "actual data here..." リスト3(簡潔さを優先し、一部のコードは割愛しています)に示すように、各フィールドのテキストは改行文字\nで終了し、HTTP POSTメッセージの一部として送信されなければなりません。また、データ・フィールドの最後には、改行文字をもう1つ追加します。 リスト3:HTML5 SSE仕様に従ってHTTP POSTを送信するsendMessageメソッド String event = "event: message\n"; String retry = "retry: 300000\n"; data = "data: " + data + "\n\n"; URL url = new URL( uri.toASCIIString() ); HttpURLConnection urlConn = (HttpURLConnection)url.openConnection(); urlConn.setFixedLengthStreamingMode( event.length() + retry.length() + data.length()); urlConn.setDoOutput(true); urlConn.setDoInput(true); urlConn.setRequestMethod("POST"); urlConn.addRequestProperty("Content-Type", "text/event-stream"); urlConn.addRequestProperty("Authorization-Info", authCode); PrintWriter out = new PrintWriter( urlConn.getOutputStream() ); out.write(event); out.write(retry); out.write(data); 特に重要なのは、HttpURLConnection.setFixedLengthStreamingModeです。このメソッドは、HTTPリクエスト本体のストリーミングを内部バッファリングなしで行うことができるようにするためのもので、合計メッセージ・ペイロード長(改行文字を含む)をここに設定する必要があります。次に、HTTP Content-TypeとAuthorization-Info(省略可能)の各ヘッダー・フィールドを設定します。最後に、データをPOST本体に書き込みます。このメッセージは、メッセージ・サーバーで受信されて処理されます(後述)。   SSEメッセージの受信(クライアントのコード) sselibraryパッケージに含まれているSSEDataSubscriberヘルパー・クラスを使用すれば、アプリケーションでHTML5 Server-Sent Eventsを受信することは簡単です。このクラスを使うためには、まずSSECallbackインタフェースを実装します。このインタフェースでは、1つのメソッドonMessageが定義されており、このメソッドを通じてメッセージが配信されます(リスト4参照)。次に、SSEDataSubscriberクラスのインスタンスを作成し、コンストラクタにメッセージ・サーバーのURL、サーバーのタイプ(TopicまたはQueue)、および認証文字列(省略可能)を渡します。 リスト4:ヘルパー・クラスを使用してアプリケーションでSSEメッセージを受信する SSEDataSubscriber sse = new SSEDataSubscriber( serverURL, SSEDataSubscriber.DestinationType.QUEUE, auth); sse.subscribe(destinationName, this); // ... @Override public void onMessage(String queue, String data) { // ... } アプリケーションでリスナーを実装するために必要なものはこれだけです。それでは、SSEDataSubscriberヘルパー・クラスの内部を詳しく見てみます。 SSEDataSubscriberクラスの内部:SSEDataSubscriberクラスでは、SSEメッセージをリスニングするHTTPの仕組みを抽象化し、隠蔽しています。コンストラクタ(リスト5参照)では、宛先のタイプ(QueueまたはTopicか)に応じて、メッセージ・サーバーのRESTエンドポイントURLに適切なAPIパスを追加しています。 リスト5:適切なRESTエンドポイントURLを作成するコンストラクタ public SSEDataSubscriber( String serverURI, DestinationType type, String authCode ) { this.authCode = authCode; if ( type == DestinationType.QUEUE ) { this.serverURL = serverURI + "/api/queue/"; } else { this.serverURL = serverURI + "/api/topic/"; } } 次に、クライアント・アプリケーションがsubscribeを呼び出したときに、与えられた宛先の名前とコールバックが保存され、Threadが開始します。すると、リスト6に示すThread.runメソッドが実行されます。 リスト6:SSEDataSubscriberによって拡張されたThread.runメソッド実装 URL url = new URL(serverURL); URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setConnectTimeout(0); BufferedReader rd = new BufferedReader( new InputStreamReader( conn.getInputStream() ) ); String line; while ((line = rd.readLine()) != null) { if ( line != null && line.length() > 0 ) { // Did we get a heartbeat or useful data? if ( line.startsWith(":") ) { // heartbeat message... } else if ( line.startsWith("data:") ) { // Received data, send to the client's callback if ( callback != null ) { callback.onMessage(destination, line); } } } } このスレッドで、与えられた宛先に送信されるメッセージの処理専用として、メッセージ・サーバーへのコネクションが作成されます。HTTPメッセージを受信したら、ハートビート(空のメッセージ)であるか、実データを含むメッセージ(テキストdata:が存在する)であるかが判定されます。データは、与えられたコールバックのonMessageメソッドを使って非同期式にクライアントに配信されます。 それでは、メッセージ・サーバーのマイクロサービス(Micronaut.ioで実装されたもの)に戻り、それがどのようにメッセージの処理と配信を行っているのかを確認してみます。   Messengerベース・クラス 再びQueueControllerクラスとTopicControllerクラスに注目します。この2つはいずれもベース・クラスMessengerを継承していることに注意してください。TopicおよびQueueという両方の宛先タイプのprocessSendメソッド(前述のリスト2で参照されているもの)が実装されているのは、このクラスです(リスト7参照)。まず、データを改行文字で分割しています(SSEメッセージを送信した際、仕様に従って改行文字を追加したことを思い出してください)。 リスト7:メッセージ・サーバーのマイクロサービス内にあるMessenger.processSendメソッド public HttpResponse processSend(Destination dest, String data) { String[] lines = data.split(System.getProperty("line.separator")); try { for ( String line: lines) { if ( line.contains("event:")) { } else if ( line.contains("id:") ) { } else if ( line.contains("data:") ) { int start = line.indexOf("data:")+"data:".length(); data = line.substring(start).trim(); dest.addMessage(data); } } return HttpResponse.ok(dest.getName()); } catch ( Exception e ) { return HttpResponse.serverError(e.toString()); } } メッセージのdata:フィールドをメッセージのテキストから取得しています。そして、宛先のaddMessageメソッドを呼び出してメッセージ・データを渡しています。このメソッドはDestination抽象ベース・クラスで定義されていますが、その拡張クラスであるQueueとTopicでは実装が異なっています。次は、その点に注目してみます。 Topicクラスの内部:宛先の1つであるTopicの仕組みは単純です。送信された各メッセージは、すべてのアクティブなリスナーに配信されます。つまり、1対多関係が成立しています(図2参照)。   図2:トピックベースのパブリッシュ/サブスクライブ・メッセージングでは、各メッセージがすべてのアクティブなサブスクライバに配信される メッセージが送信されたとき、addMessageメソッドでは、メッセージのペイロード(テキスト)をカプセル化するMessageオブジェクトを作成し、TopicオブジェクトのlastMessageメンバー変数にMessageを格納して、Topicのモニター・オブジェクトを待機しているすべてのスレッドに対してシグナルを送ります。この処理をリスト8に示します。 リスト8:着信したトピック・メッセージの処理 public boolean addMessage(String msgData) { Long messageId = System.currentTimeMillis(); Message msg = new Message(messageId, msgData); lastMessage = msg; // Notify ALL listeners of the message synchronized (topicMonitor ) { topicMonitor.notifyAll(); } return true; } メッセージ・サーバーのRESTエンドポイントを呼び出してトピック・メッセージを受け取るすべてのクライアントは、最終的に、TopicクラスのgetNextMessageを呼び出し、メッセージが利用できるようになってシグナルを受け取るまで、そこでモニターを待機することになります(リスト9)。 リスト9:メッセージが宛先に到着するまで待機するTopic.getNextMessageメソッド public Message getNextMessage() throws InterruptedException { synchronized ( topicMonitor ) { topicMonitor.wait(); } return lastMessage; } トピックについては以上です。一方のQueueクラスは、もう少し込み入っています。 Queueクラスの内部:TopicとQueueの最大の違いは、Queueには以下の特徴があることです。 メッセージは最大1つのリスナーに配信される必要がある(図3参照) メッセージは、将来的に配信される可能性があるため、リスニングしているクライアントがなくても保存する必要がある   図3:キューによって、各送信メッセージが厳密に1つのリスナーに配信される Queue.getNextMessageは、クライアントがメッセージ・サーバーのRESTエンドポイントを呼び出したときに呼び出されます(前述のリスト1で示しています)。メッセージ自体がメモリに保持されることはなく、メッセージIDのみが保持されます(リスト10参照)。キューに格納されたメッセージは、キューに関連付けられたリスナーがメッセージを取り出そうとするまで、無期限にキューにとどまる可能性があります。そのため、メッセージすべてを保存した場合、メモリをすべて使い切ってしまう可能性があります。そこでその代わりに、メッセージのデータを永続化しています。 リスト10:同じ宛先のキューに格納された次のメッセージIDを待機する間、ブロックされるコール元 public Message getNextMessage() throws InterruptedException { // 呼出しのブロック Long messageId = messageIds.take(); // IDを使ってメッセージ・データをロード Message message = persistance.getMessage(getName(), messageId); return message; }   messageIdオブジェクトは、java.util.concurrent.ArrayBlockingQueueとして実装されています。takeの呼出しは、キューに格納されたエントリが利用できるようになるまでブロックされます。その際に、このコードではキューの先頭を削除し、ブロックされているコール元1つのみに返却しています。メッセージIDが手に入ったら、メッセージのペイロードを永続化ストアから取得しています(この点は、キューベースのメッセージングにおける信頼性の一部です)。最終的に、メッセージが配信されれば、そのメッセージIDと永続化されていたメッセージ本体は削除されます。   信頼性の高いメッセージングの実装 MessagePersistenceインタフェースは、実際にメッセージを永続化する仕組みの詳細を隠蔽するために定義されています。Queueオブジェクトでは、ファクトリ・パターンを使って永続化実装のインスタンスを取得しています(リスト11参照)。 リスト11:ファクトリ・パターンを使って永続化実装を取得するQueueオブジェクト public class Queue extends Destination { final protected MessagePersistance persistance = MessagePersistanceFactory .getInstance().getMessagePersistance(); //... } このファクトリ・パターンは、MessagePersistenceインタフェースを実装した任意の永続化実装をロードするように構成することができます(その際に、依存性注入、構成ファイル、環境変数などを使用できます)。例として、1つの実装を確認してみます。 NoSQLデータベースによる永続化:MessageNoSQLクラス(ダウンロード・パッケージに含まれています)では、MessagePersistenceインタフェースを実装しており、Oracle NoSQL Databaseを使ってメッセージIDによるメッセージの格納と取得を行います。QueueControllerクラスでは、ファクトリ・パターンを使っており、このインタフェースにのみ依存しているため、簡単にクラウドベースのNoSQLデータベースなどの実装と交換できます。 メッセージは、名前/値ペアを使って格納しています。キー(名前)は、宛先名とメッセージIDを組み合わせたものです。値はメッセージのペイロードで、バイト配列としてエンコードしています(リスト12参照)。 リスト12:メッセージをNoSQLデータストアに保存 public boolean saveMessage( String destinationName, Long messageId, String message) throws Exception { String idStr = messageId.toString(); store.put( Key.createKey(destinationName, idStr), Value.createValue(message.getBytes()) ); return true; } メッセージの取得も簡単です(リスト13参照)。まず、組み立てたキーを使用して、Valueオブジェクトを取得しています。このオブジェクトが見つかった場合、それを使って格納されたバイト配列を取得しています。この配列がメッセージのペイロードを表しています。結果は、Messageオブジェクトに変換して返却しています。 リスト13:キー(宛先名とメッセージID)からメッセージ・ペイロードを取得 public Message getMessage(String destinationName, Long messageId) { String idStr = messageId.toString(); Key key = Key.createKey(destinationName, idStr); ValueVersion value = store.get(key); if ( value == null || value.getValue() == null ) { return null; } Value val = value.getValue(); String data = new String( val.getValue() ); return new Message( messageId, data); } 最初にメッセージ・サーバーが起動したときに、NoSQLデータベースに格納されているすべてのメッセージIDをメモリにロードします。これを行うため、まずQueueControllerで、永続化されているメッセージに関連付けられているすべての宛先名について反復処理を行っています(リスト14参照)。続いてgetQueueが呼ばれることで、各宛先名についてQueueオブジェクトが作成されます(キューのみが永続化されるため、これは安全です)。 リスト14:NoSQLデータベースからすべての宛先名を取得  private void loadSavedMessages() { ArrayList<String> queueNames = messageDB.getStoredDestinations(); for ( String queueName: queueNames ) { // Get the queue (loads all queued messages) Queue dest = getQueue(queueName); } } getQueueメソッド(ベース・クラスのMessengerで実装されています)では、与えられた宛先名を使ってQueueオブジェクトを作成しています。これにより、コンストラクタで指定されたデータベースから、そのキューに対応するすべてのメッセージIDがロードされます(リスト15参照)。 リスト15:キューに対応するすべてのメッセージIDをデータベースからロード public Queue(String name) { // load message IDs ArrayList<Long> ids = persistance.getMessageIds(name); if ( ids != null ) { this.messageIds.addAll( ids ); } } このサンプル実装では、kvstoreという名前のデータベースがローカル(127.0.0.1)のポート5000で実行されていることを仮定しています。これは構成ファイルでオーバーライドすることができます。 Oracle NoSQL Databaseの実行:本記事のSSEメッセージ・サーバーのコードを実行するためには、Oracle NoSQL Database Community Editionをダウンロードしてインストールします。データベースをインストールした後、config.xmlファイルを変更して、hostnameにお使いのコンピュータを、registryPortに5000を設定します。このデータベースを実行するために、次のコマンドを使用します。 > java -jar lib/kvstore.jar kvlite -secure-config disable 最後のパラメータでは、このサンプル実装を実行しやすくするためにセキュリティを無効化しています。しかし、本番環境ではこのパラメータを使うべきではありません。データベースが起動すると、次のように出力されます。 Opened existing kvlite store with config: -root ./kvroot -store kvstore -host Dolce -port 5000 -secure-config disable -restore-from-snapshot null 以上で、SSEメッセージ・サーバーを実行できるようになります。SSEメッセージ・サーバーについては、次のセクションで説明します。   エンド・ツー・エンドのデモ Micronautを使ったSSEメッセージ・サーバーは、次のコマンドで起動できます(最初にNoSQLデータベースを起動することを忘れないでください)。 > java -jar target/MessageServer-1.0-SNAPSHOT.jar コマンドの実行に成功した場合、次のような行で終わるログが出力されます。 12:53:28.857 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 701ms. Server Running: http://localhost:8080 キュー・レシーバを実装するためには、ヘルパー・クラスSSEDataSubscriberを使用します。このクラスでは、コンストラクタのパラメータとして、MicronautサーバーURI、宛先のタイプ(QueueまたはTopic)、および認証コード(省略可能)を受け取ります。キュー・レシーバの作成後、subscribeメソッドを呼び出してキューをリスニングします(リスト16参照)。 リスト16:キューのサブスクライブ SSEDataSubscriber sse = new SSEDataSubscriber( serverUrl, SSEDataSubscriber.DestinationType.QUEUE, authCode); sse.subscribe( queueName, this ); この場合、呼び出す側のクラスでSSECallbackインタフェースを実装し、subscribeを呼び出す際に自身への参照を渡しています。メッセージがキューに着信したときに、オブジェクトのonMessageメソッドが呼び出されてペイロードが渡されます。この実現方法を確認したい方は、本記事のリスト6をご覧ください。 キューにメッセージを送信するためには、SSEDataPublisherクラス(前述のリスト3で説明しています)を使います。リスト17をご覧ください。 リスト17:キューへのメッセージ送信 String url = serverURI + "/api/queue/publish?name=" + queueName; SSEDataPublisher.sendMessage(url, data, authCode); 必要なものはこれだけです。キューおよびトピックに対するメッセージの送受信に同じヘルパー・クラスを使えるため、いずれのタイプのアプリケーションでも、Javaコードは非常に似たものになります。次は、Webアプリケーション内でデータの更新を動的に表示するための、JavaScriptのリスナーを作成する方法を確認します。 JavaScript SSEリスナーの実装:この最後のサンプルでは、トピックを使って疑似的な最新の温度を送信し、SSEを使ってWebページ上で動的に更新します。Javaから温度を送信する部分は、先ほどのセクションで確認したキュー・センダーと同様のものです。しかし、リスナーはHTMLベースの単純なWebページに埋め込まれたJavaScriptコードです。 まず、リスト18に示すようにEventSourceインスタンスを作成します。 リスト18:EventSourceインスタンスの作成 var source = new EventSource(uri+"/messageserver/api/topic/temp1"); 次に、リスト19に示すように、デバッグとコネクションのリセットに使用するエラー・ハンドラをセットアップします。 リスト19:エラー・ハンドリングのセットアップ source.onerror = function(event) { console.log("SSE onerror " + event); // 1秒待って再接続する setTimeout(function() { setupEventSource(); }, 1000); }   最後に、リスト20に示すメッセージ・リスナー関数(ペイロードはここに配信されます)を実装します。 リスト20:メッセージ・リスナー関数の実装 source.onmessage = function(event) { var tempGauge = document.getElementById('temperature'); tempGauge.innerHTML = event.data; } この例において、tempGaugeは、更新される温度値を表示するために使用する、HTMLのdiv要素です。SSEメッセージ・サーバーが実行中で、送信元からtemp1トピックにデータがパブリッシュされたとき、Webページは図4のようになります。   図4:温度の測定値を更新する単純なJavaScriptを含むWebアプリケーション なお、クロスサイト・スクリプティング・セキュリティの関係で、Chromeなどのブラウザでは、Webページを公開しているURL以外からのデータがブロックされることに注意してください。これに対処する方法はいくつかありますが、開発目的の場合に簡単な方法は、次のパラメータを使ってコマンドラインからChromeを起動することです。 google-chrome --disable-web-security --user-data-dir=<User home dir> Windowsでは、google-chromeをchrome.exeに置き換えます。 さらに簡単な例として、いつもどおりブラウザを開いてから、有効なSSEの宛先の適切なURLを入力することもできます。たとえば、http://localhost:8080/messageserver/api/topic/topic1などです。その結果、データが更新されるたびにブラウザのページに追加されますが、やがては最新の値を確認するためにスクロールが必要となります。   まとめ 本記事では、MicronautのSSEサポートを使用して、キューベースのメッセージングとトピックベース(パブリッシュ/サブスクライブ)のメッセージングに対応した、シンプルで信頼性の高いメッセージ・システムを構築する方法を説明しました。Micronautの起動時間は非常に短く、スループットに優れ、メモリのオーバーヘッドは少なくなっています。そのため、インスタンスが短時間でスピンアップおよびスピンダウンされるクラウドベースのマイクロサービスで、優れた選択肢となっています。 Eric J. Bruno Dellのアドバンスト・リサーチ・グループに所属。主にエッジや5Gを扱う。大規模分散ソフトウェアの設計、リアルタイム・システム、エッジ/IoTを専門とするエンタープライズ・アーキテクト、開発者、アナリストであり、およそ30年にわたって情報テクノロジー・コミュニティで活躍している。Twitterのフォローは@ericjbrunoから。 Eric J. Bruno Dellのアドバンスト・リサーチ・グループに所属。主にエッジや5Gを扱う。大規模分散ソフトウェアの設計、リアルタイム・システム、エッジ/IoTを専門とするエンタープライズ・アーキテクト、開発者、アナリストであり、およそ30年にわたって情報テクノロジー・コミュニティで活躍している。Twitterのフォローは@ericjbrunoから。

※本記事は、Eric J. Brunoによる"HTML5 Server-Sent Events with Micronaut.io and Java"を翻訳したものです。 シンプルで信頼できるメッセージ・サービスを構築する 著者:Eric J. Bruno 2020年4月6日   筆者は最近、マイクロサービス・フレームワークのクラウド側実装としてMicronautを選択した、エンド・ツー・エンドIoTプロジェ...

PactでJavaマイクロサービスをテストする

※本記事は、Alex Soto BuenoとAndy GumbrechtとJason Porterによる"How to Test Java Microservices with Pact"を翻訳したものです。 マイクロサービス・アプリケーションがもたらす独自のテスト課題 著者:Alex Soto Bueno、Andy Gumbrecht、Jason Porter 2020年4月6日 [編集注:本記事は、Alex Soto Bueno氏、Andy Gumbrecht氏、Jason Porter氏による書籍『Testing Java Microservices』(Manning、2018年)の「Contract Tests」の章に基づいています。]   マイクロサービス・アーキテクチャでは、マイクロサービス間で大量の通信が発生します。この相互通信により、サービス間のコントラクト(契約)が効果的に形成されます。このコントラクトは、入力データおよび出力データの期待値、そして事前条件と事後条件で構成されます。 あるサービスが要件を定め、その要件に基づいて別のサービスで提供(または生成)されるデータを使用する場合、そのようなサービスごとにコントラクトが形成されます。データを生成するサービスが時間によって変化する可能性がある場合、そのデータを使用する各サービスとのコントラクトが期待値を満たし続けることが重要です。コントラクト・テストでは、コンポーネントがコントラクトを満たすことを明示的に検証する仕組みが提供されます。 ここでは、コントラクト・テストを記述するために使用できるツールについて見ていきます。とりわけ、コンシューマ指向のコントラクト・テストをサポートするテスト・フレームワーク・ファミリーであるPactに注目します。Pactには、Ruby、JVM言語、.NET、JavaScript、Go、Python、Objective-C、PHP、Swiftの公式実装があります。 私たちの意見では、コントラクト・テストを行っている現場でもっとも普及し、成熟しているプロジェクトがPactフレームワークです。主な利点の1つとして、現在マイクロサービスを書くために使われている、ほぼすべての主要言語がサポートされていることが挙げられます。さらに、フロントエンドからバックエンドまで、プログラミング言語によらず同じ考え方を再利用できます。こういった理由から、Pactはコンシューマ指向コントラクトを記述するための、もっとも汎用的なソリューションであると強く確信しています。また、Javaで開発したマイクロサービス・アーキテクチャとの間には高い親和性があります。   Pactフレームワーク Pactフレームワークにより、コンシューマ側でコントラクトを書くことができます。その際に、モックHTTPサーバーと柔軟なAPIを使い、コンシューマからサービス・プロバイダへのHTTPリクエストと、返されることが期待されるHTTPレスポンスを定義することができます。モックHTTPサーバーは、このHTTPのリクエストとレスポンスを使ってサービス・プロバイダを代行します。そして、その相互通信を使うことで、サービス・コンシューマとサービス・プロバイダの間のコントラクトが生成されます。 Pactではさらに、プロバイダ側と照合してコントラクトを検証するロジックも提供されます。コンシューマで発生した通信は、すべて「実際の」サービス・プロバイダに向けて再生されます。これによりプロバイダは、与えられたリクエストに対してコンシューマが期待するレスポンスを、確実に生成できるようになっています。予期しないものをプロバイダが返した場合、Pactは通信を失敗とマークし、コントラクト・テストは失敗します。 すべてのコントラクト・テストは、コンシューマのテストとプロバイダのテストという2つの部分で構成されます。加えて、コンシューマからプロバイダにコントラクト・ファイルが送信されます。Pactを使ったコントラクト・テストのライフサイクルは、以下のようになっています。 ステップ1:柔軟なAPIを使ってコンシューマの期待値をモックHTTPサーバーにセットアップします。コンシューマは、HTTPのリクエストとレスポンスを処理するモックHTTPサーバーと通信しますが、プロバイダとは通信しません。このようにすることで、コンシューマがプロバイダのデプロイ方法を知る必要はなくなります(この方法を知るのは簡単ではなく、コントラクト・テストではなくエンド・ツー・エンド・テストを書くことになる可能性が高いからです)。コンシューマは定義された相互通信を使い、クライアントやゲートウェイのコードがモックHTTPサーバーと通信できることを確認します。 コンシューマ・テストが実行されると、すべての相互通信がpactコントラクト・ファイルに書き込まれます。このファイルでは、コンシューマとプロバイダが従わなければならないコントラクトを定義します。 ステップ2:pactコントラクト・ファイルがプロバイダ・プロジェクトに送信され、プロバイダ・サービスに向けて再生されます。コントラクトが実際のプロバイダに向けて再生されると、プロバイダからの実際のレスポンスが、コントラクトで定義された、期待されるレスポンスと照合されます。 コンシューマがpactコントラクト・ファイルを生成でき、プロバイダがすべての期待値を満たす場合、両者によるコントラクトの検証が完了し、両者は通信できるようになります。 以上のステップを図1に示します。   図1:Pactによるコントラクト・テストのライフサイクルにおける2つのステップ   まとめると、Pactでは以下の機能が提供されます。 プロバイダに依存する必要がなくなるように、モックHTTPサーバーが提供されます。 期待値を自動再生するHTTPクライアントが提供されます。 期待値が再生される前に、期待される状態がコンシューマ側からプロバイダに送信されます。たとえば、通信のためには、期待値が再生される前に、プロバイダのデータベースにAlexandraというユーザーが含まれていなければならないというような場合があります。 Pact Brokerは、コントラクトのリポジトリです。Pact Brokerにより、pactをコンシューマとプロバイダで共有できます。また、プロバイダが固定のバージョンのコントラクトと照合して自身を検証できるように、pactコントラクト・ファイルのバージョン管理が行われます。さらに、各pactのドキュメントが提供されるとともに、サービス間のリレーションシップが視覚化されます。 次は、Java仮想マシン向けのPact実装であるPact JVMについて見ていきます。   JVM言語でのPact Pact JVMはScala、Groovy、Javaを使って書かれていますが、任意のJVM言語で使用できます。Java、Scala、Groovy、Grails(コントラクトを定義するためにGroovy DSLが提供されています)、Clojureとの間には、非常に高い親和性があります。さらに、JUnit、Spock、ScalaTest、Specs2などのテスト・フレームワークや、Maven、Gradle、Leiningen、sbtなどのビルド・ツールと緊密に統合されています。本記事ではJavaのツールについて取り上げますが、他のJVM言語を使う予定の方も、Pact JVMを用いてコンシューマ指向のコントラクト・テストを行えることは覚えておいてください。 次は、Pact JVMを使ってコンシューマ・テストとプロバイダ・テストを書く方法を紹介します。 Pact JVMによるコンシューマ・テスト:Pact JVMでは、モックHTTPサーバーと、モックHTTPサーバーの期待値を記述するJava DSLが提供されています。この期待値は、コンシューマ・テストに合格したときに、pactコントラクト・ファイルとして実体化されます。 Pact JVMはJUnitと統合されており、DSLと、JUnitでコンシューマ・テストをビルドする際に使用できるベース・クラスが提供されています。JUnitによるコンシューマ・テストを書くにあたって最初に行うことは、PactProviderRule JUnitルールの登録です。このルールでは、以下の処理を行います。 モックHTTPサーバーを起動、停止する モックHTTPサーバーを定義済みの期待値で構成する テストに合格した場合、定義済みの期待値からpactコントラクト・ファイルを生成する 次に例を示します。 @Rule public PactProviderRule mockProvider = new PactProviderRule("test_provider", "localhost", 8080, this); 最初の引数は、現在のコンシューマ・コントラクトが定義するプロバイダの名前です。この名前は、指定されたコントラクトのプロバイダを参照するために使用します。続く2つは省略可能なパラメータで、モックHTTPサーバーがバインドされているホストとリスニングしているポートです。値が指定されていない場合、それぞれlocalhostと8080が使われます。最後のthisインスタンスは、自分がテストであることを示しています。 次に、メソッドにau.com.dius.pact.consumer.Pactアノテーションを付加して期待値を定義します。このメソッドは、PactDslWithProvider型のクラスを受け取り、PactFragmentを返さなければなりません。PactDslWithProviderはDSLパターンを使って構築されたJavaクラスで、モックHTTPサーバーが使われる場合に受け取ることが期待されるリクエストの説明を提供します。 名前からもわかるように、PactFragmentオブジェクトはコントラクトの断片です。このオブジェクトは、モックHTTPサーバーで期待値として使われることに加え、プロバイダの検証に使用するpactコントラクト・ファイルを生成するためにも使われます。断片は、完全なコントラクトでも、コントラクトの一部でも構いません。同じテスト・クラスに複数の断片が定義されている場合、すべての断片を合わせたものがpactコントラクト・ファイルになります。 @Pactメソッドには次のシグネチャが必要です。 @Pact(provider="test_provider", consumer="test_consumer") public PactFragment createFragment(PactDslWithProvider builder) { //... } @Pactアノテーションに、コントラクトに従う必要があるプロバイダの名前と、コントラクトを定義するコンシューマの名前を設定することに注目してください。この情報は、プロバイダからデータが提供されるすべてのコンシューマに対して確実にプロバイダ側のテストを実行するために重要です。 次のスニペットでは、リクエストとレスポンスの期待値を定義しています。PactDslWithProviderには、いくつかのオプションを定義できます。 return builder .uponReceiving("a request for something") .path("/hello") .method("POST") .body("{\"name\": \"Ada\"}") .willRespondWith() .status(200) .body("{\"hello\": \"Ada\"}") .uponReceiving("another request for something") .matchPath("/hello/[0-9]+") .method("POST") .body("{\"name\": \"Ada\"}") .willRespondWith() .status(200) .body("{\"hello\": \"Ada\"}") .toFragment(); 上記のサンプルでは、2つの期待値を定義しています。最初のリクエストは、コンシューマがPOSTメソッドを使って/helloにリクエストを送ったときに発生します。メッセージ本体には、JSONドキュメント{"name":"Ada"}が正確に含まれている必要があります。その場合、レスポンスはJSONドキュメント{"hello":"Ada"}となります。2つ目のリクエストは、パスが/helloで始まり、その後に有効な数値が続く場合に発生します。条件は最初のリクエストと同じです。 相互通信はいくつでも必要なだけ定義できることに注意してください。個々の通信はuponReceivingで始まり、その後にレスポンスを記録するためのwillRespondWithが続きます。 ところで、できるだけテストをシンプルにして可読性を保ち、「1つのメソッドで1つのタスク」のアプローチを維持するために、すべてを返す1つの大きな@Pactメソッドを定義するのではなく、すべての通信に対して複数の断片を使用することをお勧めします。 先ほどの定義における重要な側面の1つに、本体の内容がコントラクトで指定されているものと同じでなければならないという点があります。たとえば、最初のリクエストには、そのJSONドキュメントが{"name":"Ada"}である場合にのみレスポンスが提供されるという厳格な要件があります。名前がAda以外の場合、レスポンスは生成されません。レスポンス本体も同様です。JSONドキュメントが静的であるため、レスポンスは常に同じになります。 静的な値を設定できない場合、とりわけプロバイダに対してコントラクトを実行するときは、これが制限になる可能性があります。そのため、ビルダーのbodyメソッドは、JSON本体を動的に構築するために使用できるPactDslJsonBodyを受け取れるようになっています。 PactDslJsonBodyクラス:PactDslJsonBodyビルダー・クラスはDSLパターンを実装しています。このパターンを使用して、JSON本体を動的に構築することに加え、フィールドの正規表現や型の照合を定義することができます。いくつかの例を見てみます。 次のスニペットでは、配列を使わない単純なJSONドキュメントを生成しています。 DslPart body = new PactDslJsonBody() .stringType("name") .booleanType("happy") .id() .ipAddress("localAddress") .numberValue("age", 100); xType形式を使って、省略可能な値パラメータを設定することもできます。このパラメータは、モックのレスポンスを返す際にサンプル値を生成するために使います。サンプルが指定されていない場合は、ランダムな値が生成されます。 先ほどのPactDslJsonBody定義は、次のような本体に一致します。 { "name" : "QWERTY", "happy": false, "id" : 1234, "localAddress" : "127.0.0.1", "age": 100, } 必要な型の必要なフィールドがすべて含まれ、値が100であるageフィールドを持つドキュメントは、すべて有効である点に注意してください。 PactDslJsonBodyでは、配列の照合を定義するメソッドも提供されています。たとえば、リストが最小サイズまたは最大サイズの条件を満たすことの検証や、リスト内の各項目が指定されたサンプルと一致することの検証を行うことができます。 DslPart body = new PactDslJsonBody() .minArrayLike("products", 1) .id() .stringType("name") .stringMatcher("barcode", "a\\d+", "a1234") .closeObject() .closeArray(); 上記の例の場合、products配列を空とすることはできません。そして、すべてのプロダクトにidとstring型のname、さらに「a」に数字のリストを加えた形式のbarcodeが存在する必要があります。 要素のサイズが重要でない場合は、次のようにすることもできます。 PactDslJsonArray.arrayEachLike() .date("expireDate", "mm/dd/yyyy", date) .stringType("name") .decimalType("amount", 100.0) .closeObject() 上記の例では、各配列にexpireDate、name、amountという3つのフィールドが含まれる必要があります。さらに、モックのレスポンスの各要素について、expireDateフィールドに日付型の値が、nameフィールドにランダムな文字列が、amountに値100.0が含まれる必要があります。 おわかりのように、DslPartを使って本体を生成することにより、具体的なフィールドと値のペアではなく、フィールドの型を定義できます。これにより、プロバイダ側でコントラクトを検証する際のコントラクトの柔軟性が高まります。たとえば、プロバイダの検証フェーズで.body("{'name':'Ada'}")を設定する場合を考えてみます。プロバイダは、同じ値を持つJSONドキュメントを生成することになっています。これはほとんどの場合で正しいでしょうが、テスト用のデータセットが変わり、.body("{'name':'Ada'}")ではなく、.body("{'name':'Alexandra'}")を返すようになった場合、テストは失敗します。しかし、コントラクトという観点で見れば、いずれのレスポンスも有効です。 ここまでは、コンシューマ側でのPactによるコンシューマ指向コントラクトの書き方について見てきました。次は、プロバイダ部分でのテストの書き方を説明します。 Pact JVMによるプロバイダ・テスト:コンシューマ部分でテストを行い、pactコントラクト・ファイルを生成して公開した後は、実際のプロバイダに向けてコントラクトを再生する必要があります。この部分のテストはプロバイダ側で実行します。Pactでは、そのためのツールがいくつか提供されています。 JUnit:JUnitテストでコントラクトを検証するツール Gradle、Leiningen、Maven、sbt:実行中のプロバイダと照合してコントラクトを検証するためのプラグイン ScalaTest:実行中のプロバイダと照合してコントラクトを検証するための拡張機能 Specs2:実行中のプロバイダと照合してコントラクトを検証するための拡張機能 通常、上記の機能では、公開されたコントラクトを2つの方法で取得します。具体的には、Pact Brokerと使う方法と、実際の場所(ファイルまたはURL)を指定する方法です。取得メソッドをどのように構成するかは、コントラクトの再生方法によって変わります。たとえば、JUnitではアノテーションによるアプローチを使用しますが、Mavenではプラグインの構成セクションを使用します。 それでは、Maven、Gradle、JUnitを使用してプロバイダ検証を実装する方法を確認していきます。 Mavenを使ったコントラクト検証:Pactでは、プロバイダと照合してコントラクトを検証するMavenプラグインが提供されています。このプラグインを使うためには、pom.xmlのプラグイン・セクションに次の内容を追加します。 <plugin> <groupId>au.com.dius</groupId> <artifactId>pact-jvm-provider-maven_2.11</artifactId> <version>3.5.0</version> </plugin> 次に、Mavenプラグインを構成し、検証するすべてのプロバイダを定義し、その確認に使用するコンシューマ・コントラクトの場所を定義する必要があります。 <plugin> <groupId>au.com.dius</groupId> <artifactId>pact-jvm-provider-maven_2.11</artifactId> <version>3.2.10</version> <configuration> <serviceProviders> <serviceProvider> <name>provider1</name> <protocol>http</protocol> <host>localhost</host> <port>8080</port> <path>/</path> <pactFileDirectory>path/to/pacts</pactFileDirectory> </serviceProvider> </serviceProviders> </configuration> </plugin> コントラクトを検証するためには、mvn pact:verifyを実行します。Mavenプラグインでは、指定されたディレクトリで定義されているすべてのpactコントラクトをロードし、指定されたプロバイダ名に一致するものを再生します。プロバイダと照合してすべてのコントラクトが検証されると、ビルドは成功します。そうでない場合、ビルドは失敗します。 Gradleを使ったコントラクト検証:Gradleプラグインでは、プロバイダと照合してコントラクトを検証する際に、Mavenと同様のアプローチを使用します。このプラグインを使うためには、build.gradleのpluginsセクションに次の内容を追加します。 plugins { id "au.com.dius.pact" version "3.5.0" } 次に、Gradleプラグインを構成し、検証するプロバイダを定義し、その確認に使用するコンシューマ・コントラクトの場所を定義します。 pact { serviceProviders { provider1 { protocol = 'http' host = 'localhost' port = 8080 path = '/' hasPactsWith('manyConsumers') { pactFileLocation = file('path/to/pacts') } } } } コントラクトを検証するためには、gradlew pactVerifyを実行します。Gradleプラグインでは、指定されたディレクトリで定義されているすべてのPactコントラクトをロードし、指定されたプロバイダ名に一致するものを再生します。プロバイダと照合してすべてのコントラクトが検証されると、ビルドは成功します。そうでない場合、ビルドは失敗します。 最後に、ビルド・ツールには頼らず、JUnitを使ってプロバイダを検証する方法を説明します。 JUnitを使ったコントラクト検証:Pactでは、プロバイダと照合してコントラクトを検証するJUnitランナーが提供されています。このランナーには、構成されたプロバイダに向けてすべてのコントラクトを自動再生するHTTPクライアントが含まれています。さらに、アノテーションを使ってすぐにpactをロードできる便利な方法も提供されています。 JUnitによるアプローチを使う場合、PactRunnerを登録し、@Providerアノテーションにプロバイダ名を指定して、コントラクトの場所を設定する必要があります。その後、au.com.dius.pact.provider.junit.target.Target型のフィールドを作成して@TestTargetアノテーションを付加します。そして、au.com.dius.pact.provider.junit.target.HttpTargetのインスタンスを作成し、pactコントラクト・ファイルをHTTPリクエストとして再生してレスポンスをアサートします。または、au.com.dius.pact.provider.junit.target.AmqpTargetのインスタンスを作成し、pactコントラクト・ファイルをAdvanced Message Queuing Protocol(AMQP)メッセージとして再生することも可能です(AMQPはメッセージ指向ミドルウェア用のアプリケーション・レイヤー・プロトコルで、メッセージ・オリエンテーション、キューイング、ルーティング、信頼性、セキュリティの各機能が定義されています)。 PactTest.javaからHttpTargetを使う例を見てみます。 @RunWith(PactRunner.class) @Provider("provider1") @PactFolder("pacts") public class ContractTest { @TestTarget public final Target target = new HttpTarget("localhost", 8332); } @Testアノテーションが付加されたテスト・メソッドがないことに注意してください。そういったテスト・メソッドが必要でないのは、テストは1つではなく、多数存在するからです。つまり、コンシューマとプロバイダの間の1回の通信につき、1つのテストが存在します。 このテストを実行すると、JUnitランナーではpactsディレクトリからすべてのコントラクト・ファイルを取得し、そこで定義されているすべての通信をプロバイダの場所に向けて再生します。プロバイダの場所は、HttpTargetインスタンスで指定されているものを使います。 PactRunnerでは、テスト・クラスのアノテーションに基づいて自動的にコントラクトをロードします。Pactでは、そのためのアノテーションが3つ提供されています。 PactFolder:プロジェクト・フォルダまたはリソース・フォルダからコントラクトを取得します。例:@PactFolder("subfolder/in/resource/directory") PactUrl:URLからコントラクトを取得します。例:@PactUrl(urls = {"http://myserver/contract1.json"}) PactBroker:PactBrokerからコントラクトを取得します。例:@PactBroker (host="pactbroker", port = "80", tags = {"latest", "dev"}) Custom:カスタムの取得機能を実装するためには、PactLoaderインタフェースを実装し、1つの空のデフォルト・コンストラクタを持つか、またはClass型の1つの引数(テスト・クラスを表します)があるコンストラクタを持つクラスを作成します。そして、次のようにしてテストにアノテーションを付加します。@PactSource(CustomPactLoader.class) 独自のメソッドも容易に実装できます。 Pactの状態:テストを行う場合、それぞれの通信は別々に検証する必要があります。以前の通信のコンテキストは一切引き継ぐべきではありません。しかし、コンシューマ指向コントラクトでは、プロバイダがコンシューマの期待値に一致するレスポンスを送れるようにするため、通信を実行する前にコンシューマがプロバイダ側で何かを準備したい場合もあります。一般的な使用例として、期待されるデータが格納されたデータソースを準備する場合が挙げられます。たとえば、認証操作のコントラクトをテストする際、通信が発生したときにプロバイダのロジックが適切にデータに反応できるように、プロバイダがあらかじめ実際のログインやパスワードをデータベースに挿入しておくことがコンシューマにとって必要な場合があります。図2は、コンシューマ、状態、プロバイダ間の相互通信についてまとめたものです。   図2:コンシューマ、状態、プロバイダ間の相互通信 まず、次に示すJSON本体を含むPOSTメソッドで認証プロセスを実行する必要があることを、コンシューマ側で定義します。 { "login": "John", "password": "1234" } このスニペットはプロバイダに向けてコントラクトを再生する際に使われるため、コンシューマはプロバイダに向けて通信を行う前に、指定された情報を使ってデータベースの準備をするようプロバイダに警告する必要があります。そのために、コンシューマはState Authenticationと呼ばれる状態を作成します。この状態には、必要なデータがすべて含まれ、状態の情報はコントラクトの中に格納されます。 プロバイダに向けてコントラクトが再生されたときは、テストでコントラクト検証の環境を準備できるように、通信が行われる前に状態データがテストに注入されます。最後に、期待されるユーザー情報を含むデータベースを使用してコントラクトの検証が実行されます。 コンシューマ側から状態を定義するためには、コントラクトの定義の際に特別なメソッドgivenを使う必要があります。 @Override protected PactFragment createFragment(PactDslWithProvider builder) { Map<String, Object> parameters = new HashMap<>(); parameters.put("login", "John"); parameters.put("password", "1234") builder .given("State Authentication", parameters) .uponReceiving("") ….. プロバイダ側で状態に反応するためには、@Stateアノテーションを付加したメソッドを作成する必要があります。 @State("State Authentication") public void testStateMethod(Map<String, Object> params) { //データの挿入 } 状態を使うことで、コンシューマとプロバイダの間で情報を共有できるため、通信が行われる前にテストの状態を構成できる点に注意してください。Pactの状態は、コンシューマ側からプロバイダの状態を準備する際に推奨される機能です。 MavenおよびGradleに組み込む場合も、プロバイダ側の状態を設定する方法が提供されています。その場合は、各プロバイダに状態変化URLを指定することで、プロバイダの状態を変更します。このURLでは、それぞれの通信が行われる前に、POSTメソッドを使ってpactコントラクト・ファイルからproviderStateの記述内容を受け取ります。   まとめ コンシューマ指向のコントラクト・テストにPactを使うことには、さまざまなメリットがあります。 コンシューマ指向コントラクトを使うことで、テストの実行が高速化されます。 HTTPスタブ・サーバーがあるため、信頼できるレスポンスを常に受け取ることができ、テストの信頼度が向上します。 テストがコンシューマとプロバイダに分割されるため、失敗の原因を特定しやすくなります。 コンシューマ指向コントラクトの組み込みは設計プロセスです。 「コンシューマ指向コントラクト」は「ダイレクタ・コンシューマ指向コントラクト」ではありません。コントラクトはコンシューマ側で始まる協調作業の開始点ですが、両方での作業が必要です。 コントラクト・テストでは、コンシューマ側がプロバイダ側のパッケージング方法やデプロイ方法を知っている必要はありません。コンシューマ側が知っている必要があるのは、コンシューマ部分をデプロイする方法のみです。プロバイダでコントラクトの検証を行うとき、プロバイダは、自身のデプロイ方法と、自身の依存性をモックまたはスタブする方法を知っています。これは、テストを実行できるようにするために完全な環境を開始しなければならないエンド・ツー・エンド・テストとは大きく異なります。 コンシューマ指向コントラクトは、従うべき最善のアプローチとは限らないかもしれません。通常、コンシューマ指向コントラクトは最善のアプローチですが、状況によっては、プロバイダ指向コントラクトやコンシューマ・コントラクトを使った方がよいでしょう。 Alex Soto Bueno Red Hatの開発者グループに所属するソフトウェア・エンジニア。Javaの世界とソフトウェア自動化に情熱を抱き、オープンソース・ソフトウェア・モデルを信奉する。NoSQLUnitプロジェクトの創設者であり、JSR 374(Java API for JSON processing)Expert Groupのメンバー。『Testing Java Microservices』(Manning)の共著者、Istio Refcardの共同作成者でもあるほか、複数のオープンソース・プロジェクトに貢献している。2017年にJava Championとなり、マイクロサービス向けの新しいテスト技法、21世紀の継続的デリバリ、Javaについて国際的に講演を行っている。Twitterのフォローは@alexsotobから。   Andy Gumbrecht Apache TomEE PMCメンバー、開発者であり、Tomitribeのエバンジェリストを務めた経験を持つ。現在は、Phoenix-Contact(ドイツ)でソフトウェア・アーキテクトとして勤務しつつ、OpenEJB、TomEEなどのApacheプロジェクトや、Arquillianテスト・フレームワークへの積極的な貢献を続けている。2009年以来、Apache OpenEJBとTomEEを本番環境で使い続け、貢献を繰り返す。1982年にメモリ1 KのSinclair ZX81を手にして以来、コンパクトなコードにこだわりを持っている。Twitterのフォローは@AndyGeeDeから。   Jason Porter Red Hat開発者プログラム・チーム、Arquillian、Quarkus、その他のRed Hat内開発者エクスペリエンス・プロジェクトに携わるソフトウェア・エンジニア。専門はWildfly、Quarkus、CDI、JSF、Java EE、solr、Gradleなど。PHP、Ruby、Groovy、SASSや、その他の各種Web言語(HTML、CSS、JS)の経験を持つ。現在は、Red Hatのシニア・ソフトウェア・エンジニアとして主にdevelopers.redhat.com Webサイトに携わっており、開発者エクスペリエンスとその全方位的な改善に大きな関心を持つ。Twitterのフォローは@lightguardjpから。        

※本記事は、Alex Soto BuenoとAndy GumbrechtとJason Porterによる"How to Test Java Microservices with Pact"を翻訳したものです。 マイクロサービス・アプリケーションがもたらす独自のテスト課題 著者:Alex Soto Bueno、Andy Gumbrecht、Jason Porter 2020年4月6日 [編集注:本記事は、Al...

JUnit 4からJUnit 5に移行する:重要な違いと利点

※本記事は、Brian McGlauflinによる"Migrating from JUnit 4 to JUnit 5: Important Differences and Benefits"を翻訳したものです。 改善点と新機能が魅力のJUnit 5 著者:Brian McGlauflin 2020年4月6日   JUnit 5は、JUnitフレームワークの強力かつ柔軟なアップデートであり、テスト・ケースの整理および記述を行うためや、テスト結果の理解に役立てるためのさまざまな改善と新機能が提供されています。JUnit 5へのアップデートは、すばやく簡単です。プロジェクトの依存性を更新し、新機能を使い始めるだけです。 しばらくJUnit 4を使っていた方は、テストの移行を大変な作業だと感じるかもしれません。朗報なのは、おそらくテストの変換がまったく必要ないという点です。JUnit 5ではVintageライブラリを使ってJUnit 4テストを実行できます。 とは言うものの、新しいテストをJUnit 5で書き始めるべき4つの確かな理由を次に示します。 JUnit 5では、ラムダ関数などのJava 8以降の機能を活用することで、テストが強力になるとともに、メンテナンスしやすくなっています。 JUnit 5には、テストの記述、整理、実行にとても便利ないくつかの新機能が追加されています。たとえば、テストの表示名がわかりやすくなり、テストを階層的に整理できるようになっています。 JUnit 5は複数のライブラリで構成されているため、必要な機能だけがプロジェクトにインポートされます。MavenやGradleなどのビルド・システムを使えば、適切なライブラリを含めるのは簡単です。 JUnit 5では、複数の拡張機能を同時に使用できます。これはJUnit 4では不可能でした(一度に使えるランナーは1つだけでした)。つまり、Spring拡張機能と他の拡張機能(独自のカスタム拡張機能など)を容易に組み合わせることができます。 JUnit 4からJUnit 5への切り替えはとても簡単です。この点は、既存のJUnit 4テストがある場合でも変わりません。ほとんどの組織では、新機能が必要な場合を除き、古いJUnitテストをJUnit 5に変換する必要はありません。新機能が必要な場合は、以下の手順に従います。 ライブラリとビルド・システムをJUnit 4からJUnit 5にアップデートします。既存のテストを実行できるように、テスト・ランタイム・パスにjunit-vintage-engineアーティファクトを含めます。 新しいJUnit 5コンストラクトを使って、新しいテストの構築を始めます。 (省略可能)JUnitテストをJUnit 5に変換します。   重要な違い JUnit 5テストは、ほとんどJUnit 4テストと同じように見えますが、認識しておくべき違いがいくつかあります。 インポート:JUnit 5では、アノテーションとクラスに新しいorg.junit.jupiterパッケージが使われています。たとえば、org.junit.Testはorg.junit.jupiter.api.Testになっています。 アノテーション:@Testアノテーションにはパラメータがなくなり、各パラメータは関数に移行しています。たとえば、テストで例外のスローが想定される場合、JUnit 4では次のように記述します。 @Test(expected = Exception.class) public void testThrowsException() throws Exception { // ... } JUnit 5では、次のように変更されています。 @Test void testThrowsException() throws Exception { Assertions.assertThrows(Exception.class, () -> { //... }); } タイムアウトも同様に変更されています。次に示すのは、JUnit 4での例です。 @Test(timeout = 10) public void testFailWithTimeout() throws InterruptedException { Thread.sleep(100); } JUnit 5では、次のように変更されています。 @Test void testFailWithTimeout() throws InterruptedException { Assertions.assertTimeout(Duration.ofMillis(10), () -> Thread.sleep(100)); } 変更されたその他のアノテーションは次のとおりです。 @Beforeは@BeforeEachとなっています。 @Afterは@AfterEachとなっています。 @BeforeClassは@BeforeAllとなっています。 @AfterClassは@AfterAllとなっています。 @Ignoreは@Disabledとなっています。 @Categoryは@Tagとなっています。 @Ruleと@ClassRuleはなくなっています。代わりに@ExtendWithと@RegisterExtensionを使用します。 アサーション:JUnit 5のアサーションは、org.junit.jupiter.api.Assertionsに含まれるようになっています。assertEquals()やassertNotNull()など、ほとんどの一般的なアサーションは以前と同じように見えますが、いくつかの違いがあります。 エラー・メッセージが最後の引数になっています。たとえば、assertEquals("my message", 1, 2)はassertEquals(1, 2, "my message")となっています。 ほとんどのアサーションで、エラー・メッセージを構築するラムダを受け取るようになっています。このラムダが呼ばれるのは、アサーションに失敗した場合のみです。 assertTimeout()とassertTimeoutPreemptively()は、@Timeoutアノテーションに代わるものです(JUnit 5にも@Timeoutアノテーションは存在しますが、JUnit 4とは動作が異なります)。 後述しますが、いくつかの新しいアサーションが追加されています。 なお、お好みであればJUnit 5テストでJUnit 4のアサーションを継続して使用できることに注意してください。 前提条件:前提条件はorg.junit.jupiter.api.Assumptionsに移動しました。  同じ前提条件が存在していますが、BooleanSupplierや、条件との照合を行うHamcrest Matcherもサポートされるようになっています。条件を満たした場合のコード実行に、ラムダ(Executable型)を使用できます。 次に示すのは、JUnit 4での例です。 @Test public void testNothingInParticular() throws Exception { Assume.assumeThat("foo", is("bar")); assertEquals(...); } JUnit 5では、次のように書きます。 @Test void testNothingInParticular() throws Exception { Assumptions.assumingThat("foo".equals(" bar"), () -> { assertEquals(...); }); }   JUnitの拡張 JUnit 4でのフレームワークのカスタマイズは、一般的に、@RunWithアノテーションを使ってカスタムのランナーを指定することを意味していました。複数のランナーを使った場合は問題が生じやすかったため、通常は、連鎖させることや@Ruleを使用することが必要でした。JUnit 5では、拡張機能を使ってこの点が改善され、シンプルになっています。 たとえば、Springフレームワークを使ってテストを構築する場合、JUnit 4では次のようにしていました。 @RunWith(SpringJUnit4ClassRunner.class) public class MyControllerTest { // ... } JUnit 5では、次のようにしてSpring拡張機能をインクルードします。 @ExtendWith(SpringExtension.class) class MyControllerTest { // ... } @ExtendWithアノテーションは繰り返し可能であるため、複数の拡張機能を容易に組み合わせることができます。 org.junit.jupiter.api.extensionで定義されたインタフェースを1つまたは複数実装したクラスを作成し、@ExtendWithを使ってそのクラスをテストに追加して、独自のカスタム拡張機能を容易に定義することもできます。   テストをJUnit 5に変換する 既存のJUnit 4テストをJUnit 5に変換するためには、以下の手順を使用します。この手順は、ほとんどのテストにおいて有効であるはずです。 インポートを更新し、JUnit 4を削除してJUnit 5を追加します。たとえば、@Testアノテーションのパッケージ名を更新し、アサーションのパッケージ名とクラス名を両方とも更新します(AssertsをAssertionsに)。この段階でコンパイル・エラーが発生しても心配しないでください。以下の手順を完了することで、エラーは解決するはずです。 テスト全体で、古いアノテーションとクラス名を新しいものに置き換えます。たとえば、すべての@Beforeを@BeforeEachに、すべてのAssertsをAssertionsに置き換えます。 アサーションを更新します。メッセージを提供するアサーションはすべて、メッセージ引数を最後に移動する必要があります(引数が3つとも文字列の場合は、特に注意してください)。さらに、タイムアウトと、想定される例外も更新します(前述の例をご覧ください)。 前提条件を使用している場合は、それを更新します。 @RunWith、@Rule、@ClassRuleの各インスタンスを適切な@ExtendWithアノテーションで置き換えます。その際のサンプルとして、使用している拡張機能に関する更新版ドキュメントをオンラインで探すとよいでしょう。 なお、パラメータ化テストを移行する場合は、もう少しリファクタリングが必要になることに注意してください。Unit 4のParameterizedを使っている場合は特にそうです(JUnit 5のパラメータ化テストの形式は、JUnitParamsにかなり近いものになっています)。   新機能 ここまで説明してきたのは、既存の機能と、それがどう変わったかについてだけでした。しかし、JUnit 5には、テストの表現力やメンテナンス性を向上させる新機能が多数搭載されています。 表示名:JUnit 5では、クラスおよびメソッドに@DisplayNameアノテーションを付加できます。この名前は、レポートの生成時に使用されます。そのため、テストの目的の記述や、失敗の追跡を行いやすくなります。次に例を示します。 @DisplayName("Test MyClass") class MyClassTest { @Test @DisplayName("Verify MyClass.myMethod returns true") void testMyMethod() throws Exception { // ... } } また、表示名ジェネレータを使ってテスト・クラスやテスト・メソッドを処理し、任意の形式でテスト名を生成することもできます。詳細および例については、JUnitのドキュメントをご覧ください。 アサーション:JUnit 5では、以下のようないくつかの新しいアサーションが導入されています。 assertIterableEquals()では、equals()を使って2つのIterableのディープ検証を行います。 assertLinesMatch()では、2つの文字列リストが一致することを検証します。expected引数は正規表現を受け取ります。 assertAll()では、複数のアサーションをグループ化します。個々のアサーションが失敗しても、すべてのアサーションが実行されるという利点がもたらされます。 assertThrows()とassertDoesNotThrow()は、@Testアノテーションのexpectedプロパティに代わるものです。 テストのネスト:JUnit 4のテスト・スイートは便利でしたが、JUnit 5の「テストのネスト」の方がセットアップやメンテナンスが簡単です。同時に、テスト・グループ間のリレーションシップの表現力が向上しています。次に例を示します。 @DisplayName("Verify MyClass") class MyClassTest { MyClass underTest; @Test @DisplayName("can be instantiated") public void testConstructor() throws Exception { new MyClass(); } @Nested @DisplayName("with initialization") class WithInitialization { @BeforeEach void setup() { underTest = new MyClass(); underTest.init("foo"); } @Test @DisplayName("myMethod returns true") void testMyMethod() { assertTrue(underTest.myMethod()); } } } 上記の例では、MyClassに関連するすべてのテストを1つのクラスで行っていることがわかります。このクラスのインスタンスを作成できることは、外側のテスト・クラスで検証できます。ネストされたインナー・クラスですべてのテストを行っており、そこでMyClassのインスタンスを作成し、初期化しています。@BeforeEachメソッドは、ネスト・クラスのテストにのみ適用されます。 テストとクラスの@DisplayNamesアノテーションは、テストの目的と構成の両方を表しています。これにより、テストが行われる条件(Verify MyClass with initialization(初期化時にMyClassを検証する))と、テストで検証する内容(myMethod returns true(myMethodがtrueを返す))を確認できるため、テスト・レポートがわかりやすくなります。これがJUnit 5の優れたテスト設計パターンです。 パラメータ化テスト:テストのパラメータ化はJUnit 4にも存在しており、JUnit4Parameterizedなどの組込みライブラリや、JUnitParamsなどのサードパーティ製ライブラリがありました。JUnit 5では、パラメータ化テストが完全に組み込まれる形になり、JUnit4ParameterizedおよびJUnitParamsの優れた機能の一部が採用されています。次に例を示します。 @ParameterizedTest @ValueSource(strings = {"foo", "bar"}) @NullAndEmptySource void myParameterizedTest(String arg) { underTest.performAction(arg); } 形式はJUnitParamsに似ており、パラメータが直接テスト・メソッドに渡されます。なお、テストに使う値を複数のソースから取得できる点に注意してください。ここでは、パラメータが1つしかないため、@ValueSourceを使うのは簡単です。@EmptySourceと@NullSourceは、実行に使う値のリストにそれぞれ空の文字列とNULLを追加することを示します(両方を使う場合は、上記のように組み合わせて使うこともできます)。@EnumSourceや@ArgumentsSource(カスタムの値プロバイダ)など、値のソースは他にも複数あります。複数のパラメータが必要な場合は、@MethodSourceや@CsvSourceを使うこともできます。 JUnit 5には、@RepeatedTestというテスト・タイプも追加されました。このテスト・タイプは、1つのテストを指定された回数だけ繰り返すものです。 条件付きテスト実行:JUnit 5では、テストまたはコンテナ(テスト・クラス)を条件によって有効化または無効化するExecutionCondition拡張機能APIが提供されています。これはテストで@Disabledを使うことに似ていますが、カスタムの条件を定義できます。以下のような複数の組込み条件があります。 @EnabledOnOs/@DisabledOnOs:指定されたオペレーティング・システムでのみ、テストを有効化または無効化する @EnabledOnJre/@DisabledOnJre:特定のバージョンのJavaでテストを有効化または無効化することを指定する @EnabledIfSystemProperty:JVMシステム・プロパティの値に基づいてテストを有効化する @EnabledIf:スクリプトのロジックで記述された条件が満たされた場合にテストを有効化する テスト・テンプレート:テスト・テンプレートは、通常のテストとは異なり、実行する一連の手順を定義するものです。このテンプレートは、特定の起動コンテキストを使って他の場所から実行することができます。つまり、テスト・テンプレートをいったん定義してから、そのテストを行うために使用する、実行時の起動コンテキストのリストを作成することができます。詳細および例については、ドキュメントをご覧ください。 動的テスト:動的テストは、テスト・テンプレートに似ており、行うテストがその実行時に生成されます。ただし、テスト・テンプレートは特定の一連の手順を複数回実行するように定義されるのに対し、動的テストでは同じ起動コンテキストを使用して別のロジックを実行することができます。動的テストの使用方法の1つとして、抽象オブジェクトのリストをストリーミングし、それぞれの具象型に基づいて別々のアサーション・セットを実行することが考えられます。ドキュメントには、見本となる例が記載されています。   まとめ JUnit 5の新機能を使いたい場合を除けば、古いJUnit 4テストをJUnit 5に変換する必要はおそらくないでしょう。しかし、JUnit 5に切り替えるべき大きな理由があります。その1つとして、JUnit 5のテストは強力になるとともに、メンテナンスしやすくなっていることが挙げられます。さらに、JUnit 5には便利な機能が数多く搭載されています。使う機能だけがインポートされることに加え、複数の拡張機能を使うことや、独自のカスタム拡張機能を作成することもできます。こういった変更点や新機能が組み合わされて、強力で柔軟な、JUnitフレームワークのアップデートが実現しています。 Brian McGlauflin Parasoftのソフトウェア・エンジニア。SpringやAndroid、APIテスト、サービス仮想化を活用したフル・スタック開発を経験。現在は、Parasoft Jtestを使った、Javaアプリケーションの自動ソフトウェア・テストに重点的に取り組んでいる。

※本記事は、Brian McGlauflinによる"Migrating from JUnit 4 to JUnit 5: Important Differences and Benefits"を翻訳したものです。 改善点と新機能が魅力のJUnit 5 著者:Brian McGlauflin 2020年4月6日   JUnit 5は、JUnitフレームワークの強力かつ柔軟なアップデートであり、テスト・ケースの整...

Cloud Security

クラウドセキュリティ責任共有モデルを明確にするための5つのステップ

※本記事は、Farah Mithaniによる"5 Steps Toward Clarity Around the Cloud Security Shared Responsibility Model"を翻訳したものです。 現代の脅威 2020年7月8日 企業のクラウドへの移行傾向は留まることを知らず、契約するクラウド・プロバイダの数も急速に増加しています。しかしそうした熱狂のなか、見落とされているものがひとつあります。 それがクラウド・セキュリティの共有責任モデルです。それどころか、オラクルとKPMGによる2020年度の脅威レポートであるOracle and KPMG Cloud Threat Report 2020によると、すべてのクラウド・サービスに対する共有責任モデルを完全に理解しているという人の割合が、2019年の18%から8%にまで減少していました。このような理解不足による混乱は、サイバー犯罪者にセキュリティ対策の穴を見つけられる要因にもなっています。つまりここがしっかりしていれば、クラウドのデータを保護できたかもしれません。 では、見落とされているものとは、具体的にどういうものなのでしょうか。たとえば、サブスクライバ・データを保護することはクラウド・プロバイダだけの責任ではないので、どの部分はクラウド・サービス・プロバイダ(CSP)が責任を持つべきで、担当者や企業、サブスクライバが責任を持つべきなのはどの部分なのかを把握しておく、という考え方も見落とされているもののひとつになります。責任がはっきりしないと、それはリスクにつながります。またセキュリティ侵害の件数が拡大していることからも、責任の所在をより明確にすることが極めて重要だということがわかります。 クラウド・セキュリティの共有責任モデルに、包括的なマニュアルはありません。これはクラウド・プロバイダやクラウド・サービスのタイプによって異なります。しかし、品質保証契約(SLA)について、そして個々のクラウド・サービス・プロバイダが責任を有するのはどの部分なのかについて、十分に議論をし、そのうえで契約書に署名をして、新しいクラウド・サービスをデプロイすること。それは誰もが実施できることです。 たとえば、ある担当者は、自身の仕事は自社のSaaSプラットフォームのデータ・セキュリティとアイデンティティ管理のみであり、それ以外の管理はクラウド・プロバイダの仕事だとおおまかに考えていたとしましょう。しかしクラウド・プロバイダの方は、そうした責任(およびその他も)は共有のものだと考えているかもしれません。 こうした混乱が具体的な問題に発展しても不思議ではありません。Oracle and KPMG Cloud Threat Report 2020では、回答者の約3分の2が、複数のベンダーが関与する自社のSaaSアプリケーションの共有責任モデルに大きな混乱が見られると答えていました。こうした理解不足があると、アクセスや構成におけるセキュリティ侵害の種が見落とされてしまうかもしれません。自社のクラウド・データはデフォルトで保護されていると無条件に仮定している企業も少なくありませんが、その仮定が常に正しいとは限りません。共有責任を適切に理解していないと、企業の財務情報や知的財産情報、顧客の個人情報を危険にさらすことにもなりかねないのです。 では、クラウド・プロバイダとの協働において、誰がどの部分に責任を有しているのかを適切に理解するには、どうすればよいのでしょうか。   1.クラウド・プロバイダとのコミュニケーションを絶やさない クラウド・サービス・プロバイダは、クラウド・セキュリティ・プロバイダではありません。彼らのことは“クラウド・セキュリティ・パートナー”とみなすべきです。企業にも、クラウド・プロバイダにも、セキュリティ侵害を回避するという共通の目的があるからです。したがって企業は、主担当者がクラウド・プロバイダと常態的に協働していくことを義務付け、また企業自身も四半期に一度はクラウド・プロバイダとミーティングを持つようにする必要があります。これにより、クラウド・プロバイダがSLAに従っているかどうか、また契約や共有責任に変更が必要かどうかを把握しやすくなります。   2.社内に“セキュリティ・ファースト”の文化を醸成する 結局のところ、セキュリティで重要なのは従業員一人ひとりの行動です。そしてリモート・ワークが増えるなか、その重要性を感じる場面も増えています。だからこそ、脆弱性を回避するには、セキュリティ・ファーストの文化が不可欠なのです。また現在は、クラウド・ベンダーやクラウド・プラットフォーム、クラウド・アプリケーションの数もますます増えています。そのためIT部門が自社のセキュリティのすべてを監督しているような従来型モデルでは、いずれIT部門が限界を迎え、共有責任に隙間が生じてしまいます。 この問題に対処する主な方法は2つで、それはクラウド・セキュリティ・アーキテクト(CSA)の採用とビジネス情報セキュリティ・オフィサー(BISO)の採用です。CSAは、企業によるクラウド・セキュリティ戦略の構築を後押しすること、およびすべての事業部門がセキュリティ・ガイドラインを遵守するようにすることをその役割としています。BISOは、最高情報セキュリティ責任者と各事業部門との橋渡し役として、セキュリティ・ルールに関する事業部門とのコミュニケーション不足を補います。またBISOは、各事業部門の目的や目標を考慮しつつ、セキュリティ・ファーストを遂行していく役割も担います。   3.クラウド・セキュリティの共有責任モデルのエキスパートになる 企業は、共有責任モデルのエキスパートを採用し、その定着を図るなど、人材投資を実施する必要があります。またクラウド・セキュリティの共有責任モデルに関する専門知識を必要とするのはIT部門だけではないことも、理解する必要があります。誰もが自社のデータを保護する責任を負っていることを社内の一人ひとりが認識すべきなのです。そのためには、自社と顧客のデータの安全性およびセキュリティをそのアクセス方法を問わずどのように管理していくかについて、社内の全員に定期的なトレーニングを実施していく必要があるでしょう。   4.セキュリティを自動化する 自動化は非常に重要な要素です。これにより、反復的なプロセスや手作業を減らし、ITチームが他のタスクに注力できるようになるからです。また自動化を採用すれば、クラウド・セキュリティの共有責任モデルのエキスパートになれる従業員も増えることになります。自動化の例としては、自動化および機械学習を活用して不正アクセスが可能なエリアを特定し、その情報に基づいて是正措置を実施し、同様の構成を持つ他のプラットフォームやサービスにも不正アクセスの機会がないかを確認する、といったことがあります。さらに自動化では人的ミスのリスクを減らせるため、企業のセキュリティ上の隙間を防ぐことができます。   5.ビジネス詐欺にまつわるリスクを十分に理解する Oracle and KPMG Cloud Threat Report 2020によると、回答者の39%に、過去2年以内にビジネス・メールによる不正アクセスで被害を受けた経験がありました。ビジネス・メールによる不正アクセスは攻撃手段として現在非常によく用いられています。またここから他の攻撃へと派生することもあります。具体的にいえば、すべての攻撃の91%が電子メールを起点としていました(昨年のレポートで紹介)。ビジネスクリティカルなアプリケーションが電子メールとつながっている場合、こうした攻撃は極めてリスクの高いものになります。したがって外部との接点にどのようなリスクがあるのか、そしてそれを回避するにはどのような施策をとるべきなのかを理解していくことが非常に重要になります。その点、Oracle and KPMG Cloud Threat Report 2020は、セキュリティ上の最新のトレンドを理解する非常に有益なソースになります。 共有責任は、クラウド・セキュリティのなかでも誤解している人の多い分野です。しかし社内全体でクラウド・セキュリティの共有責任モデルをしっかりと理解していくことができれば、セキュリティ上の不備や脆弱性の回避にも効果的でしょう。 組織は、幹部からエンドユーザーまで組織全体にセキュリティ・ファーストの意識が浸透するよう、継続的に努力していく必要があります。このような文化を醸成し、このモデルに関する知識を増やしていけば、次に来るかもしれない大規模なセキュリティ侵害から組織を保護することができるでしょう。クラウド・セキュリティの共有責任モデルに関する詳細は、Oracle and KPMG Cloud Threat Report 2020 series:Demystifying the Cloud Shared Responsibility Model(クラウドの共有責任モデルの解説)の2回目のレポートをご覧ください。        

※本記事は、Farah Mithaniによる"5 Steps Toward Clarity Around the Cloud Security Shared Responsibility Model"を翻訳したものです。 現代の脅威 2020年7月8日 企業のクラウドへの移行傾向は留まることを知らず、契約するクラウド・プロバイダの数も急速に増加しています。しかしそうした熱狂のなか、見落とされているものが...

.NET

OCI SDK for .NET がリリースされました

※本記事は、Viral Modiによる"OCI SDK for .NET is now available for your .NET Projects"を翻訳したものです。 2020年7月23日 このブログは、Oracle Cloudの.NET向けソフトウェア開発キット(SDK)をご紹介いたします。Oracle Cloud SDK for .NETは、Oracle Cloudにて利用できる一連の開発者向けツールの1つとしてリリースされました。ちなみにOracle Cloud Infrastructureでは現在、Java、Python、GO、Ruby、TypeScript、.NET/C#.向けのSDKをサポートしています。今回は、Azureの.NET開発者向けにOracle Cloudで利用可能なプラットフォームを提供するということで、これによりOracle CloudとMicrosoft Azureのパートナーシップがさらに強化されることとなります。 前提条件 Oracle Cloud Infrastructureのアカウントの作成および設定 SDKに必要なすべての基本構成情報 私は仕事で複数の開発者SDKとコマンドライン・インターフェース(CLI)を使用しているため、アプリケーション・コードのランタイムの構成情報ではなく、自身の設定用構成ファイルを使用することにします。構成ファイルがあることで、共通構成を使用することも可能になります。私の構成ファイルは、以下のコードでもわかるとおり、デフォルトの保存場所である~/.oci/configに置かれています。   [DEFAULT]   user=ocid1.user.oc1..   fingerprint=f7:12:34:56:78:90:ab:bc:cd:de:ef:98:87:65:43:21   key_file=/Users/viralmodi/.oci/my_oci_api_key.pem   tenancy=ocid1.tenancy.oc1..   region=us-phoenix-1 view raw config hosted with ❤ by GitHub .NETアプリケーション内でSDKを使用するには この.NET向けSDKは、.NETコンソール・アプリケーション、デスクトップアプリケーション、または.NETクラスのライブラリにて使用できます。.NETに対応した統合開発環境(IDE)であれば、大半の場合、これらのタイプのプロジェクトを作成することができます。今回は、コンソール・アプリケーションのプロジェクトを作成することにします。 プロジェクトが作成できたら、IDEにて提供されているNuGetパッケージ・マネージャ・ウィンドウにて、Oracle Cloudの.NET SDKパッケージをインストールします。このウィンドウにて"OCI.DotNetSDK"と入力してOracle Cloudの.NETパッケージを検索します。またはNuGetパッケージ・マネージャにて.NET SDKを参照すると、すべてのOracle Cloud .NETパッケージが表示されます。 一般的なIDEにおいてNuGetパッケージをインストールする方法については、以下のリソースを参照してください。 Visual Studio for Mac Visual Studio for Windows JetBrains Rider Visual Studio Code IDEを使用していない場合は、シェル内のプロジェクトのルート・ディレクトリにて次の.NET CLIツールコマンドを使用することで、Oracle Cloud Infrastructureのアイデンティティ・サービス向けの.NETパッケージをインストールできます。 dotnet add package OCI.DotNetSDK.Identity -v 1.0.0 これにより、パッケージに含まれているすべての依存関係が抽出されます。この例では、Oracle Cloud SDK for .NETから提供されている.NETインプリメンテーションを使用して、アイデンティティ・サービスのコンパートメントをリストするためのREST APIを呼び出します。 以下のスクリーンショットでもわかるとおり、インストールにはRider IDEを使用しています。   これでOracle Cloud SDK for .NETを使用するために必要なインストールと構成が完了しました。ここからはアプリケーションのためのコードの記述に移ります。大半のIDEでは、ソース・コードを記述していけば自動入力されるので、非常に便利です。この例では、"using"ディレクティブにて、アプリケーションに以下のタイプを使用しています。   using System;   using System.Threading.Tasks;   using Oci.Common.Auth;   using Oci.IdentityService;   using Oci.IdentityService.Requests;   using Oci.IdentityService.Responses; view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub 次に、以下のようにコンパートメントOCIDを初期設定します。   private static String compartmentId = "ocid1.tenancy.oc1.."; view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub なおOCIDの値は、セキュリティ上の理由により一部が非表示になっています。 構成時にサービス・クライアントに渡される認証プロバイダを作成します。認証プロバイダでは、その名のとおり、Oracle Cloud Infrastructureの各サービスによるリクエストを認証します。今回の例では、アイデンティティ・クライアントの裏付けに際して認証プロバイダを渡します。構成ファイルはすでに前提条件の一環として作成済みであるため、Oci.Common.AuthモジュールのConfigFileAuthenticationDetailsProviderを使用します。ここでは構成ファイルのデフォルトのプロファイルを使用していますが、必要に応じて好きなプロファイルを使用することができます。   var authProvider = new ConfigFileAuthenticationDetailsProvider("DEFAULT"); view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub なお、ランタイム時に構成を渡す場合は、SimpleAuthenticationDetailsProviderを使用します。 次に、authProviderを渡すIdentityClientのインスタンスを作成します。   var client = new IdentityClient(authProvider); view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub リクエストに必要な必須パラメータにてListCompartmentRequest を構成します。今回は、コンパートメントOCIDを渡します。   var listCompartmentsRequest = new ListCompartmentsRequest   {   CompartmentId = compartmentId   }; view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub この例では、.NET SDKのページネーション機能を使用します。これにより、コンパートメントのリストのような大量のアイテムをシンプルな方法で取得することができます。.NET SDKでは以下のページネーション・メカニズムに対応しています。 リスト呼出しを作り、次のページ・トークンを手動で処理することで、すべてのレコードを取得する。 各サービス・クライアントにより公開されているページネーションを使用して、サービス呼出しへのレスポンス(Oracle Cloud SDK Responseオブジェクト)を繰返し処理する。 各サービス・クライアントにより公開されているページネーションを使用して、サービス呼出しから返されたモデルとリソースを繰返し処理する。 この例では、ページネーション・メソッドにて、テナンシのルート・コンパートメントからコンパートメント・レコードを取得します。テナンシのOCIDは、それ自体がOracle Cloudのすべてのアカウントのルート・コンパートメントOCIDになります。 アイデンティティ・クライアントのページネータとクラス内のRecordEnumeratorリスト呼出しを使用します。この呼出しでは、IEnumerable<コンパートメント>タイプの列挙子が返されるので、リクエストにて渡されるコンパートメントOCIDのすべてのサブコンパートメントにこれを繰り返し使用することができます。   try   {   // Using Identity client paginator and its record enumerator to iterate through all compartments   var compartments = client.Paginators.ListCompartmentsRecordEnumerator(listCompartmentsRequest);   foreach (var compartment in compartments)   {   Console.WriteLine("{0, 30} | {1, 60} | {2, 60}",   compartment.Name,   compartment.Description,   compartment.Id);   }   }   catch (Exception e)   {   Console.WriteLine("Failed to call List Compartments " + e.Message);   } view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub ここでコンソール・アプリケーションを保存し、構築して実行すると、以下のインプットが表示されます(セキュリティ上の理由で、コンパート名は変更され、OCIDは一部が非表示になっています)。   source_dotnetsdk1.sCdm2sWV | source compartment | ocid1.compartment.oc1..   source_dotnetsdk2.MTCBvYR8 | source compartment | ocid1.compartment.oc1..   source_dotnetsdk3.JLfWRqsX | source compartment | ocid1.compartment.oc1..   source_dotnetsdk4.c4Dh9Bht | source compartment | ocid1.compartment.oc1..   target_dotnetsdk1.HDbFwat0 | target compartment | ocid1.compartment.oc1..   target_dotnetsdk2.90WbFnEJ | target compartment | ocid1.compartment.oc1..   target_dotnetsdk3.01JaKkYG | target compartment | ocid1.compartment.oc1..   target_dotnetsdk4.WaYvdMUj | target compartment | ocid1.compartment.oc1..   target_dotnetsdk5.tdmO3sbW | target compartment | ocid1.compartment.oc1..   TeamSandbox | Sandbox for team members to do manual testing, etc. | ocid1.compartment.oc1.. view raw consoleAppOutput hosted with ❤ by GitHub IDEを使用している場合は、そのコードで使用されるメソッドの自動完了が可能で、また"var"を使用している場合など、各Oracle Cloud APIの呼出しに対する戻りタイプの情報も提供されます。IDEのデコンパイラでもコードを調べることができ、 オラクルのGitHubにアクセスして、ソース・コードを確認することができます。またOracle Cloud SDK for .NETのAPIリファレンスは、すべての.NET開発者にとって総合的なリファレンスとなっています。 例:リージョンを非同期リストするためのAPIの呼出し この例では、ListRegionsを呼び出して、Oracle Cloud Infrastructureのすべてのリージョンをリストします。しかしリージョンの数は少なく、固定されているため、このREST APIではページネーションはサポートされていません。そこでこのAPIの使用に際しては、C#の“async and await”コンセプトを用いることにします。大半の.NET SDKの呼出しは非同期であり、System.Threading.Tasks.Taskを使用して追跡することができます。これは、.NET/C#で提供される非同期プログラミングモデルの基礎を形成するものです。   private static async Task ListOciRegions()   {   // Accepts profile name and creates a auth provider based on config file   var authProvider = new ConfigFileAuthenticationDetailsProvider("DEFAULT");   // Create a client for the service to enable using its APIs   var client = new IdentityClient(authProvider);   // List regions   var listRegionsRequest = new ListRegionsRequest();   try   {   Task <ListRegionsResponse> regRespTask = client.ListRegions(listRegionsRequest);   var listRegionsResponse = await regRespTask;   foreach (Oci.IdentityService.Models.Region reg in listRegionsResponse.Items)   {   Console.WriteLine(reg.Key + " : " + reg.Name);   }   }   catch (Exception e)   {   Console.WriteLine("Failed to call List Regions "+e.Message);   }   } view raw testOCIDotNetSdkConsoleApp.cs hosted with ❤ by GitHub このAPIによる結果は以下のとおりです。   AMS : eu-amsterdam-1   BOM : ap-mumbai-1   FRA : eu-frankfurt-1   GRU : sa-saopaulo-1   HYD : ap-hyderabad-1   IAD : us-ashburn-1   ICN : ap-seoul-1   JED : me-jeddah-1   KIX : ap-osaka-1   LHR : uk-london-1   MEL : ap-melbourne-1   NRT : ap-tokyo-1   PHX : us-phoenix-1   SYD : ap-sydney-1   YNY : ap-chuncheon-1   YUL : ca-montreal-1   YYZ : ca-toronto-1   ZRH : eu-zurich-1 view raw consoleAppOutput hosted with ❤ by GitHub   まとめ このブログでは、最近リリースされたOracle Cloud SDK for .NETを紹介いたしました。具体的には、リクエストや認証プロバイダの作成方法、Oracle Cloudのアイデンティティ・サービスの使い方、クライアントを使用したサービスAPIの呼出し方を説明いたしました。このブログをご覧の方が.NET開発者で、現在Oracle Cloud Infrastructureを使用されている場合は、ぜひこの.NET SDKを試してみてください。またご質問やご意見がありましたら、コメントをお送りただくか、ドキュメントをご参照ください。 また以下のリソースも.NET SDKの理解に役立ちます。 Oracle Cloud SDK for .NETの公式ドキュメント .NET SDKのリファレンス GitHub oci-dotnet-sdkレポジトリ(オープン・ソースで、外部からの協力が可能) .NET SDKを使用してOracle Cloudのサービスおよび機能を活用するための例 NuGetパッケージ・マネージャの各種.NET SDK Oracle Cloud Infrastructureの開発者ツールを使用するための前提条件        

※本記事は、Viral Modiによる"OCI SDK for .NET is now available for your .NET Projects"を翻訳したものです。 2020年7月23日 このブログは、Oracle Cloudの.NET向けソフトウェア開発キット(SDK)をご紹介いたします。Oracle Cloud SDK for .NETは、Oracle Cloudにて利用できる一連の開発者...

Developers

サーバーレスOracle Functionsを呼び出すための完全ガイド

※本記事は、Todd Sharpによる"The Complete Guide To Invoking Serverless Oracle Functions"を翻訳したものです。 July 23, 2020 Oracle Functionsについては、数多くの情報をこのブログにて共有し、ファンクションの呼出しに関する例もさまざま紹介してきました。しかし、Oracle Cloudでサーバーレスのファンクションを呼び出すさまざまな方法も含め、すべてを包括した総合的なガイドを作成したことはありませんでした。そこで今回は、サーバーレスのファンクションを呼び出すための(このブログを掲載する時点で可能な)すべての方法を紹介していきたいと思います。 以下の目次には、このブログの各セクションに移動できる便利なリンクを付けていますので、どうぞご活用ください。 ファンクション・メタデータの取得 Fn CLIを使用 コンソール・ダッシュボードを使用 OCI CLIを使用 ファンクションの呼出し テストまたはデバック目的の呼出し Fn CLIを使用 OCI CLIを使用 OCI-CURLを使用 トリガーやイベントに応じた自動的な呼出し  Oracle Notification Serviceを使用 クラウドのイベントを使用 Oracle Integration Cloud(OIC)を使用 HTTPリクエストおよびSDKを使った手動による呼出し HTTPリクエスト(API Gateway経由)を使用 Java SDKを使用 TypeScript/JavaScript SDKを使用 その他のSDKおよびAPIを使用   ファンクション・メタデータの取得 ファンクションを呼び出す前に、まずはそのファンクションに関する情報を取得しておく必要があります。ここでは、ファンクション名とファンクションをデプロイするアプリケーション名についてはすでに把握しているものの、その他の情報についてはまだ把握していないと仮定します。今回は以下の情報を使用します。なおこのブログでは、実際のデータの代わりに使用するプレースホルダを[ ]で示しています。 ファンクション名 [function-name] ファンクション・アプリケーション名 [application-name] ファンクションOCID [function-ocid] ファンクション呼出しエンドポイント [invoke-endpoint] (コレクション・メソッドによっては、これを [invoke-endpoint-base-url] から取得する必要があるかもしれません) では、これらの情報を取得する方法を見ていきましょう。   Fn CLIを使用 個人的には、Fn CLIを使用して以下のコマンドを実行するやり方が、もっとも迅速かつ簡単にこれらの情報を取得できる方法だと思っています。   $ fn inspect function [application-name] [function-name] view raw fn-invoke.sh hosted with ❤ by GitHub 例:   $ fn inspect function hello-world-app hello-world-fn view raw inspect.sh hosted with ❤ by GitHub 結果は、ファンクション・メタデータを含むJSONで表示されます。 上の例では、(1)に[invoke-endpoint]があり、(2)に[function-ocid]があります。   コンソール・ダッシュボードを使用 OCIコンソール・ダッシュボードにて、必要なファンクション・アプリケーションを選択し、ファンクション名をクリックしてファンクション情報を確認します。この情報画面には、[function-ocid] が表示され(1)、[invoke-endpoint]も確認できますが、前の例のfn inspectとは異なり、ここに表示されるのは[invoke-endpoint-base-url]のみとなります。   注:このブログの初回掲載時点では、[invoke-endpoint-base-url]がわかっており、これが常に以下の形式にある場合のみ、[invoke-endpoint]を取得できます [invoke-endpoint-base-url]/20181201/functions/[function-ocid]/actions/invoke   OCI CLIを使用 最後に、OCI CLIを使用して情報を取得する方法を紹介します。   $ oci fn function get --function-id [function-ocid] view raw oci-get.sh hosted with ❤ by GitHub   [function-ocid](1)と[invoke-endpoint-base-url](2)が返されます。[invoke-endpoint]はこの情報から取得する必要があります。   ファンクションの呼出し 必要なデータを取得できたら、呼び出しの作業に移ります。 こちらもご覧ください! Oracle Cloud でのサーバーレス・ファンクションの呼び出し方法を確認されているということは、きっとサーバーレスのOracle Functionsのロギングについても関心をお持ちかと思います。その場合は、どうぞこちらをご覧ください。   テストまたはデバック目的の呼出し まずはテストまたはデバッグ目的の呼出しについて紹介します。つまりシンプルなリクエストにてコマンドラインからシンプルな結果を取得し、ファンクションをテストする場合の方法です。   Fn CLIを使用 このメソッドはすでにご存じかもしれませんが、このブログは完全ガイドですので、これについても簡単に押さえておきます。   $ fn invoke [application-name] [function-name] view raw fn-invoke.sh hosted with ❤ by GitHub データをファンクションの呼出しにパスする必要がある場合は、echoを使用し、データをfn invokeコールにパイプします。   $ echo "{'name': 'todd'}" | fn invoke hello-world-app hello-world-fn view raw fn-invoke.sh hosted with ❤ by GitHub   OCI CLIを使用 OCI CLI にてファンクションを呼び出すことも可能です。ファイルの引数では、ファンクションの結果が入るファイルへのパスが指定されます("-"はSTDOUTへリダイレクト)。また本文の引数にて、入力内容をファンクションへ渡すこともできます。なお、OCI CLIを使用する場合、前のFn CLIの例で使用した[application-name]と[function-name]ではなく、[function-ocid]が必要です。     oci fn function invoke \  --file "-" \  --body '{"name": "todd"}' \  --function-id [function-ocid] view raw oci-invoke.sh hosted with ❤ by GitHub   OCI-CURLを使用 他にも、oci-curlを使用してコマンドラインにてファンクションを呼び出すこともできます。 ご承知おきください: 私はこのメソッドはお勧めしません。Fn CLIの方が(またはOCI CLIでも)呼出しは遥かに簡単なため、この方法を用いる必要がないからです。しかし、このメソッドが機能するものであることは事実であり、またCLIツールをインストールできないがテストのためにファンクションを呼び出す必要があることもあるかもしれないため、お勧めしないながらも私はこの方法を掲載しました。したがってこの方法を採用するか否かは、皆さんの裁量にお任せします。 ディスクには、POSTリクエストにて呼出しエンドポイントに送信した本文を含むテキストファイルが必要です。入力内容を渡す必要がない場合も、ディスクには、本文の入っていない空のファイルが必要です。私がoci-curlを書いたわけではないので、私には質問しないでくださいね?   $ echo '{"name": "todd"}' > /tmp/body.json view raw write-body.sh hosted with ❤ by GitHub では、ファンクションを呼び出します。具体的には、以下のとおりです。   oci-curl \    "[invoke-endpoint-base-url]" \    POST \    /tmp/body.json \    "/20181201/functions/[function-ocid]/actions/invoke" view raw oci-curl.sh hosted with ❤ by GitHub   トリガーやイベントに応じた自動的な呼出し Oracle Functions は Oracle Cloud Infrastructure の一連のサービスと深く統合されています。そのためさまざまな方法にて、イベントや、特定のトリガーに呼応して、サーバーレス・ファンクションを呼び出すことができます。   Oracle Notification Serviceを使用 通知トピックへのサブスクリプションを作成し、これによりファンクションを呼び出すことができます。 ヒント! 通知は非常に便利です。詳しくは、通知サービスの開発者向け完全ガイドをご覧ください。 新しいトピックを作成するか、既存のトピックを入力して、「Create Subscription」をクリックします。プロトコルとして「Functions」を選択し、通知を受け取ったときに呼び出したいファンクションを検索して選択します。 詳しくは、通知とファンクションの統合に関するガイドをご覧ください。   クラウドのイベントを使用 クラウドのイベント・トリガーに呼応してサーバーレス・ファンクションを呼び出すこともできます。Oracle Cloudにてクラウド・イベントを生成するサービスが数多くあることや、メトリクスやアラームに基づいてクラウドのイベントをトリガーできることを考えると、この統合は非常に便利です。Oracle SDKと組み合わせると、クラウドのアクションやリソースを詳細にコントロールすることができます。 クラウドのイベントからファンクションを呼び出すには、次のようにルールを作成します。   詳しくは、クラウドのイベントによるファンクションの呼出しに関するブログをご覧ください。   Oracle Integration Cloud(OIC)を使用 私はOracle Integration Cloudを使用したことがないため、このメソッドについ詳しく説明することはできませんが、Oracle Integration Cloudからサーバーレス・ファンクションを呼び出すことは可能です。詳しくは、OICのドキュメントをご覧ください。   HTTPリクエストおよびSDKを使った手動による呼出し 最後に、アプリケーション・インフラストラクチャの一部としてサーバーレス・ファンクションを実装する際に開発者がもっともよく用いると思われるメソッドの1つである、HTTPまたはSDKを使った手動によるファンクションの呼出しについて説明します。   HTTPリクエスト(API Gateway経由)を使用 OCI API Gatewayサービスを使用すれば、サーバーレス・ファンクションをHTTPにて示すことができます(認証、CORS、およびレート制限の設定は任意)。まずは、ゲートウェイを作成するか、既存のゲートウェイを選択し、「Create Deployment」をクリックします。   「From Scratch」を選択し、名前とパスのプリフィックスを入力します。   必要に応じて、認証、CORS、レート制限、ロギングを設定し、「Next」をクリックします。   「Routes」タブにて、(1)パス(ワイルドカードも可)、(2)HTTPメソッド、(3)Oracle Functions、(4)アプリケーション、(5)ファンクション名を入力します。   次の画面で入力内容を確認し、「Create」をクリックします。変更内容がデプロイされたら、デプロイメントの情報にてそのデプロイメントの呼出しエンドポイントを見つけます。これは、ファンクション呼出しエンドポイントとは異なります。   この呼出しエンドポイントをベースURLとして使用し、上で指定したルートパスを追加すると、ゲートウェイ経由でファンクションを呼び出すことができます。   Java SDKを使用 OCI Java SDKには、サーバーレスのOCIファンクションを呼び出すメソッドが2つあります。1つは従来のブロッキングクライアントを使う方法で、もう1つはコールバックを必要とする非同期クライアントによるノンブロッキングの方法です。 OCI Java SDKでファンクションを呼び出すには、まず以下の依存関係があることを確認する必要があります。ここではGradleを使用していますが、もし別のビルドシステムを使用している場合は、それに合わせて調整してください。   compile('javax.activation:javax.activation-api:1.2.0')   compile('com.oracle.oci.sdk:oci-java-sdk-common:1.19.3')   compile('com.oracle.oci.sdk:oci-java-sdk-functions:1.19.3') view raw build.gradle hosted with ❤ by GitHub 現時点では、Java 11以降でのファンクションの呼出しには問題があるため、以下のようにスクリプトにシステム・プロパティを設定してTLS 1.2を使用するようにすることで、その問題を回避します。   /* when using Java 11+, force TLS 1.2 */   System.setProperty("jdk.tls.client.protocols", "TLSv1.2"); view raw FunctionInvokeSync.java hosted with ❤ by GitHub 次に、[function-ocid]と[invoke-endpoint-base-url] を設定し、認証プロバイダのインスタンスを作成します。   String functionId = " [function-ocid]";   String invokeEndpoint = "[invoke-endpoint-base-url]";   ConfigFileAuthenticationDetailsProvider provider = new ConfigFileAuthenticationDetailsProvider("DEFAULT"); view raw FunctionInvokeSync.java hosted with ❤ by GitHub 次に、呼出しリクエストと一緒に送信するペイロードを作成します。ここでは、JSONにシリアライズするMapを作成します。またこれは呼出しリクエストとともに送信する本文としても使用します。   /* generate the payload */   Map<String, String> payloadMap = new HashMap<>();   payloadMap.put("name", "Sync Client");   ObjectMapper mapper = new ObjectMapper();   String payload = mapper.writeValueAsString(payloadMap); view raw InvokeFunctionSync.java hosted with ❤ by GitHub 次に、FunctionsInvokeClientのインスタンスを作成し、呼出しリクエストを構成し、そのリクエストをクライアントのinvokeFunctionメソッドにパスします。これにより応答を解析したり、クライアントをクローズしたり、STDOUTへの応答を印刷したりすることが可能になります。   /* use sync client */   FunctionsInvokeClient functionsInvokeClient = FunctionsInvokeClient.builder()           .endpoint(invokeEndpoint)           .build(provider);   InvokeFunctionRequest request = InvokeFunctionRequest.builder()           .functionId(functionId)           .invokeFunctionBody(                   StreamUtils.createByteArrayInputStream(payload.getBytes())           )           .build();   InvokeFunctionResponse invokeFunctionResponse = functionsInvokeClient.invokeFunction(request);   String syncResponse = IOUtils.toString(invokeFunctionResponse.getInputStream());   functionsInvokeClient.close();   System.out.println(syncResponse); view raw FunctionInvokeSync.java hosted with ❤ by GitHub コンパイルし実行すると、以下が返されます。 {"message":"Hello Sync Client"} このアプローチを部分的に変更すれば、非同期クライアントを使用してノンブロッキングのアプローチにすることができます。まずは以下のようにペイロードを変更します。   /* use async client */   payloadMap.replace("name", "Async Client");   String asyncPayload = mapper.writeValueAsString(payloadMap); view raw InvokeAsync.java hosted with ❤ by GitHub 次に、非同期クライアントと呼出しリクエストを構成します。   FunctionsInvokeAsyncClient functionsInvokeAsyncClient = FunctionsInvokeAsyncClient.builder()          .endpoint(invokeEndpoint)          .build(provider);   InvokeFunctionRequest asyncRequest = InvokeFunctionRequest.builder()          .functionId(functionId)          .invokeFunctionBody(StreamUtils.createByteArrayInputStream(asyncPayload.getBytes()))          .build(); view raw InvokeAsync.java hosted with ❤ by GitHub 前のアプローチと少し異なる点があります。今回のアプローチでは、invokeFunctionコールにパスするためにAsyncHandlerが必要になります。これはこの呼出しの結果に対して作業する際に使用できます。   AsyncHandler<InvokeFunctionRequest, InvokeFunctionResponse> handler = new AsyncHandler<>() {   @Override   public void onSuccess(InvokeFunctionRequest invokeFunctionRequest, InvokeFunctionResponse invokeFunctionResponse) {   try {   System.out.println(IOUtils.toString(invokeFunctionResponse.getInputStream()));   } catch (IOException e) {   e.printStackTrace();   }   }       @Override   public void onError(InvokeFunctionRequest invokeFunctionRequest, Throwable error) {   error.printStackTrace();   }   }; view raw InvokeAsync.java hosted with ❤ by GitHub 最後に、リクエストとハンドラを非同期クライアントのinvokeFunctionメソッドに渡して、そのクライアントをクローズします。   functionsInvokeAsyncClient.invokeFunction(asyncRequest, handler);   functionsInvokeAsyncClient.close(); view raw InvokeAsync.java hosted with ❤ by GitHub 非同期メソッドを実行すると、以下が生成されます。 {"message":"Hello Async Client"}   TypeScript/JavaScript SDKを使用 OCI TypeScript/JavaScript SDKでも、サーバーレスのOCIのファンクションを呼び出すことができます。この場合、まずはSDKをノードプロジェクトにインストールします。 npm install oci-sdk 次に、必要なモジュールを取得し、authProviderを構成します。   import fn = require("oci-functions");   import common = require("oci-common");   import helper = require("oci-common/lib/helper");   const configurationFilePath = "~/.oci/config";   const configProfile = "DEFAULT";   const authProvider: common.ConfigFileAuthenticationDetailsProvider =          new common.ConfigFileAuthenticationDetailsProvider(           configurationFilePath,           configProfile         ); view raw invoke-fn.ts hosted with ❤ by GitHub [function-ocid]と[invoke-endpoint-base-url]を設定します。   const functionId: string = "[function-ocid]";   const invokeEndpoint: string = "[invoke-endpoint-base-url]"; view raw invoke-fn.ts hosted with ❤ by GitHub クライアントを構成し、エンドポイントを設定します。   const fnClient: fn.FunctionsInvokeClient = new fn.FunctionsInvokeClient({     authenticationDetailsProvider: authProvider   });   fnClient.endpoint = invokeEndpoint; view raw invoke-fn.ts hosted with ❤ by GitHub リクエストを構成し、それをクライアントにパスし、結果を記録します。   (async () => {     const request: fn.requests.InvokeFunctionRequest = {       functionId: functionId,       invokeFunctionBody: JSON.stringify({         name: "Todd"       })     };     const response: fn.responses.InvokeFunctionResponse = await fnClient.invokeFunction(request);     console.log(         JSON.parse(           await helper.getStringFromResponseBody(response.value)         )     )   })() view raw invoke-fn.ts hosted with ❤ by GitHub TypeScriptをコンパイルし、呼び出します。   その他のSDKおよびAPIを使用 .NET、Python、Ruby、Go用のOCI SDKでもサーバーレスのファンクションを呼び出すことができます。詳しくは、それぞれのSDKのドキュメントを参照してください。  またOCI REST APIでもサーバーレスのファンクションを直接呼び出すことができます。しかしこれにはHTTPリクエストへの手動による署名が必要となるため、難しい作業になるかもしれません。しかしここまで紹介したメソッドでは対応していない言語やプロトコロルを使用している場合は、この方法をお試しください。 ボーナス情報! Ashishが執筆したこちらのブログで紹介されている便利なメソッドを使って、PostmanでOCI REST APIを呼び出すことも可能です。このことを教えてくれたMatt Curtisに感謝します! 写真提供:Goh Rhy Yan(Unsplash)

※本記事は、Todd Sharpによる"The Complete Guide To Invoking Serverless Oracle Functions"を翻訳したものです。 July 23, 2020 Oracle Functionsについては、数多くの情報をこのブログにて共有し、ファンクションの呼出しに関する例もさまざま紹介してきました。しかし、Oracle Cloudでサーバーレスのファンクシ...

津島博士のパフォーマンス講座 第77回 実行計画の比較について

津島博士のパフォーマンス講座 Indexページ ▶▶ 皆さんこんにちは、今年も昨年以上に厳しい夏になっていますが、体調に気をつけてください。 今回は、データベースの変更(バージョンアップなど)でパフォーマンス比較として使用するSQLパフォーマンス・アナライザ(SPA:SQL Performance Analyzer)とOracle Database 19c(Oracle19c)からの実行計画の違いの原因を特定する機能の二つの実行計画の比較について説明しますので、参考にしてください。   1. SQLパフォーマンス・アナライザ SQLの実行計画に影響を与える変更は、SQLパフォーマンスにも深刻な影響を与える可能性があります。DBAは、変更によって劣化したSQL文を特定して、修正するために多くの時間と労力を費やします。そのため、Oracle Database 11gからのSPAにより、実行計画に影響を与える変更を事前に予測・防止できるようになりました(ただし、Real Application Testingオプションが必要です)。また、SPAで問題のSQL文を特定してから、手動でSQLチューニング・アドバイザやSQL Plan Management(SPM)などを行うような使い方も可能なため、SQLチューニングも簡単に行えます。 (1)一般的な使用シーン まずは、SPAの一般的な使用シーンから説明します。 SPAは、SQL文の実行計画や実行統計に影響を与えるシステム変更を分析することに使用できます。一般的なシステム変更としては、以下のようなものがあり、変更前と変更後を比較することで、事前にSQLパフォーマンスの影響を確認することができます。 データベースのアップグレード データベースのアップグレードは、SQLパフォーマンスに影響を与えるオプティマイザを更新するので、特定のSQL文のパフォーマンスが劣化する可能性があります。 データベースの初期化パラメータの変更 データベースの初期化パラメータを変更すると、予期しない影響が生じることがあります。 スキーマの変更 索引の変更や新しい索引の作成などの変更は、ほぼ必然的にSQLのパフォーマンスに影響を与えます。 オプティマイザ統計情報の更新 新しいオプティマイザ統計を収集することは、オプティマイザのコスト計算と生成される実行計画に大きな影響を与えます。 オペレーティング・システムおよびハードウェアの変更 新しいオペレーティング・システム、CPUやメモリの追加、Oracle Real Application Clusters環境への移行などの変更は、SQLパフォーマンスに大きな影響を与える可能性があります。 チューニング推奨事項の実装 アドバイザ(ADDM、SQLチューニング・アドバイザ、SQLアクセス・アドバイザなど)からの推奨事項は、実装する前に効果を検証する必要があるかもしれません(遅くなるSQL文が存在するかもしれません)。 (2)SPAの使用方法 次に、SPAの使用方法について説明します。 SPAは、データベースの変更前後で、SQLチューニング・セット(STS)内のSQL文を比較して、SQLパフォーマンスに与える影響を評価します。そのシステム変更の評価は、以下のような手順で行います(以下のように、DBMS_SQLPAパッケージを使用して手動で実行するか、Enterprise Managerを使用して実行することができます)。 …<SQLワークロードの取得(SQLチューニング・セットに格納)>… /* ① SPAタスクの作成(SQLチューニング・セットの設定) */ VARIABLE v_task VARCHAR2(64); EXEC :v_task := DBMS_SQLPA.create_analysis_task(sqlset_name => 'spa_test_sqlset'); /* ② 変更前のSQL実行 */ BEGIN DBMS_SQLPA.execute_analysis_task(task_name => :v_task, execution_type => 'test execute', execution_name => 'before_change'); END; / …<システム変更の実施>… /* ③ 変更後のSQL実行 */ BEGIN DBMS_SQLPA.execute_analysis_task(task_name => :v_task, execution_type => 'test execute', execution_name => 'after_change'); END; / /* ④ SQLパフォーマンスの比較 */ BEGIN DBMS_SQLPA.execute_analysis_task(task_name => :v_task, execution_type => 'compare performance', execution_params => dbms_advisor.arglist('execution_name1','before_change', 'execution_name2','after_change')); END; / /* ⑤ レポートの出力 */ SET LINESIZE 1000 PAGESIZE 0 SET LONG 1000000 LONGCHUNKSIZE 1000000 SELECT DBMS_SQLPA.report_analysis_task(:v_task, 'TEXT', 'SUMMARY') FROM DUAL; ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   SPAの施行タイプには、以下の三つのモードがあります(上記の例は、'TEST EXECUTE'で行っています)。 実行計画だけを取得する(EXPLAIN PLAN) テスト・データを使用できないときに、オプティマイザ統計だけで実行計画の変化までを分析します。 SQLを実行して性能も取得する(TEST EXECUTE) テスト環境でテスト・データが使用できるときに行います。 STSの実行統計と計画を変換する(Convert SQLSET) SQLの実行を省略したいときに、STSの実行統計と実行計画などを変更前のテスト結果として使用します。ただし、動作環境の違い(同時実行など)による性能差に注意が必要です。 SPAには、テスト環境が用意できない場合でもテストを実施できるSPA Quick Checkもあり、本番環境で簡単に素早くテスト(初期化パラメータの影響、保留オプティマイザ統計の影響、SQLプロファイルの影響などの確認)ができるようになっています。 (3)SQLパフォーマンスの比較 次に、SQLパフォーマンスの比較方法について説明します。 SPAは、変更前と変更後のSQL試行で収集されたパフォーマンス・データを比較し、SQL文の実行計画やパフォーマンスの変化を特定するレポートを作成します。以下のように、execution_paramsパラメータにcomparison_metricパラメータを設定して、パフォーマンスの分析に使用する実行統計の式が指定できます。 /* SQLパフォーマンスの比較 */ BEGIN DBMS_SQLPA.execute_analysis_task(task_name => :v_task, execution_type => 'compare performance', execution_params => dbms_advisor.arglist('execution_name1','before_change', 'execution_name2','after_change', 'comparison_metric','buffer_gets')); END; / ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   指定可能な値は、elapsed_time(デフォルト)、cpu_time、buffer_gets、disk_reads、direct_writes、optimizer_cost、io_interconnect_bytesの各メトリックまたはこれらの組合せです。一般的なパフォーマンスの比較には、buffer_gets(論理リード)を使用すると判断しやすいと思います(elapsed_timeやdisk_readsで比較した場合、バッファ・キャッシュの状況やDBインスタンスの利用状況などによって誤差が生じるため、適切な判断がしにくいことがあります)。 SPAレポートの出力例が以下になります(sectionパラメータがSUMMARYのため「結果の詳細」は出力されていません)。これで実行計画が変化したSQLやパフォーマンス劣化のSQLなどが判断できます。 ※コードはこちらから.txtをダウンロードしてご確認ください。   (4)SQLチューニング・セット 最後に、SQLワークロードの取得に使用するSQLチューニング・セット(STS)について説明します。 STSは、チューニング・ツール(SQLチューニング・アドバイザ、SPMなど)への入力やデータベース間でのSQL転送などに使用できる便利なデータベース・オブジェクト(1つ以上のSQL文とその実行統計や実行コンテキスト)で、SYSAUX表領域に格納されています。STSの使用可能なソースには、AWR、共有SQL領域、SQLトレース・ファイル、他のSTSなどがあり、以下のように行うことでSTSを作成します(以下の例は、SELECT_CURSOR_CACHEテーブル・ファンクションを使用して、SPA_TESTスキーマによってSQL解析された"my_obj"が含むSQL文のカーソルを取得し、LOAD_SQLSETプロシージャを使用してSTSにロードします)。 /* STSの作成 */ EXEC DBMS_SQLTUNE.create_sqlset(sqlset_name => 'spa_test_sqlset'); /* STSへのロード */ DECLARE l_cursor DBMS_SQLTUNE.sqlset_cursor; BEGIN OPEN l_cursor FOR SELECT VALUE(a) FROM TABLE( DBMS_SQLTUNE.select_cursor_cache( basic_filter => 'sql_text LIKE ''%my_obj%'' and parsing_schema_name = ''SPA_TEST''', attribute_list => 'ALL') ) a; DBMS_SQLTUNE.load_sqlset(sqlset_name => 'spa_test_sqlset', populate_cursor => l_cursor); END; / ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   以下のように、DBA_SQLSET_STATEMENTSビューでSTSに関連付けられているSQL文を確認できます。 SQL> select SQL_TEXT from DBA_SQLSET_STATEMENTS where SQLSET_NAME='spa_test_sqlset'; SQL_TEXT -------------------------------------------------------------------------------- SELECT object_name FROM my_objects WHERE object_id = 100 ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   STSは、DBMS_SQLTUNEパッケージ(Oracle Tuning Packが必要)を使用していましたが、Oracle Database 18cからTuning Packの必要ないDBMS_SQLSETパッケージが使用できるようになりました。   2. 実行計画の比較 ここでは、Oracle19cからの実行計画の比較機能について説明します。 実行計画の比較で難しいのは、単純な行ごとに比較しても意味がなく、より複雑な分析で論理的な違いを特定する必要があるからです。そのため、慣れていない方だと行うことが難しいので、Oracle19cから簡単に比較する機能が提供されています(以前からのDBMS_XPLAN.DIFF_PLANファンクションでも実行計画の比較ができるようですが、詳細な説明がないので、この機能を使用してください)。 (1)使用用途 実行計画の比較は、比較する実行計画に対して、作成した比較レポートで相違の原因を特定することができ、実行計画の再現性の問題を調査する際に使用できます。この比較レポートは、特に以下のようなシナリオ(様々なユースケース)で役立ちます。 パフォーマンスが低下している問合せの計画と古い計画を比較する場合 実行計画の変更が発生した場合、それを古い計画(AWRに保存された計画など)と比較して、調査するときがあります。 SQL計画ベースラインの再現に失敗したときの新しい計画との相違点を判断する場合 計画ベースライン内の計画が再現されない場合、生成された計画とどのように異なっているかを確認する必要があります。 計画に与える影響(ヒントの追加方法など)を判断する場合 特定のヒントの追加、パラメータの変更、索引の作成などが、計画にどのように影響するかを確認したいときがあります。 SQLプロファイルやSPAの異なる点を判断する場合 新しいSQLプロファイルに基づいて生成された計画と元の計画、SPAによって示された計画が、どのように異なっているかを確認したいときがあります。 同じSQL文なのに、実行計画が変化した理由が分からない場合があります。また、実行計画の行数が多いと慣れた方でも比較するのが大変です。そのような場合に、このレポートが原因の究明に役立つかと思います。 (2)使用方法 実行計画の比較は、DBMS_XPLAN.COMPARE_PLANSファンクションに、参照プラン(reference_plan)と比較プラン・リスト(compare_plan_list)を指定して行います。参照プランが単一の計画ソース、比較プラン・リストがplan_object_listタイプによる汎用オブジェクト・リスト(複数の計画ソース)を、入力として使用できます(つまり、複数の計画ソースの実行計画を同時に比較することができます)。実行計画は、様々な場所に存在するので、以下のような計画ソースを指定することができるようになっています。オプション・バラメータがNULLのときには、複数のオブジェクトと比較することを意味します(例えば、cursor_cache_objectにchild_numberを指定しないと、指定したSQL_IDのすべての実行計画と比較します)。 計画ソース 指定方法 PLAN TABLE plan_table_object(owner, plan_table_name, statement_id, plan_id) カーソル・キャッシュ cursor_cache_object(sql_id, child_number) AWR awr_object(sql_id, dbid, con_dbid, plan_hash_value) SQLチューニング・セット sqlset_object(sqlset_owner, sqlset_name, sql_id, plan_hash_value) SQL計画管理 spm_object(sql_handle, plan_name) SQLプロファイル sql_profile_object(profile_name) アドバイザ advisor_object(task_name, execution_name, sql_id, plan_id)   以下のように実行して、比較レポートを出力します(この例は、カーソル・キャッシュの実行計画を比較して、TEXTタイプのレポートを出力しています)。 VARIABLE v_rep CLOB BEGIN :v_rep := DBMS_XPLAN.COMPARE_PLANS( reference_plan => cursor_cache_object('0hxmvnfkasg6q', NULL), compare_plan_list => plan_object_list(cursor_cache_object('10dqxjph6bwum', NULL)), type => 'TEXT', level => 'TYPICAL', section => 'ALL'); END; / SET LINESIZE 210 SET PAGESIZE 50000 SET LONG 100000 COLUMN report FORMAT a200 SELECT :v_rep REPORT FROM DUAL; ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   比較レポートの出力例が以下になります。比較プランごとに「Comparison Results」セクションが出力されます(この例は、参照プランの問合せのみが第34回で説明した「結合の排除」変換を使用していることを示しています)。 ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   3. おわりに 今回は、実行計画の比較について説明しましたが、少しは参考になりましたでしょうか。これからも頑張りますのでよろしくお願いします。 それでは、次回まで、ごきげんよう。    

津島博士のパフォーマンス講座 Indexページ ▶▶ 皆さんこんにちは、今年も昨年以上に厳しい夏になっていますが、体調に気をつけてください。今回は、データベースの変更(バージョンアップなど)でパフォーマンス比較として使用するSQLパフォーマンス・アナライザ(SPA:SQL Performance Analyzer)とOracle...

第22回 ExaCSのメンテナンスに備える - スケジューリング機能 | もしもみなみんがDBをクラウドで動かしてみたら

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/08/25時点のものになります みなさん、こんにちは。 前回、Database Cloud Service(DBCS)/Exadata Cloud Service(ExaCS)のバージョンやパッチ適用について解説しましたが、続きとして定期的なインフラ・メンテナンスについて触れてきたいと思います。今回は、ExaCSの定期メンテナンスの考え方や、ユーザー側で任意のタイミングを指定することが可能なスケジューリング機能を紹介していきます。 目次 ExaCS 定期メンテナンスについて メンテナンス・スケジューリング機能について メンテナンス・スケジューリングの設定方法 スケジュールを確認する方法 スケジュールされた日程を変更する 通知の設定 まとめ window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-159254751-1');   1. ExaCS 定期メンテナンスについて PaaSサービスとして提供しているExaCSでは、インフラ層、具体的にはExadataのH/W、Databaseサーバー上の管理ドメイン(dom0)、Storageサーバー、ネットワーク、ファームウェアなどは、オラクルが責任範囲として運用・管理を行います。そのため、インフラ層に対するメンテナンスをオラクルが定期的に実施する形になります。安定性・セキュリティを考慮したサービスの提供のために、インフラ層の定期メンテナンスは四半期ごとに必須で行います。 四半期の定期メンテナンスでは、前回解説したユーザー管理層のコンピュートのOS以上に対してはメンテナンスは行いません。ただし、Databaseサーバーの管理ドメイン(dom0)に対してのメンテナンスを行うため、その際にコンピュート(ノード)の再起動が発生します。ですが、ExaCSはデフォルトで各コンポーネントが冗長化されている構成です。データベースに関しては、Oracle Real Application Clusters (RAC) を利用したデータベースのクラスタリング、データベースがアクセスするストレージもOracle Autonomatic Storage Management (ASM) でのクラスタリング構成となっています。メンテナンス自体は、1サーバー、1ノードずつ行われる(ローリング・メンテナンス)ため、基本的にExaCSのサービス、その上で動くデータベースとしてはオンラインで稼働し続ける形になり、ダウンタイムはほぼない形で実施されます。(緊急のメンテナンスやセキュリティ対応などでサービス停止が必要となる場合には、事前にその旨も通知されます) ただし、基本サービス停止はしないものの、メンテナンス中は縮退運転(通常時よりも利用可能なリソースが減少)のため、性能影響やその間のセッションの瞬断などはあります。そのため、オラクル側で計画実施される定期メンテナンスで、懸念点を許容するのが難しいという場合には、対応を検討する必要が出てきます。検討するケースにも2種類あり、1つはユーザーの指定した日時でならメンテナンス実施可能なケース、1つはメンテナンス期間を設けるのが難しい(ローリング・メンテナンスでも難しい)ケースになります。後者の場合は、クライアント側のエラーハンドリングや再接続/再試行の仕組みの実装や、メンテナンス前後でのレプリカへの切り替えなどが対応策としてあります。今回は、前者の場合に利用できる、メンテナンス・スケジューリング機能について紹介していきます。 参考 ・マニュアル Oracle-Managed Infrastructure Maintenance Updates 2. メンテナンス・スケジューリング機能について 定期メンテナンスの実施日時はオラクルから予定日時が通知されますが、ExaCSではコンソール上から事前にメンテナンス実施可能なタイミングを設定することが可能です。具体的には、メンテナンスが実施可能な月、週 、曜日、開始時間(UTC) と、実施日からみて通知をどれくらい前(リード・タイム)にほしいかを設定可能です。この設定内容に基づき、オラクル側で定期メンテナンスの実施予定日を設定し、リード・タイムに従う形で事前にユーザーに通知されます(通知先はテナントの管理者)。 参考 ・マニュアル Scheduling Oracle-Managed Infrastructure Updates   3. メンテナンス・スケジューリングの設定方法 「DBシステムの詳細」ページで、「DBシステム概要」の「インフラストラクチャ・メンテナンス」のセクションに、オラクル実施の定期メンテナンスの情報が記載されています。 インフラストラクチャ・メンテナンス実施日時の指定 『メンテナンス・スケジュール カスタム・スケジュール』の右にある『編集』をクリック デフォルトは『プリファレンスなし』=任意のタイミングでスケジューリングされる設定になっているので、『スケジュールの指定』のボタンをクリックし、メンテナンスがスケジュールされてもいい日程にあてはまる内容を入力していきます。 設定項目 ・メンテナンス月 : 任意の月 or 四半期(1-3月/4-6月/7-9月/10-12月)の中で1つ以上選択 ・週 : 任意の週 or 第x週を1つ以上選択 ・曜日 : 任意の曜日 or 1つ以上選択 ・開始時間(UTC) : 任意の時間 or 1つ指定 ・リード・タイム : 1-4週間前から1つ指定 (事前通知のタイミングとして、実施日からみて通知をどれくらい前にほしいか) 設定をしたら、『インフラストラクチャ・メンテナンス・スケジュールの更新』をクリックして完了です。あとは、この内容に基づき、定期メンテナンスの日程が計画されていく形になります。よくある問い合わせとして、「今月のxx週に実施されるように設定したのに、メンテナンスが実施されなかった」ということがありますが、スケーリング機能で設定したタイミングがリード・タイムを守れないタイミング(例えば、リード・タイムを4週間前と設定したにも関わらず、次回のメンテナンスが2週間後に実施されるような設定にされている場合など)は、リード・タイムを守るように次の四半期内の実施可能なタイミングに予定されます。リード・タイプの最短は1週間前のため、いきなり翌日を指定しても翌日には計画・実施はされません。 参考 ・マニュアル Scheduling Oracle-Managed Infrastructure Updates   4. 予定されたメンテナンス・スケジュールを確認 インフラストラクチャのメンテナンスが計画されると、テナントの管理者へのメール通知ならびにコンソール上での確認ができるようになります。コンソール上でスケジューリングされた内容を確認してみましょう。 「DBシステム詳細」ページからの確認 「インフラストラクチャ・メンテナンス」セクションの『次回のメンテナンス』に計画されたメンテナンス日時が表示されます。 詳細を確認するには、右の『表示』をクリック(今回は7月の平日の17時(UTC)に実施されるように事前に設定した内容に対して計画された、次回メンテナンス) 計画されたメンテナンスは、左側の『メンテナンス』のタブから確認可能です。その下の「メンテナンス履歴」から実施済の履歴をみることも可能です。 『お知らせ』からの確認 インフラストラクチャ・メンテナンスの日程の通知はテナントの管理者に通知される内容のため、テナント全体に対するお知らせを確認する画面でも、このメンテナンスについての内容が確認可能です。 コンソールの右上の『お知らせ』マークをクリック お知らせの一覧に表示されます。フィルタしたい場合は、タイプを「スケジュール済メンテナンス」にすると絞れます。 参考 ・マニュアル To view or edit the time of the next scheduled maintenance for Exadata DB system infrastructure   5. スケジュールされた日程を変更する 事前にプリファレンスを設定して計画されたメンテナンスであっても、何かしらの理由によりメンテナンス日時を変更したいということはありますよね。その場合も、コンソール上からスケジュールの変更が可能です。 次回のメンテナンスの日時変更 「DBシステム詳細」ページの「インフラストラクチャ・メンテナンス」>『次回のメンテナンスの右の『表示』をクリック 『スケジュール開始時間』の右の『編集』をクリック 入力画面の右にあるカレンダーアイコンをクリックして、変更したい先の日時を指定し、『スケジュールされた開始時間の更新』をクリックします。 参考 ・マニュアル To view or edit the time of the next scheduled maintenance for Exadata DB system infrastructure   6. 通知の設定 よくある質問として、 ・「テナント管理者以外に、メンテナンス計画の通知はできないのか」 ・「インフラストラクチャ・メンテナンスを実際に実施するタイミング(開始時間と終了時間)に通知を飛ばせないのか」 といったものがありますが、この場合はOracle Cloud Infrastructureの「イベント」と「通知」サービスを使ってみましょう。イベントサービスは、クラウド上のリソースの変化をとらえてサービスが自動的に連動する仕組みを作れます。その連動先として、通知サービスを用いると、メール通知やHPPTS(カスタムURL)、PagerDutyなどのメッセージング機能を使うことができます。 関連イベントの種類 ExaCSのインフラストラクチャ・メンテナンスに関連するイベント・タイプとしては、2020/08時点では下記のものがあります。 ・Exadata Infrastructure - Maintenance Scheduled : メンテナンス日時が計画(テナント管理者には自動通知)   ・Exadata Infrastructure - Maintenance Reminder : メンテナンス実施前のリマインド(テナント管理者には自動通知) ・Exadata Infrastructure - Maintenance Begin : メンテナンス開始時 ・Exadata Infrastructure - Maintenance End : メンテナンス完了時 7. まとめ クラウドを利用するにあたり、計画停止・計画外停止に関する対応を考えることは大事です。もちろん、その点はオンプレミスでも同じですが、違うところはインフラ側のクラウド・ベンダー主導のメンテナンスがあることです。そのため、クラウド利用を検討する場合には、システムでメンテナンス日時を設けたり、計画停止時の対応方針を決めたり、サービス継続するための実装を考えたり、事前にどのように対応するかを考えることは大事になります。インフラ側はオラクル管理のレイヤーとして定期メンテナンスが実施されますが、今回ご紹介したように、事前にユーザー側で任意のタイミングを指定することが可能です。また、RACやGrid Infrastructure(ASM含む)、SCANなどOracle Databseの可用性機能をデフォルトで実装している構成のため、メンテナンス実施時にもサービスとしての停止は抑える形で実施されています。さらに対応が必要な場合には、Oracle Application Continuityによるアプリケーション側からの接続の自動再接続・トランザクションの再実行や、Oracle Data GuardやOracle GoldenGateで同期している環境への切り替えを行ったりなど、Oracle Databaseの機能や関連製品でも対応が可能です。クラウド上で動かすシステムの要件にあった、計画メンテナンス(停止)の対応を実装・運用に取り組み、安全なサービス継続を検討していきましょう。   よくある質問 Q1) インフラストラクチャ・メンテナンスを拒否したり、スキップすることはできますか A1) 出来ません。安定性・セキュリティを考慮したサービスの提供のために、インフラ層の定期メンテナンスは四半期ごとに必須で行います。 Q2) テナント管理者以外へのメンテナンス計画の通知はできますか。また、開始時・終了時に連絡してもらえますか。 A2) デフォルトでは、計画日時の決定とリマインドのみが自動でテナント管理者に通知されます。それ以外は、OCIのイベントサービスと通知サービスで該当イベントの通知の仕組みを設定することで通知可能です。詳細は、6. 通知の設定をご確認ください。   メンテナンスに関して、2020/07/29 Oracle Database Technology Nightで解説した資料はこちら(動画あり)   もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/08/25時点のものになります みなさん、こんにちは。 前回、Database Cloud Service(DBCS)/Exadata...

Java Magazine

クイズに挑戦:関数型インタフェース(上級者向け)

※本記事は、Simon Roberts、Mikalai Zaikinによる"Quiz Yourself: Functional Interfaces (Advanced)"を翻訳したものです。 想定通りに動作する関数型インタフェースの定義と実装   著者:Simon Roberts、Mikalai Zaikin 2020年2月27日   その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 関数型インタフェースの書き方に関する知識を確認します。 次の4つのインタフェースについて: interface Printable { void print(); } interface Stringable { String toString(); } interface Cloneable { Object clone(); } interface InfoItem extends Printable, Stringable, Cloneable {} Java 11で有効な関数型インタフェースはどれですか。2つ選んでください。 A.    Printable B.    Stringable C.    Cloneable D.    InfoItem 解答:この設問は、関数型インタフェースが従うルールについて問うものです。コンパイルできるという単純な観点から見れば、上記のインタフェースはすべて有効です。つまり、それぞれの構文は有効なJavaインタフェースです。しかし、関数型インタフェースには追加の制約があります。この点は、Java 11のJava言語仕様のセクション9.8に記載されています。 このルールを簡単に説明する場合、「関数型インタフェースには厳密に1つの抽象メソッドがある」と言うのが一般的です。多くの場合、これは単一抽象メソッド・インタフェースまたはSAMインタフェースと呼ばれます。実は、この用語は他のいくつかの言語において中核的な仕様になっています。 以上のことを踏まえれば、選択肢A、B、Cは正しそうに見えます。しかし、選択肢Dは他の選択肢ほど正しいようには思えません。選択肢Dで定義されているインタフェースには、print()、toString()、clone()の各メソッドが必要になるでしょう。ただし、toString()とclone()というメソッドのシグネチャには見覚えがあるでしょう。この2つは、いずれもjava.lang.Objectクラスの中に存在しています。そこで定義されていることを考えれば、他のオブジェクトにも存在しているに違いありません。そこから、これらのメソッドは実のところ抽象メソッドではないと言うことができるでしょうか。 実は、Java仕様では追加の制限を定義することにより、この問題に対処しています。 関数型インタフェースの定義では、Objectのパブリック・メソッドでもあるインタフェースのメソッドは除外されます。 この点から、引数がなくStringを返す、設問のtoString()メソッドが、Objectのpublicメンバーとして定義されているtoString()メソッドとまったく同じであることは明らかなはずです。したがって、前述のルールを満たす抽象メソッドがStringableインタフェースには定義されていないことになるため、このインタフェースはJavaの有効な関数型インタフェースではありません。以上より、Stringableは関数型インタフェースではなく、選択肢Bは誤りであることがわかります。 それでは、Objectには引数のないclone()メソッドが定義されているため、選択肢Cも誤りということになるのでしょうか。そうではありません。ここで鍵となるのは、Objectのclone()メソッドがprotectedとして宣言されていることです。実際の定義は次のようになっています。 protected native Object clone() throws CloneNotSupportedException; ここで問題になるのも、選択肢CのCloneableインタフェースで定義されているメソッドが、関数型インタフェースに必要な単一抽象メソッドの要件を満たすかどうかです。ルールは明確です。clone()メソッドがObjectに存在することは重要ではありません。clone()はObjectでpublicではないため、Cloneableは関数型インタフェースと見なされます。したがって、選択肢Cは正解です。 選択肢Aは簡単なはずです。print()メソッドは、このインタフェースで宣言されている唯一の抽象メソッド(実際には唯一のメソッド)であり、Objectには存在しません。そこから、選択肢Aも正解であることがわかります。 選択肢Dのインタフェースは少し異なっており、他の3つのインタフェースを拡張しています。インタフェースにabstractメソッドがある場合、そのメソッドを実装する義務が生じます。あるインタフェースが別のインタフェースを拡張する場合、新しいインタフェースはすべての拡張対象インタフェースにおけるすべての義務を引き継ぐことになります。ただし、そのインタフェースにいずれかの義務を満たすdefaultメソッドを追加される場合を除きます。したがって、次に例を示す3つのインタフェースは、いずれも1つの抽象メソッドdoIt()を宣言しているだけであるため、すべて有効な関数型インタフェースです。 @FunctionalInterface interface A { void doIt(); } @FunctionalInterface interface B extends A {} @FunctionalInterface interface C extends B {} これを踏まえれば、選択肢Dのインタフェースには、print()とclone()という2つのabstractメソッドが必要であることがわかります。設問のtoString()メソッドは、抽象メソッドであるという前述の条件を満たさないため、ここでの対象には含まれないことに注意してください。抽象メソッドが厳密に1つでなければならないというのは、関数型インタフェースのもっとも基本的な要件です。しかし、このインタフェースには2つのabstractメソッドがあるため、要件を満たしません。よって、InfoItemが関数型インタフェースであるはずがなく、選択肢Dは誤りです。 補足ですが、java.lang.Cloneableインタフェースは、設問のCloneableインタフェースとはまったく別物である点に注意してください。java.lang.Cloneableインタフェースにはメソッドが存在しないため、関数型インタフェースではありません。これはマーカー・インタフェースと呼ばれ、メソッドが存在しないインタフェースを指す用語として使われます。その一例がjava.io.Serializableです。マーカー・インタフェースは、クラスにラベルを付けることで、そのインタフェースを実装するインスタンスに関する決定を実行時に行うことができるようにする方法です。このアプローチは、Java 5で言語にアノテーションが導入される前に使われていました。Objectで宣言されているclone()メソッドは、サブクラスがjava.lang.Cloneableインタフェースを宣言しているかどうかにはよらず存在します。 正解は選択肢AとCです。   Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon Roberts、Mikalai Zaikinによる"Quiz Yourself: Functional Interfaces (Advanced)"を翻訳したものです。 想定通りに動作する関数型インタフェースの定義と実装   著者:Simon Roberts、Mikalai Zaikin 2020年2月27日   その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、...

Java Magazine

クイズに挑戦:ラムダ式(上級者向け)

※本記事は、Simon Roberts、Mikalai Zaikinによる"Quiz Yourself: Lambda Expressions (Advanced)"を翻訳したものです。 ラムダ式でvarを使用する際の細かい決まり   著者:Simon Roberts、Mikalai Zaikin 2020年2月27日   その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 ラムダ式を作成し、ローカル変数とともに使用する際の知識を確認します。 ユーザー情報を含むPDFレポートを生成するソフトウェアを書いているとします。ドキュメント・ヘッダーのラベルの長さを計算するために、ユーザー名とタイトルを引数として受け取り、テキストの長さを返すラムダ式を作成します。このラムダ式は、次のように関数型インタフェースToIntBiFunctionを実装します。 ToIntBiFunction<Name, String> l = ... // この部分のコードは省略 あるフレームワークを使ってこの動作を呼び出し、メソッド引数のアノテーションにより、提供される引数の妥当性を保証します。たとえば、このフレームワークでは前述の機能を使用して、欠落した値(NULLポインタ)や範囲外の値などの問題を検知することができます。ラムダ式の実装では、最初のパラメータ(Name型のもの)にアノテーション@ValidNameを適用する必要があります。 以上の要件を満たすラムダ式はどれですか。2つ選んでください。 (@ValidName Name n, String t) -> n.firstName.length() + n.lastName.length() + t.length(); (@ValidName var n, String t) -> n.firstName.length() + n.lastName.length() + t.length(); (@ValidName var n, var t) -> n.firstName.length() + n.lastName.length() + t.length(); (@ValidName n, t) -> n.firstName.length() + n.lastName.length() + t.length(); (@ValidName Name n, var t) -> n.firstName.length() + n.lastName.length() + t.length(); 解答:Java 10では、メソッドのローカル変数に適用できる予約済み型名として、varが導入されました。これを(指定された条件のもとで)使用して、ローカル変数を初期化する式から変数の型が推論されるようにすることができます。この提案は、JEP 286に記載されています。Java 11では、JEP 323によってこの機能が拡張されたため、varをラムダ式のパラメータとして使えるようになりました。 ラムダ式の引数の形式は、もともと、すべての引数に完全な型を指定するか、すべての引数の型を推論させるかのいずれかでした。型推論形式は、次のようになります。 (x) -> x.clear(); 型明示形式は、次の例のようになります(型は非常に複雑な場合があることに注意してください。そのような、コードを理解するうえで型の明示的な知識があまり役に立たない状況は、型推論が便利な場面の1つです)。 (Map<String, List<Map.Entry<String, Long>>> x) -> x.clear(); 型推論形式の場合、型宣言にアノテーションを追加することはできませんが、型明示形式では可能です(この場合、アノテーションはパラメータ自体ではなく、パラメータの型に適用されます)。そこで、@ValidMapアノテーションが存在すると仮定すれば、設問の例は次のようになるでしょう。 (@ValidMap Map<String, List<Map.Entry<String, Long>>> x) -> x.clear(); しかし、型推論形式ではアノテーションを適用できないため、次のコードは無効な構文でしょう。 (@ValidMap x) -> x.clear(); // // 型がないためコンパイルできない Java 11で導入されたJEP 323の拡張により、ラムダ式の引数で疑似タイプvarを使えるようになりました。この機能により、アノテーションに対応した簡潔な型推論構文を使用できます。アノテーションがない場合、コードは次のようになります。 (var x) -> x.clear(); これにアノテーションを適用することもできます。その場合、次のようになります。 (@ValidMap var x) -> x.clear(); この最後の形式を、元の形式と直接比較してみます。 (x) -> x.clear(); (@ValidMap var x) -> x.clear(); いずれの場合も、xの型は推論されることに注意してください。明示的な型を指定しないため、コードが簡潔になる可能性があるというメリットがあります。ただし、前者ではアノテーションの使用が認められていないのに対し、var疑似タイプを用いる後者ではアノテーションを使用できます。この構文には、アノテーションを付加する「型」の要素が含まれているからです。 ここまでで、アノテーションはvarまたは実際の型とともに使えますが、いずれも存在しない場合は使えないことがわかりました。選択肢Dは、型もvarもないところでアノテーションを使おうとしているため、選択肢Dは誤りであることがわかります。 もちろん、この設問はこれだけで解決できるものではありません。ラムダ式には、Java 8で導入されてから存在し続けている、別のルールがあります。そのルールとは、1つのラムダ式の引数リストで1つでも明示的に型を指定している場合、そのラムダ式のすべての引数で型を明示しなければならないというものです(逆に言えば、型を省略したい場合は、すべての型を省略しなければなりません)。 このルールは現在も適用されますが、varの使用に対応するために拡張されています。具体的には、すべての引数の型を明示する、すべての引数でvarを使って型推論を行う、すべての引数で明示的な型もvarも使わない、という3つから選択しなければなりません。 このルールから、1つのラムダ式の宣言で、ある引数にはvarを使い、その他の引数には明示的な型を使うというように混在させることはできないとわかります。そのため、選択肢BとEは誤りです。 また、1つのラムダ式の宣言において、すべての引数で同じ型メカニズムが使われている(つまり、すべてで型が明示されている)か、すべてでvarが使われているか、すべてに何も付けずに推論を行っている場合、(少なくとも前述のルールの点では)コードは正しいことになります。選択肢Aと選択肢Cは、これを満たしていることがわかります。選択肢Aではすべての引数で明示的な型が宣言されており、選択肢Cではすべての引数にvarが適用されています。そこから、選択肢Aと選択肢Cは正解であることがわかります。 この設問に関連した、役立つかもしれない補足を2つほどしておきます。1点目は、選択肢AはJava 8で有効だったのに対し、選択肢CはJava 11およびそれ以降でのみ有効であることです。2点目は、ラムダ式が型指定のない引数を1つ受け取る場合に限った特例があることです。次に例を示します。 (x) -> x.doSomething(); // OKラムダ式の通常の構文 この場合に限り、括弧を省略できます。そのため、次のコードも有効です。 x -> x.doSomething(); // OK単一引数ラムダ式の特殊な構文 ただし、このルールでは、以下の形式は認められていません。 SomeType x -> x.doSomething(); // 不可 -- 括弧が必要 var x -> x.doSomething(); // 不可 -- 括弧が必要 型を指定したい場合、またはvarを使用したい場合は、括弧を使用する必要があります。次に例を示します。 (SomeType x) -> x.doSomething(); // OK (var x) -> x.doSomething(); // OK 正解は選択肢AとCです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon Roberts、Mikalai Zaikinによる"Quiz Yourself: Lambda Expressions (Advanced)"を翻訳したものです。 ラムダ式でvarを使用する際の細かい決まり   著者:Simon Roberts、Mikalai Zaikin 2020年2月27日   その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして...

Java Magazine

クイズに挑戦:2次元配列(中級者向け)

code { font-family: courier,"courier new",monaco !important; font-size: 17px !important; color: #00488a !important; margin: 0 !important; background-color: #fff !important; padding: 0 5px !important; display: inline !important; } varと2次元配列の併用には注意が必要 著者:Simon Roberts、Mikalai Zaikin 2020年2月27日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 2次元以上の配列を扱う際にvarを正しく使う能力について確認します。 次のメソッドについて: public static void main(String[] args) {     var[][] arr = {{"1", 2},{Integer.valueOf(3)},{4.0}};     System.out.print(arr[0][1]); } どのような結果になりますか。1つ選んでください。 2 3 Object@1b0375b コンパイルが失敗する この設問は、2次元配列の宣言と初期化、そしてその状況でのvarの使用について問うものです。 varはJava 10で導入されました。厳密に言えば、varはキーワードや型ではありません。Java 11のJava言語仕様のセクション3.9「キーワード」には、「ローカル変数宣言の型として特殊な意味を持つ識別子」と書かれています。便宜上、これを「var疑似タイプ」と呼ぶことがよくあります。 var構文は、特定の状況でローカル変数の宣言と初期化を行う際、一般的な形式の代わりに使用できるものとして導入されました。この構文には、見た目がわかりやすくなるなどのメリットがあります。一般的な形式とは、従来どおり、次のようにして変数の宣言と初期化を行うものです。 int count = 99; この文は、次のように変更できます。 var count = 99; この例の場合、3文字の型名intを「見にくい」と判断するのはやや無理があります。しかし、変数によっては非常に冗長になる可能性があります。ネストしたジェネリックを多用した変数などは特にそうです。さらに、状況によっては、プロトタイプのコードを頻繁に変更しながら実験する際に、var構文を使うことで変数の型を変えやすくなります。 var構文は、すべてではなく、特定の状況で使用できると言いました。制限の1つに、varは配列の宣言でベースタイプの代わりには使用できないというものがあります。これは言語仕様での実際の表現ではありませんが、こう表現するのが最適でしょう。この制限に関連する言語仕様のセクションはやや長く、かなり抽象的な構文仕様について述べているからです。興味がある方のためにお伝えすると、セクション14.4「ローカル変数宣言文」に記述されています。この制限は、配列の宣言にはvarを使用できないという意味に誤解されがちです。しかし、正確に言えばそのような内容ではありません。実際の制限は、代入による初期化の左辺に角括弧がある場合、varは使用できないというものです。例で示した方がわかりやすいでしょう。 次に示すのは、通常の形式による有効な初期化です。 int ia[] = new int[]{1,2,3}; // 有効 ただし、次に示すものは、varの有効な使用方法ではありません。代入の左辺に角括弧があることに注意してください。 var ia[] = new int[]{1,2,3}; // 有効でない しかし、次のコードは許可されています。角括弧がなくなっていることに注意してください。iaに対して推論される型は、「intの配列」という正しい型です。 var ia = new int[]{1,2,3}; // 有効 上記の例には、見慣れない部分が別にあることに注意してください。「new int[]」という部分です。この部分に注目します。次の例も有効でないことに注意してください。 var ia = {1,2,3}; // 有効でない 最後の例が失敗する理由は、配列に明示的なベースタイプが存在しないからです。実はこれは、メソッドの引数リストで簡略形による配列の宣言が許可されていない理由と同じです。例として、次のメソッド宣言について考えてみます。 void doStuff(Object[] oa) {} 次の呼出しは問題ありません。 doStuff(new String[]{"Hello", "Goodbye"}); // 有効 しかし、次のコードは有効ではありません。 doStuff({"Hello", "Goodbye"}); // 有効でない ここで問題になるのは、皆さんにとってはこの配列が「Stringの配列」であるはずだということは明らかかもしれませんが、コンパイラではそのように確実に推論することはできないという点です。本当に作りたいのはObjectの配列なのに、たまたまStringだけが入っている場合はどうなるでしょうか。不確定要素が存在する場合、コンパイラでは正しく推論することができなくなります。varを使う場合も、配列のベースタイプとして想定されるものは何であるかという、同じ問題が発生します。したがって、varはこのような状況では許可されていません。その結果、設問のコードはコンパイルに失敗するため、選択肢Dが正解で、選択肢A、B、Cは誤りです。 興味のある方は、JEP 286にある詳しい説明をご覧ください。 少し話はそれますが、varの代わりに配列のベースとして使用できる明示的な型は何であるかを考えてみるのもおもしろいものです。ここで必要になるのは、Javaの一般的な型の1つで、右辺の初期化から任意の値を受け取ることができるものです。プリミティブ値をオブジェクトでラップするオートボクシングを考慮すれば、いくつかの型が考えられます。一番わかりやすいのはおそらくjava.lang.Objectですが、java.io.Serializableやjava.lang.Comparableでもよいでしょう。次のようにしてビルドすれば、コード全体が動作するでしょう。 Object [][] arr = {{"1", 2},{Integer.valueOf(3)},{4.0}}; // OK それでは、この変更を行い、コードのコンパイルが成功している場合、どうなるかを考えてみます。mainメソッドの2行目では、値arr[0][1]を出力しています。今回の場合、arr[0]は{"1", 2}を指しているため、arr[0][1]は値2をラップしているIntegerオブジェクトを指すことになります。したがって、修正した変数宣言を使用すれば、コードから2が出力され、選択肢Aが正解となるでしょう。 ここでオートボクシングの効果に注目するのも興味深いものです。変数宣言の修正後に、次のコードを実行するとします。 System.out.print(arr[0][1].getClass().getCanonicalName()); この実行により、java.lang.Integerが出力されるでしょう。そこから、プリミティブの2がIntegerオブジェクトでボクシングされていることがわかります。 正解は選択肢Dです。   Java Magazine 日本版Vol.49の他の記事 Java 14でのJava Flight RecorderとJFR Event Streaming 多くの新機能を搭載したJava 14が登場 Java EEからJakarta EEへ クイズに挑戦:ラムダ式の型(上級者向け) クイズに挑戦:2次元配列(中級者向け) クイズに挑戦:ラムダ式(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 ※本記事は、Simon Roberts、Mikalai Zaikinによる”Quiz Yourself: Two-Dimensional Arrays (Intermediate)“を翻訳したものです。

varと2次元配列の併用には注意が必要 著者:Simon Roberts、Mikalai Zaikin 2020年2月27日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しか...

Java Magazine

クイズに挑戦:ラムダ式の型(上級者向け)

code { font-family: courier,"courier new",monaco !important; font-size: 17px !important; color: #00488a !important; margin: 0 !important; background-color: #fff !important; padding: 0 5px !important; display: inline !important; } ラムダ式はラムダ式を返せるのか 著者:Simon Roberts、Mikalai Zaikin 2020年2月27日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 java.util.functionパッケージのインタフェースの正しい使用方法に関する知識を確認します。 次のコードについて: public void doIt() {     /* insert here */ v1 = () -> a -> {};  }   次のうち、コメント/* insert here */の部分を完全に置き換えることで、コードが正しく完成するものはどれですか。1つ選んでください。 var Supplier<Consumer<?>> Function<Consumer<?>,?> Function<Predicate<?>,?> Consumer<Function<?,?>> この設問は、ラムダ式の特徴、ネストされたラムダ式の複雑さ、ラムダ式を用いた関数型インタフェース・タイプ変数の宣言と初期化、さらにはjava.util.functionパッケージの関数型インタフェースの性質について問うものです。 ラムダ式の考え方に慣れていない場合、ラムダ式を含むコードは理解しづらいことがあります。また、状況によっては、かなりの経験を持つ人でも理解しにくい場合があります。そんなときに役立つ秘けつの1つが、物事を小さな部分に分解することです。すべてのラムダ式は、引数リストとメソッド本体でできています。メソッド本体が単なる1つの式である場合も、波括弧でくくられた完全なメソッド本体が存在する場合もあります。しかし、いずれにせよ、メソッド本体は必ず存在します。 そこで、まずは選択肢に記述されている型宣言を無視し、変数v1を初期化することになっている式だけを見てみます。次の式です。 () -> a -> {} この構文は、Haskellを経験したことがある方にとっては非常になじみ深いものかもしれません。しかし本稿は、Java認定試験に出題されるJavaの構文について説明する、Java Magazineの記事であるため、ほとんどの方は初めて見ると考えるのが無難です。 それでは、どのように分解すればよいでしょうか。まず、すべてのラムダ式は引数リスト、アロー、メソッド本体という3つの部分でできていることがわかります。この場合、どこがそれぞれの部分に当てはまるのかを見てみます。 ()         ->      a -> {} 引数リスト    アロー    メソッド本体 2つ目のアローがこのラムダ式のアローではないことがどうしてわかるのかというのは、もっともな疑問でしょう。理由は2つあります。1つ目は、引数リストにアローを含めることはできないからです。2つ目はさらに抽象的ですが、ほとんどのJava演算子と同じく、アローも左から右へとグループ化するからです。 この時点でわかるのは、全体としては引数が0個のラムダ式であることです。このことだけで多くのことがわかりますが、それはひとまず横に置き、メソッド本体を見てみることにします。 今回の場合、メソッド本体はもう1つのラムダ式になっていますが、短縮形で記述されています。ラムダ式には、2種類の形式が存在します。1つ目の形式は右側に波括弧があるもの、もう1つは波括弧がないものです。実を言えば、波括弧がないものは、波括弧があるものの特殊な形式にすぎません。次に示す、ブロックを使っているラムダ式について考えてみます。 (a, b) -> { return a + b; } ラムダ式の中にあるこのようなブロックは、実際には完全なメソッド本体です(そして、メソッド本体のすべての要件に従わなければなりません)。しかし、このブロックは非常にシンプルで、行っているのは、式を評価してその結果の値を返すことだけです。この場合(具体的に言えば、メソッド本体が式を評価してその結果を返すことしかしていない場合)、波括弧とreturnキーワード、そしてreturnキーワードに関連付けられているセミコロンを省略できます。つまり、右側にあるのは式だけになります。したがって、先ほどの例は次の短縮形と同じになります。 (a, b) -> a + b ただし、少なくともJavaでは、ラムダ式はオブジェクトのインスタンスを表す式です(「内部的には」違う実装である可能性もありますが、ソース・コードから見れば、ラムダ式はあらゆる点でオブジェクトのように振る舞います)。そのため、最初のラムダ式がどのようなものであれ、そのラムダ式はオブジェクトを返します。オブジェクトの型は明らかではありませんが、何らかのインタフェースを実装したオブジェクトです。 それでは、返される値を分解してみます。 a          ->      {} 引数リスト    アロー    メソッド本体 今回のラムダ式は、厳密に1つの引数を受け取り、完全なメソッド本体が波括弧で囲まれている「ブロック・ラムダ」形式であることがわかります。ただし、このメソッド本体は空です。メソッド本体が空のブロックであることを考えれば、このラムダ式の戻りタイプはvoidであるとわかります。 そこから、もう一度式全体を考えてみると、0個の引数を受け取り、その戻り値が、1個の引数を受け取ってvoidを返すラムダ式であるラムダ式ということがわかるでしょう。 外側のラムダ式でもブロック形式を使った場合、式全体がどのように見えるかを考えればわかりやすいかもしれません。 () -> { return a -> {}; } このラムダ式を同様に分解しても、同じ要素が得られます。 ()         ->    { return a -> {}; } 引数リスト    アロー    メソッド本体 ここから、この式は0個の引数を受け取って「何か」を返すラムダ式であることがわかります。 返される「何か」を調べても、先ほどと同じ結果になります。 a          ->      {} 引数リスト    アロー    メソッド本体 先ほどとまったく同じになりました。 最初の問題に戻ります。この複雑でネストされたラムダ式には、どんな種類の宣言を使用できるでしょうか。 最初に、選択肢Aについて考えてみます。このようにvar疑似タイプと変数宣言を組み合わせて使う場合、変数の型が、その変数を初期化するために使う式(この場合はラムダ式)から決定可能であることが必要です。しかし当然ながら、ラムダ式が実装する関数型インタフェース・タイプは、そのラムダ式の代入先によって決まります。言い換えるなら、varが示す型を決めるためには、ラムダ式の型が決まっていなければなりません。しかし、ラムダ式の型を決めるためには、ラムダ式を代入する型がわかっていなければなりません。この場合、その型がvarになっています。このAはBであり、BはAであるという循環依存は論理的に解決できないため、選択肢Aのような形でラムダ式とvar疑似タイプを一緒に使うことはできません。そのため、選択肢Aは誤りであることがわかります。 その他の選択肢では、コア関数型インタフェースをどう組み合わせればラムダ式を正しく記述できるかについて確認することになります。ここで考える必要があるのは、設問にある4つの関数型インタフェースの型要件です。その4つとは、Supplier、Consumer、Function、Predicateです。APIドキュメントを見てみると、それぞれ次の形式の抽象メソッドが宣言されていることがわかります。 public interface Supplier<T> { T get(); } public interface Consumer<T> { void accept(T t); } public interface Function<T, R> { R apply(T t); } public interface Predicate<T> { boolean test(T t); } ラムダ式が有効であるためには、ラムダ式の引数リストと、そのラムダ式が実装しているインタフェースで要求される引数リストとが一致しなければなりません。さらに、ラムダ式の戻りタイプの振る舞いも適切なものでなければなりません(ここで適切というのは、自動変換が可能という意味です)。 思い出しておくと、ここで探しているのは、0個の引数を受け取り、その戻り値が、1個の引数を受け取ってvoidを返すラムダ式であるラムダ式を表す型です。それでは、戻りタイプを考えない場合、0個の引数を受け取るラムダ式に対応しているのは4つのうちのどれでしょうか。この条件に該当するのは、Supplierだけです。ここから、選択肢C、D、Eは誤りであることがわかります。残ったのはもちろん選択肢Bだけですが、解答をやめる前に、この選択肢が完全に正しいかどうかを確認する必要があります。 選択肢Bでは、Supplier<Consumer<?>>を定義しています。ラムダ式は0個の引数を受け取らなければならないと規定されていますが、実際にそのとおりになっています。ここまでは問題ありません。次に、戻りタイプについて考えてみます。選択肢Bでは、戻りタイプがConsumerであるとしています(厳密に言えば、Consumer<?>です。しかしこれは、Objectに代入できる何かのConsumerであると言っているだけであるため、Consumerの引数について何かがわかるわけではありません)。このSupplierの戻り値は(a) -> {}です。このラムダ式は、1つの引数を受け取ってvoidを返します。これはConsumerと一致します。そこから、選択肢Bのコードは完全に正しく、選択肢Bが確かに正解だと判断できます。 正解は選択肢Bです。   Java Magazine 日本版Vol.49の他の記事 Java 14でのJava Flight RecorderとJFR Event Streaming 多くの新機能を搭載したJava 14が登場 Java EEからJakarta EEへ クイズに挑戦:ラムダ式の型(上級者向け) クイズに挑戦:2次元配列(中級者向け) クイズに挑戦:ラムダ式(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 ※本記事は、Simon Roberts、Mikalai Zaikinによる”Quiz Yourself: Lambda Types (Advanced)“を翻訳したものです。

ラムダ式はラムダ式を返せるのか 著者:Simon Roberts、Mikalai Zaikin 2020年2月27日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほと...

Java Magazine

Java EEからJakarta EEへ

※本記事は、Arjan Tijmsによる"Transition from Java EE to Jakarta EE"を翻訳したものです。 何が起きたのか、何を知っておくべきか   著者:Arjan Tijms 2020年2月27日   Java EEは、間違いなく、サーバー・サイドJavaで特によく知られたフレームワークの1つです。実質的に、業界がJavaをサーバーで使用するという方向に勢いをつけたのはJava EEです。Java EEは、Javaがまさに始動したばかりである1996年のKiva Enterprise Server(GlassFish)やTengahアプリケーション・サーバー(Oracle WebLogic Serverの前身)にまでさかのぼります。なお、ここで使われているTengahという言葉は、インドネシアのジャワ島の中部にある行政区域を指しています。 Java EEは、かつてJ2EE(Java 2 Enterprise Edition)と呼ばれていました。その認知度をもっとも高めているのは、Java Servlet仕様と、この仕様を実装するサーバー(TomcatやJettyなど)でしょう。多くの場合、このようなサーバーはサーブレット・コンテナと呼ばれます。代わりとなる選択肢もありますが、多くのサーバー・アプリケーションやサードパーティ・フレームワークがJava Servlet仕様に基づいています。その後のJava EEは、この仕様に加えて、永続化(Java Persistence API(JPA)、大部分はHibernate経由)、REST(JAX-RS)、WebSocketの仕様、さらにトランザクション(Java Transaction API(JTA)、大部分はJPAで内部的に使用)、検証(Bean Validation)、JSON(JSON-PおよびJSON-B)といった多数の細かな仕様群で知られるようになりました。 実際には、Java EEアプリケーションに分類されることは考えにくいアプリケーションでも、さまざまなJava EE APIが使われている場合があります。 アプリケーション・サーバーでは、以前からJava EEの完全な実装が使われていますが、こちらも大きな成功を収めています。JBoss/WildFly、GlassFish/Payara、そして直近のOpen Liberty(WebSphereの後継)はすべてよく知られています。 さらに、アプリケーション・サーバーでもサーブレット・コンテナでもないにもかかわらず、さまざまなJava EE APIをすぐに使用できる製品群もあります。たとえば、Quarkus(Contexts and Dependency Injection(CDI)、JAX-RS、JPA)、Helidon(CDI、JAX-RS、JPA、JTA)、KumuluzEE(CDI、JAX-RS、JPA、Servlet、JavaServer Faces (JSF)、WebSocket、Bean Validation、JSON-P)、Piranha(CDI、JAX-RS、Java EE Security、Expression Language(EL))です。 最後に、MicroProfileと呼ばれる、Java EEから派生したプラットフォームもあります。MicroProfileは、CDI、JAX-RS、JSONなどのJava EE APIに直接的に依存しています。これらすべてによって、Java EE APIは多くのユーザーにとって欠かせないものとなっています。 Java EEに何が起こっているのか 厳密な意味でのJava EEの最新リリースは、2017年8月のJava EE 8でした。このリリースは範囲が限定されたものでしたが、Java EE Securityなどの重要な機能も含まれていました。その年のうちに、オラクルはJava EEをオープンソース団体に完全に移管することを決めました。そして、Java EEパートナーのRed HatとIBMの協力のもと、完全なリファレンス実装およびTechnology Compatibility Kit(TCK)とともに、Java EEはEclipse Foundationに移管されることに決まりました。 この移管に伴って大量の作業が発生することから、プロセスは3つのステージに分割されました。 ステージ1:APIおよび実装コードの移管、検証済みビルドのリリース。最初のステージは、Eclipse Enterprise for Java(EE4J)と呼ばれるトップレベルの新プロジェクトをEclipseに作ることでした。EE4Jプロジェクトと、このプロジェクトに関連するGitHubの組織eclipse-ee4jzは、仕様プロジェクトと実装プロジェクトの両方のホームです。EE4Jを、Java EEの新しいブランド名であるJakarta EEと混同しないでください。Jakarta EEは、この数か月後にコミュニティが選んだ名前です。 github.com/javaeeにあるオラクルのリポジトリからすべての既存ソース・コードを実際に移管する前に、すべてのコードについて法的問題を解消する必要がありました。これは何より、問題となる可能性がある部分を削除しなければならないということを意味しました。数百万行ものコードを調べるという作業が、簡単なものでないことは明らかでした。過去のコードすべてについても同様に法的問題を解消するということは不可能でした。そのため、まず特筆すべきなのは、リリースされた最新バージョンのコードのみが移管されたということです。たとえば、JSF 2.3は、マスター・ブランチからのスナップショットとして移管されました。JSF 2.2およびそれ以前のバージョンは元々の場所に置かれたままで、Eclipse Foundationによるメンテナンスやサポートは行われていません。 ソース・コードの移管後に、Eclipseのビルド・サーバーを使ってすべてのコードがビルドされ、その結果がMavenリポジトリにステージングされました。APIのJARファイルは、Eclipseが作成したビルド・アーティファクトであることを示すため、MavenのグループIDがjavax.*からjakarta.*に変更されました。そこからGlassFishの新しいビルドが生成され、そのビルドに対してオリジナルのJava EE 8 TCKが実行されました。このビルドがTCKテストに合格し、すべてのコードの移管が成功したことが証明された後に、GlassFish 5.1としてリリースされました。 ところで、グループIDがJakartaになって最初のリリースのAPIは、Jakarta EE 8認定ではなく、Java EE 8認定です。たとえば、jakarta.faces:jakarta.faces-api:2.3.1はjavax.faces:javax.faces-api:2.3と同じもので、両方ともJava EE 8認定です。しかし、前者はgithub.com/eclipse-ee4jからビルドされ、後者はgithub.com/javaeeからビルドされています。 ステージ2:TCKコードの移管、新しい仕様プロセスの立ち上げ、新しい用語の定義、新ブランド版ビルドのリリース。第2ステージは、TCKを移管し、そこからJakarta EE 8認定の新しいバイナリをビルドすることでした。新しい認定プロセスであるJakarta EE Specification Process(JESP)も立ち上がりました。また、新しい仕様ライセンスであるEclipse Foundation Technology Compatibility Kitライセンスも作成されました。 このステージでは、シンプルで一貫性を高めた新しい名前がすべての仕様に付けられました。新しい名前は、すべてJakartaで始まり、簡単に仕様を説明した言葉が続きます。一貫性のないつなぎの言葉(アーキテクチャ、API、サービスなど)は入らないようになっています。 新旧の用語を表1に示します。 表1:古いJava EE 8の用語と新しいJakarta EE 8の用語の比較 このステージでは、新しい用語を反映させるために、すべてのAPIのJavadocが更新されました。また、生成されたAPI JARファイルのライセンスも新しくなり、移管されたTCKソース・コードからビルドした新しいブランドのTCKを使ってGlassFish 5.1でテストが行われました。これはすべて、新しいJESP仕様のプロセスに従ったものです。 生成されたすべてのAPI JARファイルは、ほぼ空のプレースホルダ仕様ドキュメントとともにリリースされました。このファイルとドキュメントを組み合わせたものが、Jakarta EE 8を構成しています。 個々のJARファイルでは、厳密に言って同じAPIが、このステージで3回目のリリースを迎えたことになります。MavenのグループIDにJakartaを使用した2回目のリリースであり、Jakarta EE認定となった初めてのリリースです。表2にJSF/Jakarta Facesの例を示します。   表2:JSFおよびJakarta FacesのJARファイルの例(画像を拡大) ここで注目すべき点があと2つあります。 1つ目は、Jakarta EE 8には対応するGlassFishリリースがなかったことです。ただし、GlassFish 5.1は既存のJava EE 8認定であることに加え、Jakarta EE 8認定でもありました。 2つ目は、前述のように、Jakarta EE 8は実質的に空の仕様ドキュメントとともにリリースされたことです。仕様ドキュメントの法的問題を解消して移管するために必要な時間が膨大で、Jakarta EE 8のリリースには到底間に合わなかったというのがその理由です。現在のところ、このテクノロジーのユーザー(実装者以外)は、対応するJava EE 8ドキュメントの評価版を読むことができます。 Jakarta EEバージョンのAPIへの更新は、今後予定されている大きな変更に備えてユーザーが踏み出せる小さな第一歩です。Mavenプロジェクトでは、この更新を非常に簡単に行うことができます。次の記述を、 <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency> 次のように置き換えます。 <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> <version>8.0.0</version> <scope>provided</scope> </dependency> または、個々の依存性を使用している場合、次のような記述を <dependency> <groupId>javax.faces</groupId> <artifactId>javax.faces-api</artifactId> <version>2.3</version> <scope>provided</scope> </dependency> 次のように置き換えます。 <dependency> <groupId>jakarta.faces</groupId> <artifactId>jakarta.faces-api</artifactId> <version>2.3.2</version> <scope>provided</scope> </dependency> APIは本質的に同じであるため、この更新を行ってもほとんど問題はないはずです。ただし、Mavenでは、一方が他方より新しいと2つの依存性を関連付けることはしないという点に注意してください。 Mavenにとっては、まったく異なる2つの依存性が存在することになります。そのため、Mavenは関連性を何も知らずにその両方をインクルードします。たとえば、トップレベルの依存性が推移的にJava EE依存性を取り込むような場合、二重インクルードが起きるかもしれません。Jakartaに更新する前であれば、推移的に導入されるjavax.faces:javax.faces-api:2.2はトップレベルのjavax.faces:javax.faces-api:2.3などで上書きされるでしょう。 トップレベルの依存性がjakarta.faces:jakarta.faces-api:2.3.2に変更された場合、2.2の依存性は上書きされなくなり、Mavenは両方を使用することになるため、さまざまな問題につながります。推移的なインクルードを更新できない場合、通常は除外を使用してこの問題を修正することができます。たとえば、次のようにします。 <dependency> <groupId>com.example</groupId> <artifactId>foo</artifactId> <scope>provided</scope> <exclusions> <exclusion> <groupId>javax.faces</groupId> <artifactId>javax.faces-api</artifactId> </exclusion> </exclusions> </dependency>   これで、プロセスの最後のステップにたどり着きました。 ステージ3:仕様ドキュメントの移管および更新、余分な仕様の削除、APIパッケージ名の変更、JDK 11のリリース。現在は移管の最後のステップにあり、今年中に終わる予定となっています。このステージには、仕様ドキュメントのソース・コード(ほとんどがAsciiDoc)の移管が含まれています。仕様ドキュメントのソース・コードは、APIコード、実装コード、TCKコードに続いて移管されることになる、最後のアーティファクトです。APIのJavadocと同じように、仕様ドキュメントも新しい用語を使って更新される予定です。 しかし、このステージでもっとも影響の大きい項目は、すべてのJava APIパッケージ名をjavax.*からjakarta.*に変更することです。たとえば、javax.faces.context.FacesContextはjakarta.faces.context.FacesContextになります。このパッケージ名変更は、結果として既存アプリケーションのコードの更新が必要になるため、重大なアップデートです。 Java EE 8のリリースからかなりの時間が経過していることを考慮すれば、Jakarta EE 9ではJDK 11の公式サポートが必要でしょう。しかし、JDK 8は今でも重要性が高いため、APIはJDK 8のままです。現実的には、APIはJDK 8のソース・コード・レベルでコンパイルできる必要があるものの、実装はJDK 11で実行するTCKに合格しなければならないという意味になります。 JDK 11では、それまでにJava EEからJava SEに移動したいくつかの仕様が削除されています。そのため、今度はそれらの仕様を復帰させることになります。Jakarta Activationは必須仕様(具体的には、Jakarta Mailに必須の依存性であるため)としてJakarta EEに追加されていますが、Jakarta XML Binding、Jakarta XML Web Services、Web Services Metadata、SOAP with Attachmentsは、オプション仕様として追加されています。 Jakarta Enterprise Beans(旧称EJB)は、サイズが再度削減される予定です。EJB 3.1では、エンティティBean(EJB Query Languageを含む)とJava API for XML-based RPC(JAX-RPC)エンドポイントが削除されました。そして今回は、EJB 2.1 APIグループ(javax.ejb.EJBHomeなど)と、いわゆる分散相互運用性が削除されます。 さらに、Deployment仕様(JSR 88)とManagement仕様(JSR 77)も削除されます。JSR 88はJava EE 8ですでにオプションでした。JSR 77は、かつてメジャー・アップデートが予定されていましたが、実現しませんでした。JAX-RPCは、かなり以前にJAX-WSに取って代わられ、Java EE 8ですでにオプションとなっていました。今回はこのJAX-RPCもついに削除されることになります。さらに、同様にJava EE 8ですでにオプションになっていたXML Registriesも削除されます。   表3は、Jakarta EE 9で再度更新されるJSF/Jakarta Facesの例を示しています。Java EE 8に関連する変更は太字になっています。最後の行は不確定なものであり、まだ変更される可能性があります(ただし、その可能性は低いはずです)。 表3:Jakarta EE 9で更新されるJSF/Jakarta Facesの例(画像を拡大) まとめ Jakarta EE 9のリリースと、おそらくはすべての仕様ドキュメントの移管および更新が完了したら、Java EE 8の移管が完了したと見なされます。その時点で、Java EEに関連するすべてのものがEclipse Foundationに移管され、新しいブランドに更新されていることになります。 機能的に見れば、Jakarta EE 9はまだ本質的にJava EE 8と同じです。そのため、純粋に機能的な見方をすれば、Jakarta EE 8にもJakarta EE 9にもユーザーが更新したくなるような特段の魅力はありません。しかし、これらのリリースの目的は、コミュニティやエコシステム(たとえば、ツールやライブラリのベンダー)にアプリケーションや製品の準備をする機会を与えることにあります。新機能が登場する最初のバージョンは、Jakarta EE 10になるでしょう。表4に、リリースとその日程を示します(*印が付いているのは暫定的な予定です)。   表4:リリースとその日程   Raoul-Gabriel Urma Arjan Tijms:JSF(JSR 372)およびSecurity API(JSR 375)で専門家グループのメンバーを務めた。現在はJakarta Security、Jakarta Authentication、Jakarta Authorization、Jakarta Faces、Jakarta Expression Languageなど、多くのJakartaプロジェクトでプロジェクト・リードを担当。2015年のDuke's Choice Awardを受賞した、JSF用の人気ライブラリOmniFacesの共同作成者で、2冊の書籍『The Definitive Guide to JSF』と『Pro CDI 2 in Java EE 8』の著者でもある。オランダのライデン大学でコンピュータ・サイエンスの理学修士号を取得している。Twitterのフォローは@arjan_tijmsから。        

※本記事は、Arjan Tijmsによる"Transition from Java EE to Jakarta EE"を翻訳したものです。 何が起きたのか、何を知っておくべきか   著者:Arjan Tijms 2020年2月27日   Java EEは、間違いなく、サーバー・サイドJavaで特によく知られたフレームワークの1つです。実質的に、業界がJavaをサーバーで使用するという方向に勢いをつけたのはJav...

Java Magazine

多くの新機能を搭載したJava 14が登場

※本記事は、Raoul-Gabriel Urmaによる"Java 14 Arrives with a Host of New Features"を翻訳したものです。 これまでの2回のリリースを超える新機能が搭載されたJava 14は、コーディングを簡単にする新機能が満載   著者:Raoul-Gabriel Urma 2020年2月27日   Java 14は、3月17日にリリースされる予定です。バージョン14には、Java 12と13を合わせたものよりも多くのJEP(Java拡張提案)が搭載されています。それでは、その内容はどのようなもので、日々コードを書き、そのメンテナンスを行っているJava開発者にとってもっとも関連があるものは何でしょうか。 本記事では、以下に示す大きな進展について説明します。 Java 12でプレビュー機能として初登場し、Java 13を経て改善され、Java 14で正式機能となったswitch式 instanceofのパターン・マッチング(言語機能) 便利なNullPointerException(JVM機能) 本記事を読んで、これらの機能を自分のコードベースで試してみる場合は、Javaチームにフィードバックを提供し、経験を共有することをお勧めします。そうすることで、Javaの発展に貢献する機会が得られます。 switch式 Java 14で、switch式は恒久版になりました。switch式がどんなものであるかを忘れてしまった方は、以前の2つの特集記事をお読みください。 以前のリリースでは、switch式は「プレビュー」機能でした。ご存じの方もいらっしゃると思いますが、「プレビュー」機能はフィードバックを集めるためのもので、その内容によって変更されることや、場合によっては削除される可能性があるものです。しかし、「プレビュー」機能のほとんどはやがて正式にJavaの一部になることが想定されています。 新しいswitch式のメリットには、フォールスルー動作がないためバグが発生しにくい、網羅性がある、式と複合条件のおかげで書きやすくなっている、などがあります。switch式では、アロー構文が使えるようになっています。次の例を見て思い出してください。 var log = switch (event) { case PLAY -> "User has triggered the play button"; case STOP, PAUSE -> "User needs a break"; default -> { String message = event.toString(); LocalDateTime now = LocalDateTime.now(); yield "Unknown event " + message + " logged on " + now; } }; テキスト・ブロック Java 13では、プレビュー機能としてテキスト・ブロックが導入されました。テキスト・ブロックを使うことで、複数行文字列リテラルを扱いやすくなります。この機能は、Java 14でプレビューの第2ラウンドを迎えることになり、いくつかの微調整が行われています。少しばかり復習しておきますが、しかるべき複数行テキストを表現する場合、多くの文字列結合とエスケープ・シーケンスを使用してコードを書くのが至って普通でした。以下のコードは、HTML形式のテキストを組み立てる例です。 String html = "<HTML>" + "\n\t" + "<BODY>" + "\n\t\t" + "<H1>\"Java 14 is here!\"</H1>" + "\n\t" + "</BODY>" + "\n" + "</HTML>"; テキスト・ブロックにより、この作業を簡略化し、テキスト・ブロックの最初と最後を表す3つの二重引用符を使って美しいコードを書くことができます。 String html = """ <HTML> <BODY> <H1>"Java 14 is here!"</H1> </BODY> </HTML>"""; テキスト・ブロックは、通常の文字列リテラルと比較して、表現力もかなり向上しています。この点の詳細については、以前の記事をご覧ください。 Java 14では、2つの新しいエスケープ・シーケンスが追加されました。1つ目として、単一の空白を示す新しいエスケープ・シーケンス\sを使用できます。2つ目に、行末に改行文字が挿入されるのを防ぐための方法として、バックスラッシュ\を使用できます。このバックスラッシュは、非常に長い行があり、テキスト・ブロック内部の可読性を向上させるために分割したい場合に便利です。 例として、複数行文字列を扱う、現在の方法を示します。 String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " + "elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua."; テキスト・ブロックにエスケープ・シーケンス\を含めることで、上記の内容を次のように表現できます。 String text = """ Lorem ipsum dolor sit amet, consectetur adipiscing \ elit, sed do eiusmod tempor incididunt ut labore \ et dolore magna aliqua.\ """; instanceofのパターン・マッチング Java 14には、プレビュー機能として、instanceofによる条件チェック後に明示的キャストを行わなくてよくなる機能が導入されています。たとえば、次のコードについて考えてみます。 if (obj instanceof Group) { Group group = (Group) obj; // use group specific methods var entries = group.getEntries(); } 今回のプレビュー機能を使用して、上記のコードを次のようにリファクタリングすることができます。 if (obj instanceof Group group) { var entries = group.getEntries(); } 条件チェックによってobjがGroup型であることが明らかになるため、最初のスニペットの条件ブロックのように、objがGroup型であると再度宣言する必要はありません。この宣言は、エラーが紛れ込む可能性を高めるものです。。 このように短くなった構文では、一般的なJavaプログラムに含まれる多くのキャストが除去されます(ある関連言語機能を提案した2011年の研究論文では、すべてのキャストのおよそ24 %が、条件文に含まれるinstanceofより後に登場していることが報告されました)。 この変更を扱っているJEP 305では、Joshua Bloch氏の書籍『Effective Java』に掲載されている例に注目しており、等価性を評価する次のメソッドを使用して説明されています。 @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString) && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } 上記のコードは、CaseInsensitiveStringへの冗長な明示的キャストを削除して、次のような形に縮小することができます。 @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString cis) && cis.s.equalsIgnoreCase(s); } これによってさらに汎用的なパターン・マッチングへの扉が開くことから、興味深く実験できるプレビュー機能です。パターン・マッチングの考え方は、一定の条件に基づいてオブジェクトのコンポーネントを抽出する便利な構文を持つ言語機能を提供するというものです。これはinstanceof演算子にも当てはまります。パターンマッチの条件が型チェックであり、抽出は、しかるべきメソッドの呼出しまたは固有のフィールドへのアクセスであるからです。 すなわち、このプレビュー機能は始まりにすぎません。さらに冗長性を減らすことでバグ発生の可能性を低下させる言語機能に期待できるということです。 レコード もう1つのプレビュー言語機能がレコードです。これまでに浮上してきた他の考え方と同じように、この機能も、Javaの冗長性を減らして開発者がより簡潔なコードを書けるようにするというトレンドに従っています。レコードは、フィールドにデータを保存することだけを目的とし、カスタム動作は一切宣言していないドメイン・クラスを対象にしています。 簡単なドメイン・クラスBankTransactionを例に、直接問題に踏み込んでみることにします。このクラスは、date(日付)、amount(金額)、description(説明)という3つのフィールドで取引をモデリングしたものです。クラスを宣言するときは、複数のコンポーネントについて考える必要があります。 コンストラクタ getterメソッド toString() hashCode()とequals() 多くの場合、このようなコンポーネントのコードはIDEで自動的に生成され、そのコードで多くの領域が占められます。生成されたBankTransactionクラスのすべての実装を示します。 public class BankTransaction { private final LocalDate date; private final double amount; private final String description; public BankTransaction(final LocalDate date, final double amount, final String description) { this.date = date; this.amount = amount; this.description = description; } public LocalDate date() { return date; } public double amount() { return amount; } public String description() { return description; } @Override public String toString() { return "BankTransaction{" + "date=" + date + ", amount=" + amount + ", description='" + description + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BankTransaction that = (BankTransaction) o; return Double.compare(that.amount, amount) == 0 && date.equals(that.date) && description.equals(that.description); } @Override public int hashCode() { return Objects.hash(date, amount, description); } } Java 14では、冗長性を省き、必要なのはデータを集めるためだけのクラス(equals、hashCode、toStringの各メソッドの実装を含むもの)であるという意図を明確にする方法が提供されます。BankTransactionは、次のようにリファクタリングすることができます。 public record BankTransaction(LocalDate date, double amount, String description) {} レコードを使うことで、コンストラクタとgetterの実装に加え、equals、hashCode、およびtoStringの実装が「自動的」に生成されます。 この例を試す際は、プレビュー・フラグを使ってファイルをコンパイルする必要があることを思い出してください。 javac --enable-preview --release 14 BankTransaction.java レコードのフィールドは、暗黙的にfinalになります。つまり、再代入はできません。ただし、レコード全体が不変という意味ではない点に注意してください。フィールドに格納するオブジェクト自体は可変でも構いません。 レコードについてさらに詳しく知りたい方は、Ben Evans氏が最近執筆したJava Magazineの記事をご覧ください。 この機能には期待できます。レコードは、次世代のJava開発者に対する教育的観点から、興味深い質問をもたらすものでもあります。たとえば、若い開発者を指導する場合、レコードはカリキュラムのどこで説明するべきでしょうか。OOPやクラスを紹介する前がよいでしょうか。それとも、その後がよいでしょうか。 便利なNullPointerException NullPointerExceptionのスローを、Javaの新たな「Hello world」にすべきだという意見があります。。この例外から逃れることはできないからです。冗談はさておき、この例外はコードが本番環境で実行されているときにアプリケーションのログにたびたび現れては、フラストレーションを引き起こします。元々のコードをすぐに見ることはできないため、デバッグが難しい場合もあります。たとえば、次のコードがソースの5行目にある場合を考えてみます。 var name = user.getLocation().getCity().getName(); Java 14より前の場合、次のようなエラーが発生することがあります。 Exception in thread "main" java.lang.NullPointerException at NullPointerExample.main(NullPointerExample.java:5) 残念ながら、5行目を見ても、複数のメソッド呼出し、すなわちgetLocation()とgetCity()が割り当てられています。そのいずれかがNULLを返している可能性があります。実際には、変数userがNULLである可能性もあります。つまり、何がNullPointerExceptionを引き起こしているかは明確ではありません。 Java 14には、詳細な診断を受け取ることができる新しいJVM機能が追加されました。 Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Location.getCity()" because the return value of "User.getLocation()" is null at NullPointerExample.main(NullPointerExample.java:5) JVMのメッセージに、2つの明確な要素が含まれるようになりました。 結果:Location.getCity()を呼び出すことができない 理由:User.getLocation()の戻り値がNULLである この拡張診断は、次のフラグを使ってJavaを起動した場合にのみ有効になります。 -XX:+ShowCodeDetailsInExceptionMessages 次に例を示します。 java -XX:+ShowCodeDetailsInExceptionMessages NullPointerExample こちらに記されているように、将来のバージョンのJavaではこの機能がデフォルトで有効になる可能性があります。 この機能拡張は、メソッド呼出しに対してだけ有効になるわけではなく、NullPointerExceptionが発生する可能性のあるその他の場所(フィールドへのアクセス、配列へのアクセス、代入など)でも動作します。 まとめ Java 14には、開発者の日々の仕事に役立つ新しいプレビュー言語機能やアップデートが搭載されています。たとえば、Java 14では、明示的キャストを減らすための方法であるinstanceofパターン・マッチングが導入されています。さらに、データを集めるだけのクラスを簡潔に宣言する新しい構造であるレコードも導入されています。加えて、NullPointerExceptionのメッセージが拡張されて詳しい診断情報が含まれるようになり、switch式が正式にJava 14の一部になっています。複数行文字列値を扱う際に役立つテキスト・ブロック機能は、2つの新しいエスケープ・シーケンスが導入されたうえで、プレビューの第2ラウンドに入っています。他の変更点の中で、Javaを運用する技術者の一部にとって興味深いものが、JDK Flight Recorderのイベント・ストリーミングです。このオプションは、Ben Evans氏によるこちらの記事で説明されています。 おわかりのように、Java 14にはイノベーションが満載されています。ぜひJava 14を試し、プレビュー機能に関するフィードバックをJavaチームにお寄せください。 Raoul-Gabriel Urma Raoul-Gabriel Urma(@raoulUK):イギリスのデータ・サイエンティストや開発者の学習コミュニティをリードするCambridge SparkのCEO/共同創業者。若いプログラマーや学生のコミュニティであるCambridge Coding Academyの会長/共同創設者でもある。ベストセラーとなったプログラミング関連書籍『Java 8 in Action』(Manning Publications、2015年)の共著者として執筆に携わった。ケンブリッジ大学でコンピュータ・サイエンスの博士号を取得している。

※本記事は、Raoul-Gabriel Urmaによる"Java 14 Arrives with a Host of New Features"を翻訳したものです。 これまでの2回のリリースを超える新機能が搭載されたJava 14は、コーディングを簡単にする新機能が満載   著者:Raoul-Gabriel Urma 2020年2月27日   Java...

Java Magazine

Java 14でのJava Flight RecorderとJFR Event Streaming

※本記事は、Ben Evansによる"Java Flight Recorder and JFR Event Streaming in Java 14"を翻訳したものです。 実行中のアプリに関する大量のデータ・ポイントからなるストリームを取得する   著者:Ben Evans 2020年2月27日   本記事では、Java 14に導入される新機能について説明します。この機能はJFR Event Streaming(JEP 349)と呼ばれており、長い歴史を持つ成熟したプロファイリングおよび監視テクノロジー群の最新版です。 元々のJava Flight Recorder(JFR)ツールとJava Mission Control(JMC)ツールは、2008年にオラクルがBEA Systemsを買収したときに、その一環として獲得したものでした。この2つのコンポーネントは連携して動作します。JFRは低オーバーヘッドのイベントベース・プロファイリング・エンジンで、バイナリ形式でイベントを書き込む高パフォーマンス・バックエンドが搭載されています。一方のJMCは、JVMの遠隔測定機能を使ってJFRが作成したデータファイルを調査するGUIツールです。 元々、これらのツールはBEAのJRockit JVM向けツール製品の一部でしたが、JRockitがJava HotSpot VMに統合される過程で、Oracle JDKの商用バージョンに含まれることになりました。JDK 9のリリース後、オラクルはJavaのリリース・モデルを変更し、JFRとJMCがオープンソース・ツールになることを発表しました。 JFRはOpenJDKに寄贈され、JEP 328としてJDK 11で提供されました。JMCは独立したオープンソース・プロジェクトとなり、現在は別個のダウンロードとして存在しています。 Java 14の到来とともにJFRに新機能が導入され、連続したイベント・ストリームを生成できるようになっています。この変更により、事後にファイルを解析するのではなく、イベントを即時処理するAPIも提供されるようになっています。また、この変更によって、JFR Event Streamingは監視ツールや観測ツールを構築するまたとない土台となります。 ただし、1つ問題があります。JFRとJMCがオープンソース・ツールになったのはごく最近であるため、多くのJava開発者はその高機能さを知らないということです。そこで、Java 14での新機能の紹介に入る前に、JMCとJFRについて最初から説明することにします。 JFRの概要 JFRが初めてオープンソースとして利用できるようになったのは、OpenJDK 11でのことです。そのため、OpenJDK 11(以降)を実行しているか、またはオラクルの顧客として商用JDKを実行している必要があります。 JFRの記録はさまざまな方法で作成できますが、ここでは、JVMの起動時にコマンドライン引数を使用する方法と、jcmdを使用する方法の2つの方法を取り上げます。 まずは、JFRを起動するために必要なコマンドライン・スイッチを確認します。次に示すのは、重要なスイッチです。 -XX:StartFlightRecording:<options> このスイッチを指定することで、プロセス開始時にJFRの記録が有効になります。Java 14より前のJFRでは、プロファイリング・データのファイルを生成していました。このデータは、1つのダンプ・ファイルにすることも、連続したリング・バッファにすることも可能でした。データをどのようにキャプチャするかは、多数のコマンドライン・オプションで制御します。 また、JFRでは100を超えるメトリックをキャプチャすることができます。ほとんどのメトリックは非常に軽微な影響しか与えませんが、一部には顕著なオーバーヘッドを招くものもあります。それらのメトリックの構成を個々に管理した場合、膨大な作業になってしまいます。そこで、この作業を簡単に行うために、JFRではプロファイリング構成ファイルを使用するようになっています。この構成ファイルは、各メトリックの構成を含む単純なXMLファイルで、それぞれのメトリックをキャプチャするかどうかを指定します。標準のJDKダウンロードには、default.jfcおよびprofile.jfcという2つの基本的なファイルが含まれています。 default.jfcによるデフォルト・レベルの記録は、オーバーヘッドが非常に小さくなるように設計されており、基本的にすべての本番環境のJavaプロセスで使用できるようになっています。profile.jfc構成を使うことで、さらに詳しい情報を取得できますが、当然ながらランタイム・コストは高くなります。 提供されている2つのファイルを使用する以外に、必要なデータ・ポイントだけを含むカスタム構成ファイルを作成することも可能です。JMCツール(次のセクションで説明します)には、このファイルを簡単に作成できるテンプレート・マネージャが搭載されています。 コマンドラインで渡すことができる他のオプションとして、記録されたデータを格納するファイルの名前や、保持するデータの量(データ・ポイント取得からの経過時間で指定します)などがあります。次に示すのは、JFRのコマンドライン全体の例です。 -XX:StartFlightRecording:disk=true,filename=/sandbox/service.jfr,maxage=12h,settings=profile これにより、詳細なプロファイリング情報を含む、12時間のローリング・バッファが作成されます。このファイルがどこまで大きくなれるかについては、何の指定もしていません。 注:JFRが商用ビルドの一部だったときは、-XX:+UnlockCommercialFeaturesスイッチでアンロックされました。しかし、Oracle JDK 11以降のバージョンでこのオプションを使った場合は、警告が発生します。警告が表示されるのは、商用機能のすべてがオープンソース化されているからです。また、このフラグはOpenJDKには当初から存在しないため、使い続ける意味はありません。OpenJDKビルドでこの商用機能フラグを使った場合は、エラーになります。 JFRの優れた機能の1つとして、JVMの起動時に構成する必要がないという点が挙げられます。JFRは、jcmdプログラムを使ってコマンドラインから起動し、実行中のJVMをUNIXコマンドラインから制御することもできます。次に例を示します。   $ jcmd <pid> JFR.start name=Recording1 settings=default $ jcmd <pid> JFR.dump filename=recording.jfr $ jcmd <pid> JFR.stop それだけでなく、アプリケーションの起動後にJFRにアタッチすることもできます。JMC(次のセクションを参照)には、ローカル・マシンで実行されているJVM内のJFRを動的に制御する機能があります。 JFRをどのように起動しても結果は同じで、1つのJVMに対する1回のプロファイリング実行につき、1つのファイルになります。このファイルには大量のバイナリ・データが含まれており、人間が読むことはできません。そのため、データを抽出して視覚化する何らかのツールが必要になります。その1つがJMCです。   JMC 7の概要 JMCのグラフィカル・ツールを使って、JFR出力ファイルに含まれるデータを表示してみます。このツールは、jmcコマンドで起動します。以前のJMCはOracle JDKダウンロードにバンドルされていましたが、現在は別個に公開されています。 図1にJMCの起動画面を示します。ファイルをロードすると、JMCは自動分析を行い、実行記録に含まれる明らかな問題を特定します。   図1:JMCの起動画面 注:プロファイリングを行うためには、対象のアプリケーションでJFRが有効化されていなければなりません。JMCでは、以前に作成したファイルを使うことに加え、左上のパネルの左側にあるJVM Browserというタブからローカル・アプリケーションに動的にアタッチすることもできます。 JMCで最初に見かける画面の1つに、遠隔測定の概要を示した画面(図2)があります。ここには、JVMの全体的な健全性を示す、概要レベルのダッシュボードが表示されます。   図2:最初の遠隔測定ダッシュボード 主要なJVMサブシステムには、すべて専用の画面が割り当てられており、詳しい分析を行うことができます。たとえば、ガベージ・コレクションの概要画面(図3)には、JFRファイルの対象期間全体のガベージ・コレクション・イベントが表示されます。タイムラインで異常に長いガベージ・コレクション・イベントが発生している場合、「Longest Pause」という表示からその場所を確認できます。   図3:ガベージ・コレクションの概要 詳細プロファイリング構成では、新しい割当てバッファ(TLAB)がアプリケーション・スレッドに渡されたことを示す個々のイベントを確認することもできます(図4)。これにより、プロセス内のリソースをはるかに正確に確認できるようになります。   図4:TLAB割当て TLAB割当てからは、どのスレッドに一番多くメモリを割り当てているかを簡単に確認できます。図4では、Apache Kafkaトピックのデータを使用しているスレッドです。 他の主要なJVMサブシステムに、JITコンパイラがあります。図5からわかるように、JMCを使ってコンパイラの詳細な動作を確認することができます。   図5:コンパイラの観測 JITコンパイラのコード・キャッシュの利用可能メモリは重要なリソースです。ここには、コンパイルされたメソッドが格納されます。コンパイルされたメソッドが大量に含まれるプロセスでは、このメモリ領域が枯渇し、プロセスがピーク・パフォーマンスに到達しない可能性があります。図6に、このキャッシュに関連するデータを示します。   図6:コード・キャッシュの監視 JMCには、メソッドレベルのプロファイラ(図7)も含まれています。このプロファイラの動作は、VisualVMのプロファイラや、商用ツール(JProfilerやYourKitなど)とよく似ています。   図7:プロファイリング・ペイン JMCの高度な画面の1つに、VM Operationsビュー(図8)があります。ここには、JVMが実行する内部オペレーションの一部と、それらのオペレーションにかかる時間が表示されます。すべての分析に必要になると考えられるビューではありませんが、特定の種類の問題を検出する際にはとても有効です。   図8:JVMオペレーションの表示 たとえば、ここで視覚化されている重要なデータ・ポイントの1つが、バイアス・ロックの取り消し時間です。ほとんどのプログラムには、JVMのバイアス・ロック・テクノロジー(大まかに言えば、オブジェクトをロックした最初のスレッドが、そのオブジェクトをロックする唯一のスレッドとなる可能性が高いという考え方)が有効です。しかし、一部のワークロード(Apache Cassandraなど)は、バイアス・ロックが悪影響を与えるような設計になっています。VM Operationsビューでは、そのようなケースをとても簡単に見つけることができるでしょう。 プログラムでJFRファイルを解析する JMCの重要なユースケースは、1つのJVMから作成されたファイルをアプリケーションのプロファイリングや開発のために調べることです。しかし、サービスベースの環境やエンタープライズ環境では、GUIを使ってファイルを1つずつ調査するというモデルがもっとも便利なアプローチであるとは限りません。 それよりも、JFRでキャプチャした生データを解析できるツールを作成し、もっと便利な方法でデータにアクセスして視覚化できるようにしたいと思う方もいるでしょう。ありがたいことに、プログラムからJFRイベントにアクセスできるAPIは、とても簡単に使い始めることができます。 重要なクラスは、jdk.jfr.consumer.RecordingFileとjdk.jfr.consumer.RecordedEventです。RecordingFileクラスにはRecordedEventクラスのインスタンスが含まれています。イベントをただ順番に調べるのは簡単で、次のような単純なJavaコードで実現できます。 var recordingFile = new RecordingFile(Paths.get(fileName)); while (recordingFile.hasMoreEvents()) { var event = recordingFile.readEvent(); if (event != null) { var details = decodeEvent(event); if (details == null) { // Log a failure to recognize details } else { // Process details System.out.println(details); } } } 各イベントには、微妙に異なるデータが含まれている可能性があります。そのため、どのような型のデータが記録されているかを考慮したデコード処理が必要になります。この例では、簡略化するため、受け取ったRecordedEventを単純な、文字列のマップに変換しています。 public Map<String, String> decodeEvent(final RecordedEvent e) { for (var ent : mappers.entrySet()) { if (ent.getKey().test(e)) { return ent.getValue().apply(e); } } return null; }      鍵となるのはデコーダの処理です。このシグネチャは少しばかり複雑です。 private static Predicate<RecordedEvent> testMaker(String s) { return e -> e.getEventType().getName().startsWith(s); } private static final Map<Predicate<RecordedEvent>, Function<RecordedEvent, Map<String, String>>> mappers = Map.of(testMaker("jdk.ObjectAllocationInNewTLAB"), ev -> Map.of("start", ""+ ev.getStartTime(), "end", ""+ ev.getEndTime(), "thread", ev.getThread("eventThread").getJavaName(), "class", ev.getClass("objectClass").getName(), "size", ""+ ev.getLong("allocationSize"), "tlab", ""+ ev.getLong("tlabSize") )); mapperは、ペアのコレクションです。各ペアは、条件と関数で構成されています。条件は、入力イベントの評価に使われます。条件がTRUEを返した場合、関連付けられた関数がイベントに適用され、イベントが文字列のマップに変換されます。 つまり、decodeEvent()メソッドはmapperをループし、入力イベントに一致する条件を探そうとします。一致する条件が見つかった場合、対応する関数が呼び出され、イベントがデコードされます。 JFRが提供する一連のイベントは、Javaのバージョンによって微妙に異なります。Java 11では、120種類を超えるイベントが存在する可能性があります。イベントの種類ごとに微妙に異なるデコーダが必要であるため、すべての種類のイベントをサポートすることは至難の業です。しかし、興味のあるメトリックのサブセットをサポートすることは、容易に実現できます。 Java 14のJFR Event Streaming Java 14では、JFRを新しいモードで使用できるようになりました。それがJFR Event Streamingです。このAPIを使うことで、JFRイベントが発生したときにコールバックを受け取り、そのコールバックに即座に応答するプログラムを作れるようになります。 開発者がこのAPIを使うと考えられる方法のうち、明らかであり重要なのは、Javaエージェントを用いる方法です。Javaエージェントは、Instrumentation APIを使用する特別なJARファイルです。このAPIでは、クラスで特別な静的メソッドpremain()を宣言し、クラス自体をインスツルメント・ツールとして登録します。次に、Javaエージェントの一例を示します。 public class AgentMain implements Runnable { public static void premain(String agentArgs, Instrumentation inst) { try { Logger.getLogger("AgentMain").log( Level.INFO, "Attaching JFR Monitor"); new Thread(new AgentMain()).start(); } catch (Throwable t) { Logger.getLogger("AgentMain").log( Level.SEVERE,"Unable to attach JFR Monitor", t); } } public void run() { var sender = new JfrStreamEventSender(); try (var rs = new RecordingStream()) { rs.enable("jdk.CPULoad") .withPeriod(Duration.ofSeconds(1)); rs.enable("jdk.JavaMonitorEnter") .withThreshold(Duration.ofMillis(10)); rs.onEvent("jdk.CPULoad", sender); rs.onEvent("jdk.JavaMonitorEnter", sender); rs.start(); } } } premain()メソッドが呼び出されると、AgentMainの新しいインスタンスが作成され、新しいスレッドで実行されます。構成された記録ストリームは、一度起動すると終了することはありません(そのため、別個のインスツルメント・スレッドで作成されます)。 記録ストリームでは、大量のイベントが生成される可能性があります。ありがたいことに、JFR APIでは、コールバックでの処理が見込まれるイベントの数を減らすための基本的なフィルタリングがいくつか提供されています。      Enabled:イベントの記録自体を行うかどうかを指定する Threshold:記録されるイベントの最短時間を指定する Stack trace:Event.commit()メソッドでスタック・トレースを記録するかどうかを指定する Period:イベントが定期的に送信される場合、その間隔を指定する 上記以外のフィルタ機能は、アプリケーション・プログラマーが実装しなければならない点に注意してください。たとえば、毎秒数GBを割り当てるJavaプログラム(かなりの割当て量ですが、大規模なアプリケーションでは十分考えられます)では、何十万というイベントがおそらく生成されるでしょう。 先ほどのサンプルJavaエージェントは、記録ストリームを開始し、それぞれのイベントをJfrStreamEventSenderと呼ばれるオブジェクトに送ります。このオブジェクトは、次のようになっています。 public final class JfrStreamEventSender implements Consumer<RecordedEvent> { private static final String SERVER_URL = "http://127.0.0.1:8080/events"; @Override public void accept(RecordedEvent event) { try { var payload = JFRFileProcessor.decodeEvent(event); String json = new ObjectMapper().writeValueAsString(payload); var client = HttpClient.newHttpClient(); var request = HttpRequest.newBuilder() .uri(URI.create(SERVER_URL)) .timeout(Duration.ofSeconds(30)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); Logger.getLogger("JfrStreamEventSender").log( Level.INFO, "Server response code: " + response.statusCode() + ", body: " + response.body()); } catch (IOException | InterruptedException e) { Logger.getLogger("JfrStreamEventSender").log( Level.SEVERE, "Unable to send JFR event to server", e); } } } このステートレス・クラスでは、イベントをそのまま受け取り、ファイルを処理する場合で示したのと同じようにデコードを行ってから、Jacksonライブラリを使ってJSONにエンコードしています。その後、Java 11 HTTPクライアントを使い、メトリックのペイロードを、その処理が可能なサービスに送信しています。この例で使っているサービスは、localhostのポート8080です。 もちろん、この単純なサンプル・コードから本番グレードのモニタリング・ツールを作るためには、さまざまなことが必要になります。大規模なツールを構築するためには、大量の入力データ・ポイント処理が可能なHTTPエンドポイントの作成が必要です。JVMを一意に特定することや、入力データが正しく分類されていることを保証するといった問題にも対処しなければなりません。 データ分析パイプラインや中期的ストレージ・システム、そしてフロントエンドでの視覚化やページ表示の導入も必要です。本番システムを監視していることから、こういった仕組みのすべてが最大限の信頼性レベルで動作することが不可欠です。 このような難題はありますが、JFR Event Streamingは、アプリケーションを実行しているJVMの監視および観測に使用可能なテクノロジーにおける大きな一歩を表しています。この新しいAPIは大きな注目を集めています。そしておそらく、Java 14のリリース後まもなく、オープンソースのトレーシングおよび監視ライブラリにイベント・ストリーミングのサポートが追加されることになるでしょう。監視やプロファイリングにおけるJFRのアプローチにはメリットがあります。そのメリットが皆さんのJVMのもとにやってくるのは、まもなくです。 Ben Evans Ben Evans(@kittylyst):Java Champion。New Relicのプリンシパル・エンジニア。『Optimizing Java』(O'Reilly)を含め、プログラミングに関する5冊の書籍を執筆している。jClarity(Microsoftにより買収)の創業者の1人であり、Java SE/EE Executive Committeeの元メンバーである。

※本記事は、Ben Evansによる"Java Flight Recorder and JFR Event Streaming in Java 14"を翻訳したものです。 実行中のアプリに関する大量のデータ・ポイントからなるストリームを取得する   著者:Ben Evans 2020年2月27日   本記事では、Java 14に導入される新機能について説明します。この機能はJFR...

Java Magazine

Java Magazine July 2020

  2020年7月   Java 14でのJava Flight RecorderとJFR Event Streaming 著者:Ben Evans 実行中のアプリに関する大量のデータ・ポイントからなるストリームを取得する   多くの新機能を搭載したJava 14が登場 著者:Raoul-Gabriel Urma これまでの2回のリリースを超える新機能が搭載されたJava 14は、コーディングを簡単にする新機能が満載   Java EEからJakarta EEへ 著者:Arjan Tijms 何が起きたのか、何を知っておくべきか   クイズに挑戦:ラムダ式の型(上級者向け) 著者:Simon Roberts、Mikalai Zaikin ラムダ式はラムダ式を返せるのか   クイズに挑戦:2次元配列(中級者向け) 著者:Simon Roberts、Mikalai Zaikin varと2次元配列の併用には注意が必要   クイズに挑戦:ラムダ式(上級者向け) 著者:Simon Roberts、Mikalai Zaikin ラムダ式でvarを使用する際の細かい決まり   クイズに挑戦:関数型インタフェース(上級者向け) 著者:Simon Roberts、Mikalai Zaikin 想定通りに動作する関数型インタフェースの定義と実装

  2020年7月   Java 14でのJava Flight RecorderとJFR Event Streaming 著者:Ben Evans 実行中のアプリに関する大量のデータ・ポイントからなるストリームを取得する   多くの新機能を搭載したJava 14が登場 著者:Raoul-Gabriel Urma これまでの2回のリリースを超える新機能が搭載されたJava...

全国のOTNおすすめ技術者向けセミナー、イベント&書籍(2020年8月)

セミナー&イベント 8/6(木) 【基本を知ろう】自律型データベース Oracle Autonomous Database 最新情報 Oracle Autonomous Database」は、自律型データベースのサービスで、データベースの自己稼働、自己保護、自己復旧を実現する、新しいクラウド上のサービスです。これまでデータベース管理者が手作業で行ってきた日々の運用作業を自動化することができます。 本セミナーでは、お客様活用事例と共に、サービス概要をご紹介します。   8/27(木) MySQL Day Virtual Event in Japan KDDI様をゲストスピーカーにお招きし、"Upgrading to MySQL8.0" をテーマとしたMySQL初のオンライン・カンファレンス「MySQL Day Virtual Event in Japan」を開催します。KDDI様、MySQLサポート奥野、MySQL SE梶山・稲垣がMySQL 8.0の新機能およびバージョンアップによる効果などを5セッションに渡ってご紹介いたします。ぜひご参加ください。   8/28(金) Always Freeを使って無料でMySQLのレプリケーション検証環境を構築しよう!@オープンソースカンファレンス2020 Online/Kyoto MySQLの検証環境を無料で構築しましょう! Always Freeとは、オラクルクラウドを無料で利用できるサービス。このサービスを利用すると、無料でLinuxサーバーを2台構築できます。そのサーバーにMySQLをインストールして、レプリケーションの検証環境を構築する方法を説明します。また、本セミナーの後に1時間半ほど展示の時間を設けています。本セミナーで紹介した内容を皆さんに実践して頂くハンズオンの時間としていますので、すぐに試したいという方は是非展示にもご参加ください。   8/28(金) 実践Kubernetesハンズオン ~OKEでKubernetesをバーチャル体験しよう~ Oracle Cloudは大規模なコンテナの管理、デプロイおよび運用に適したクラウドサービスを複数提供しています。本ハンズオンセミナーではOracle Cloudが提供する下記コンテナ・サービスを実際に利用しKubernetes環境の構築からコンテナ・アプリケーションのデプロイ、CI/CDまでの一連の流れを体感いただけます。 書籍 予約受付中!新 Bronze DBA のオラクルマスター教科書(黒本) 「オラクルマスター教科書 Bronze DBA Oracle Database Fundamentals」(翔泳社出版) が9月17日に刊行されます。出題範囲を網羅し、かつ「試験に出るところ」を一冊に凝縮された日本オラクル株式会社監修の書籍はただいま予約受付中です。   オラクル認定資格教科書 Javaプログラマ Bronze SE スピードマスター問題集(試験番号1Z0-818) Oracle Javaの最新試験「SE 7/8(IZ0-814)試験」を徹底的に分析し、書き下ろした対策問題集です。 【本書の特徴】 分野ごとの練習問題+本番さながらの模擬試験 各問ごとに詳しい解説 図や表が多く、初心者でも安心 読みやすく分かりやすい2色刷 サンプルコード付なので、手を動かしながら学習できる Java教育に定評のある著者による書き下ろし 問題の重要度がわかるアイコン付き AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。          

セミナー&イベント 8/6(木) 【基本を知ろう】自律型データベース Oracle Autonomous Database 最新情報 Oracle...

第21回 バージョンとパッチ適用 (DBCS/ExaCS) | もしもみなみんがDBをクラウドで動かしてみたら

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/08/25時点のものになります みなさん、こんにちは。今回は、Database Cloud Service (DBCS) と Exadata Cloud Service (ExaCS) 上のバージョンやパッチについて、考え方やパッチ適用方法を解説していきたいと思います。 目次 DBCSとExaCSのS/Wバージョンの考え方 H/Wモデルの考え方 パッチ適用方法  DBSCの場合  ExaCSの場合 まとめ よくある質問 window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-159254751-1');   1. DBCSとExaCSのS/Wバージョンの考え方 Oracle Cloud 上でOracle Databaseを利用する上での選択肢として検討にあがるのは、主にPaaSサービスもしくはIaaS(Compute)上で利用するパターンになると思います。PaaSサービスの種類は第9回でご紹介しましたが、PaaSサービスの中にもFull-Managed型のAutonomous DatabaseとUser-Managed型のDBCS/ExaCSがあります。Aunomous Databaseの場合は、Full-Managedとしてソフトウェア・バージョンの管理も含むOracle Databaseの管理はオラクルが責任を持つため、バージョンやパッチレベルなどは基本的に最新のものを使う形になります。一方でDBCSやExaCSの場合は、OS以上はユーザー管理のためOracle DatabaseやGrid Infrastructureなどのバージョンやパッチレベルを、提供されている選択肢の中からお客様で選択して簡単に構築することができます。OS以上の中に含まれるデフォルトでインストール済のS/Wは、OS、Grid Infrastructure(GI)、Database(DB)、そしてPaaSサービスの管理のためのクラウド・ツールがあります。 DBCSやExaCSの場合、データベースを作成時に選択可能なバージョンはサポート提供中の各バージョンの最新リリースから選択が可能です。これは、Oracle Databaseのライフタイム・サポートに準拠している形となっており、Premier or Extended Supportの期間のものが対象になります。具体的には、現時点(2020/07)で利用可能なバージョンは、19/18/12.2.0.1/12.1.0.2/11.2.0.4の5種類となります。それぞれのパッチレベル(RU/BP/PSU)は、クラウド上でリリースされていてる最新と最新から2つ前までの、計3種類が提供されているので少し古いイメージでも作成可能です。これは、セキュリティ・ポリシーに準拠して、サービスとしてのイメージの提供は古くても最新から2つ前までとなっています。 また、Premier or Extended Supportの最終日がクラウド上でのサポートの最終日となります。この日を過ぎるとどうなるのかが一番気になると思いますが、OS以上はユーザー管理となるため、その日を過ぎたら勝手に削除されるということはありません。ただ、新規で作成が不可能となりクラウドの機能(コンソールやCLIなど)としてのサポートが終了することになるので、サポート終了日までにアップグレードをご検討いただくことが推奨です。 参考  ・MOS Doc ID 742060.1: Release Schedule of Current Database Releases ・MOS Doc ID 2333222.1: Exadata Cloud Service Software Versions ・Oracle Lifetime Support Policy   2. H/Wモデルの考え方 インフラはオラクルが管理するため、普段あまり気にする必要のないH/Wについてですが、DBCSやExaCSなどはDBシステム(インスタンス)を作成する際に選択する"シェイプ"でH/Wの世代が決まっています。例えば、DBCSの場合はシェイプ名のVM.Standard2.2、ExaCSの場合はExadata.Quarter3.100の、それぞれの2セクション目(青字)の部分がH/Wモデルになります。(タイミングによっては、H/WモデルのEOLを意識する必要が出てくる可能性がありえますが、また別の回で解説したいと思います) 参考 ・Doc ID 2649066.1: OCI - Oracle Database Exadata Cloud Service (ExaCS) Support Dates   3. パッチ適用方法 DBCSやExaCSはOS以上がユーザー管理となるため、OS以上のOS、Grid Infrastructure、Database、そしてPaaSサービスの管理のためのクラウド・ツールに対するパッチ適用は、ユーザー側でパッチ適用の計画と適用実施が可能です。ここでは、それぞれのパッチ適用方法についてご紹介します。 まずは、DatabaseとGrid Infrastructureについて。それぞれ、RU/BP/PSUのパッチがコンソールやAPI/CLIなどから適用可能です。ここで適用可能なパッチイメージは、定期的にリリースされて自動で使えるようになるので、ユーザー側でイメージのダウンロードなどは不要です。なお、DBCS/ExaCSでは、DBシステムのバージョンと表示されるものがGrid Infrastructureのバージョンを指します。 なお、Grid Infrastructure(Oracle Clusterware)は自分と同じもしくは下位のバージョン・パッチレベルのDatabaseの管理が可能です。そのため、もしDatabaseにパッチ適用することで、Grid Infrastructureよりも上位になる場合、先にGrid Infrastructure側にパッチ適用をしましょう。   DBCSの場合 DBシステム(Grid Infrastructure) 『DBシステムの詳細』ページで、「DBシステム・バージョン」というのが、Grid Infrastructureのバージョンを指します。今回の環境は、19.5.0.0 の状態です。 opatchコマンドでも確認してみます。 [grid@emee ~]$ /u01/app/19.0.0.0/grid/OPatch/opatch lspatches 30269395;ACFS Interim patch for 30269395 30125133;Database Release Update : 19.5.0.0.191015 (30125133) 30122149;OCW RELEASE UPDATE 19.5.0.0.0 (30122149) 29401763;TOMCAT RELEASE UPDATE 19.0.0.0.0 (29401763) OPatch succeeded. そのまま下の方にスクロールして、リソースの『パッチ』を選択すると、適用可能なパッチリストが表示されます。 今回は19.8.0.0を適用してみます。適用したいパッチの右の…をクリックして、『事前チェック』をしてみましょう。この環境に適用できるかどうか(コンフリクトなど)をチェックが走ります。Opatchのprereqなどが実行されるイメージです。確認画面で『OK』をクリックします。 事前チェックで問題がなければ、『適用』をしてみましょう。確認画面で『OK』をクリックします。 Real Application Clustersで2ノード構成になっている場合は、ローリングで一台ずつ適用されます。適用が完了すると、DBシステムのステータスが「使用可能」になります。バージョンが、19.8.0.0になってますね。 念のため、Opatchコマンドでも確認してみましょう。 [grid@emee ~]$ /u01/app/19.0.0.0/grid/OPatch/opatch lspatches 31335188;TOMCAT RELEASE UPDATE 19.0.0.0.0 (31335188) 31305087;OCW RELEASE UPDATE 19.8.0.0.0 (31305087) 31304218;ACFS RELEASE UPDATE 19.8.0.0.0 (31304218) 31281355;Database Release Update : 19.8.0.0.200714 (31281355) OPatch succeeded.   Database 「データベース詳細」のページで、『データベース・バージョン』を確認します。今回の環境は、19.5.0.0になります。パッチのセクションに、このデータベースに対して適用可能なパッチリストが表示されます。 opatchコマンドでも確認してみます。 [oracle@emee ~]$ /u01/app/oracle/product/19.0.0.0/dbhome_1/OPatch/opatch lspatches 30423135;REINSTATE IS FAILING POST APPLYING 19C OCT19 PATCHES 30128191;OJVM RELEASE UPDATE: 19.5.0.0.191015 (30128191) 30125133;Database Release Update : 19.5.0.0.191015 (30125133) 30122149;OCW RELEASE UPDATE 19.5.0.0.0 (30122149) OPatch succeeded.   今回は19.8.0.0を適用してみます。適用したいパッチの右の…をクリックして、『事前チェック』をしてみましょう。この環境に適用できるかどうか(コンフリクトなど)をチェックが走ります。Opatchのprereqなどが実行されるイメージです。 事前チェックで問題がなければ、『適用』をクリックして確認画面で『OK』をクリックします。 Real Application Clustersで2ノード構成になっている場合は、ローリングで一台ずつ適用されます。適用が完了すると、DBシステムのステータスが「使用可能」になります。バージョンが、19.8.0.0になってますね。また、適用したパッチは、「パッチ履歴」のリストに表示されます。 念のため、Opatchコマンドでも確認してみましょう。 [oracle@emee ~]$ /u01/app/oracle/product/19.0.0.0/dbhome_1/OPatch/opatch lspatches 31301460;JDK BUNDLE PATCH 19.0.0.0.200714 30432118;MERGE REQUEST ON TOP OF 19.0.0.0.0 FOR BUGS 28852325 29997937 31305087;OCW RELEASE UPDATE 19.8.0.0.0 (31305087) 31281355;Database Release Update : 19.8.0.0.200714 (31281355) 30128191;OJVM RELEASE UPDATE: 19.5.0.0.191015 (30128191) OPatch succeeded.   クラウド・ツール DBCSのOS上/コンピュート内のクラウド・ツール=CLIも定期的にアップデートをしておくことが推奨です。クラウド・サービスの新機能やそれに伴う既存機能の改善を反映することができます。Real Application Clustersの2ノード環境では、各ノードで実行して下さい。 [root@emee bin]# /opt/oracle/dcs/bin/cliadm update-dbcli   参考) Oracle Cloud Infrastructure ドキュメント CLI Update Command   OS OSのアップデートに関しては、コンソールやAPI/CLIなどのクラウドの機能としては提供しているわけではなく、yumコマンドを使います。実際の手順は、下記ドキュメントに従って実施してください。 参考) Oracle Cloud Infrastructure ドキュメント OS Updates   ExaCSの場合 DBシステム(Grid Infrastructure) 『DBシステムの詳細』ページで、「DBシステム・バージョン」というのが、Grid Infrastructureのバージョンを指します。今回の環境は、19.7.0.0 の状態です。 opatchコマンドでも確認してみます。 [grid@emexa1-fzexn1 ~]$ /u01/app/19.0.0.0/grid/OPatch/opatch lspatches 30898856;TOMCAT RELEASE UPDATE 19.0.0.0.0 (30898856) 30894985;OCW RELEASE UPDATE 19.7.0.0.0 (30894985) 30869304;ACFS RELEASE UPDATE 19.7.0.0.0 (30869304) 30869156;Database Release Update : 19.7.0.0.200414 (30869156) OPatch succeeded.         「使用可能な最新パッチ」の横にある『表示』をクリックします。 「データベース・システム」のセクションに、適用可能なパッチリストが表示されます。今回は19.8.0.0を適用してみます。適用したいパッチの右の…をクリックして、『事前チェック』をしてみましょう。この環境に適用できるかどうか(コンフリクトなど)をチェックが走ります。Opatchのprereqなどが実行されるイメージです。 事前チェックで問題がなければ、『適用』をクリックして確認画面で『OK』をクリックします。 ExaCSはReal Application Clusters構成になっているので、ローリングで一台ずつ適用されます。適用が完了すると、DBシステムのステータスが「使用可能」になります。バージョンが、19.8.0.0になってますね。 念のため、Opatchコマンドでも確認してみましょう。 [grid@emexa1-fzexn1 ~]$ /u01/app/19.0.0.0/grid/OPatch/opatch lspatches 31335188;TOMCAT RELEASE UPDATE 19.0.0.0.0 (31335188) 31305087;OCW RELEASE UPDATE 19.8.0.0.0 (31305087) 31304218;ACFS RELEASE UPDATE 19.8.0.0.0 (31304218) 31281355;Database Release Update : 19.8.0.0.200714 (31281355) OPatch succeeded.   参考) Oracle Cloud Infrastructure ドキュメント Patching an Exadata DB System CLIで実行したい場合はこちら Patching an Exadata DB System Manually Database ExaCSの場合、1つの環境上に複数バージョンの複数のデータベースが作成可能です。また、複数データベースで1つのデータベース・ホーム(ORACLE_HOME)を共有可能です。データベースのパッチは、"データベース・ホーム"に適用されるので、コンソール上の表示としてもデータベースごとではなくデータベース・ホームに対して作業をしていきます。今回は、対象のデータベース「EMDB」が属するデータベース・ホーム「dbhome202007」に適用していきます。バージョンは19.7.0.0になります。 opatchコマンドでも確認してみます。 [oracle@emexa1-fzexn1 ~]$ opatch lspatches 30432118;MERGE REQUEST ON TOP OF 19.0.0.0.0 FOR BUGS 28852325 29997937 29997959;DSTV34 UPDATE - TZDATA2019B - NEED OJVM FIX 30805684;OJVM RELEASE UPDATE: 19.7.0.0.200414 (30805684) 30894985;OCW RELEASE UPDATE 19.7.0.0.0 (30894985) 30869156;Database Release Update : 19.7.0.0.200414 (30869156) OPatch succeeded.   「データベース・ソフトウェア・バージョン」の「使用可能な最新パッチ」の横の『表示』をクリックします。 Grid Infrastructureと同じ「パッチ」ページへ遷移しました。 「データベース・システム」のセクションに、適用可能なパッチリストが表示されます。適用したいパッチの右の…をクリックして、『事前チェック』をしてみましょう。この環境に適用できるかどうか(コンフリクトなど)をチェックが走ります。Opatchのprereqなどが実行されるイメージです。 事前チェックで問題がなければ、『適用』をクリックして確認画面で『OK』をクリックします。 ExaCSはReal Application Clusters構成になっているので、ローリングで一台ずつ適用されます。適用が完了すると、DBシステムのステータスが「使用可能」になります。バージョンが、19.8.0.0になってますね。 念のため、Opatchコマンドでも確認してみましょう。 [oracle@emexa1-fzexn1 ~]$ opatch lspatches 30432118;MERGE REQUEST ON TOP OF 19.0.0.0.0 FOR BUGS 28852325 29997937 29997959;DSTV34 UPDATE - TZDATA2019B - NEED OJVM FIX 31301460;JDK BUNDLE PATCH 19.0.0.0.200714 31219897;OJVM RELEASE UPDATE: 19.8.0.0.200714 (31219897) 31305087;OCW RELEASE UPDATE 19.8.0.0.0 (31305087) 31281355;Database Release Update : 19.8.0.0.200714 (31281355) OPatch succeeded.   参考) Oracle Cloud Infrastructure ドキュメント Patching an Exadata DB System CLIで実行したい場合はこちら Doc ID 2676835.1: Manual Install of Database Patch Updates on the Exadata Cloud Service in OCI クラウド・ツール ExaCSのOS上/コンピュート内のクラウド・ツールは、デフォルトで自動アップデートが毎週実行されるように設定されています。実際にはrpmのパッケージのアップデートが実行されるイメージなので、データベースやGrid Infrastructureなど、システムへの影響はありません。また、このツールが最新になることで、クラウド・サービスの新機能やそれに伴う既存機能の改善が使えるようになるため、自動アップデートは有効のままにしておくことが推奨です。 ・クラウド・ツールのバージョンの確認 # rpm -qa|grep -i dbaastools_exa dbaastools_exa-1.0-1+20.1.1.0.0_200620.0101.x86_64   自動アップデートは毎週1回ですが、すぐに新機能を使いたい場合や既知問題の対応など、手動でアップデートしたい場合は下記のコマンドでアップデート可能です。 ・適用可能なパッチリスト #  dbaascli patch tools list DBAAS CLI version 20.1.1.0.0 Executing command patch tools list Checking tools on all nodes Current Patchid on emexa1-fzexn1: 20.1.1.0.0_200620.0101 Available Patches Patchid : 20.1.1.0.0_200709.1315(LATEST) Install tools patch using dbaascli patch tools apply --patchid 20.1.1.0.0_200709.1315    or dbaascli patch tools apply --patchid LATEST All Nodes have the same tools version   ・パッチ適用(最新を適用 ★推奨) #  dbaascli patch tools apply --patchid LATEST   ・パッチ適用(IDを指定して適用) #  dbaascli patch tools apply --patchid <パッチID>   参考) Oracle Cloud Infrastructure ドキュメント Updating Tooling on an Exadata DB System   OS OSのアップデートに関しては、コンソールやAPI/CLIなどのクラウドの機能としては提供しているわけではなく、Exadataのpatchmgrを使います。実際の手順は、下記ドキュメントに従って実施してください。 参考) Oracle Cloud Infrastructure ドキュメント OS Updates   4. まとめ DBCS/ExaCSはOS以上はユーザー管理のため、パッチ適用のタイミングや適用するか否かの判断をユーザー側でできるという柔軟度はあります。ただし、ずっとパッチを適用しない(塩漬け)でいいというわけではありません。パッチを古いまま使い続けることはセキュリティ面や既知の不具合などリスクがあること、また、環境の再作成が必要になった場合に古いイメージで新規作成ができないことなどのリスクが考えられます。そのため、ぜひ定期的なパッチ適用を行っていただければ幸いです。   よくある質問 Q1) DBCS/ExaCS上のGrid InfrastructureやDatabaseに対する個別パッチは、コンソールから適用できますか A1) コンソールなどクラウドのツールで適用可能なパッチとして提供されているのは、RU/BP/PSUになります。そのため、個別パッチについては、従来通りSRでお問い合わせの上、手動(Opatch)にて適用してください。 Q2) DBCS/ExaCS上のGrid InfrastructureやDatabaseに対して、パッチ適用をせずにそのまま使い続けた場合、自動で適用されたり削除されたりしますか。 A2) DBCS/ExaCSの場合OS以上はユーザー管理領域となるため、オラクル側で勝手に自動パッチ適用や削除などは行いません。ただし、パッチを古いまま使い続けることはセキュリティ面や既知の不具合などリスクがありますので、定期的なパッチ適用の計画・実施をしていただくことを推奨いたします。   更新履歴 2020/08/25 ExaCSのDBシステム(Gird Infrastructure)とDatabaseのCLIでのパッチ適用方法のリンク追加 もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/08/25時点のものになります みなさん、こんにちは。今回は、Database Cloud Service (DBCS) と Exadata Cloud Service (ExaCS) 上のバージョンやパッチについて、考え方やパッチ適用方法を解説していきたいと思います。 目次 DBCSとExaCSのS/Wバージョン...

Cloud Security

AD Bridge使用時のIdentity Cloud Serviceにおけるユーザー・アクセスのトラブルシューティング

※本記事は、Paul Toalによる"Troubleshooting User Access in Identity Cloud Service when using AD Bridge"を翻訳したものです。 June 10, 2020 これまで、Oracle Identity Cloud Service(Oracle IDCS)でActive Directory(AD)Bridgeを使用してユーザーとグループをOracle IDCSに同期する方法について詳しく説明してきました。たとえば、こちらの記事ではADユーザーのサブセットの同期について語っています。 注:Oracle IDCSはAzureも完全にサポートしています。このシナリオではAD Bridgeは使用されず、業界のオープン標準であるSCIMインタフェースがAzureとOracle IDCSの間で使用されます。一方、ADはSCIMをサポートしていないため、AD Bridgeが使用されます。 AD(またはAzure AD)からOracle IDCSへのユーザーの同期はよくある要件であり、一般的には、ADからADFS経由でOracle IDCSへのシングル・サインオン(SSO)を可能にするために、フェデレーションと共に使用されます。AD Bridgeのセットアップは、Oracle IDCSとADFSの間にフェデレーションをセットアップするのと同様に、非常に簡単です。以下の図に示すとおり、単純な2段階のプロセスで構成できます。これらについては、こちらとこちらのチュートリアルで説明しています。 先日、あるお客様から相談を受けました。そのお客様のシナリオでは、上記のデプロイメントで(ただし、ADFSではなくAzureがアイデンティティ・プロバイダである)、さらにE-Business Suite(EBS)をOracle IDCSと統合して、AzureからEBSまで全体的なSSOを実現しています。正常な状態であれば、ユーザー・フローはシームレスです。ユーザーは認証済みユーザーとしてEBSに戻るまで、AzureについてもOracle IDCSについても知ることはありません(この2つの間で透過的に移動させられるため)。しかし、一部のユーザーで、このフローの途中にエラーが発生する問題がある、ということでした。そのお客様からは、この問題のトラブルシューティングを依頼されました。  私はお客様と一緒にいくつかの共通の手順を実行し、すぐにその問題を特定しました。ここでは、同じような問題を抱えている方やOracle IDCSのトラブルシューティングの構造化手法を知りたい方のために、このトラブルシューティングのプロセスを共有したいと思います。 それでは、問題の説明から始めましょう。私の環境にこの問題の状況を再現し、テスト・ユーザーとしてukuser2@emeacp.comを使用することにします。このユーザーには以下の画面が表示されます。 先ほど説明したとおり、ユーザーにはAzure/ADFS、Oracle IDCSのいずれの画面も表示されるべきではありません。シームレスでなければならず、今の状態には問題があります。この問題を特定するために私がとった手順を詳しく見ていきましょう。 ステップ1 – ログイン失敗レポート まず、明らかに調査すべき場所はログイン失敗レポートです。ここで、ログインに失敗したユーザーが、失敗したログインおよび考えられる理由とともに記録されているかを確認します。Oracle IDCS管理コンソールで、次の場所に移動します。  「Reports」→「Unsuccessful Login Attempts」 これを見る限り、ユーザーukuser2@emeacp.comに関する情報は何もありません。   ステップ2 – ユーザーが存在するか 上記のレポートにはログインに失敗したユーザーが表示されなかったため、Oracle IDCSがそのユーザーについて知らないのではないかと私は考えました。そのため、次のステップとして、このユーザーが実際にOracle IDCSに存在するかを確認します。同じ管理コンソール内で、「Users」に移動し、このユーザーを検索します。 ややこしいことになってきました。認証を試みたユーザーはOracle IDCSに存在しません。このことは、認証できなかった理由の説明にはなりますが、なぜユーザーが存在しないのかという新たな疑問が生まれました。ADユーザーがAD Bridgeと同期されていることは分かっていますので、AD Bridgeの構成が正しくないのかもしれません。フィルタによって、必要なユーザーのオブジェクトが除外されているのかもしれません。 ステップ3 – AD Bridgeの構成 そのため、次に調査する領域はAD Bridgeの構成ということになります。私の環境では、AD構造は以下のようにかなり分かりやすくなっています。 AD Bridgeの構成はOracle IDCS管理コンソール内で実行するため、このコンソールの以下の場所が次の調査対象です。             「Settings」→「Directory Integrations」 以下のように、AD Bridgeは正しく構成され、動作しています。  次にフィルタを確認する必要があります。上のエントリをクリックして、その構成に移動します。 ここでは、UKとFranceのOUを選択しており、特定のユーザーを除外する可能性のある固有のフィルタは存在しないことが分かります。また、Global OUの下ですべてのグループを同期しています。いずれのケースでも、ネストされたOUがある場合に備え、「Include Hierarchy」をチェックしています。 ここまでの話をまとめましょう。ユーザーukuser2は、UK OU内のAD内に存在しています。AD BridgeはそのOUを同期するように構成されていますが、ukuser2はOracle IDCSにインポートされていません。 では、ユーザーのADレコードに問題があるのでしょうか。   ステップ4 – AD Bridgeのログ AD Bridgeのスコープは正しいのに、必要なユーザー・レコードがOracle IDCSに同期されていないことを確認しましたので、調査の範囲をAD Bridgeに広げる必要があります。AD Bridgeは、ユーザー・アカウントを取得するためにADと相互作用するコンポーネントであるからです。 AD Bridgeを実行しているWindowsサーバーに接続して、以下のAD Bridge UIを起動します。               C:\Program Files\Oracle\IDBridge\IDBridgeUI.exe AD Bridgeが実行中であることが確認できます。 ログを確認して、問題を特定しましょう。「 」をクリックすると、AD Bridgeのログ・ファイルが保存されたフォルダに移動できます。 メモ帳(またはお好きなエディタ)でIDBridge.logを開きます。このログ・ファイルには多数の行が含まれているため、最初に問題を探すための簡単な方法として、ファイルの最後までスクロールしてから上方向に「Error」を検索します。 問題がヒットしました。ログ・ファイルは以下のように表示されています。 ERROR IDBridge - Failed to create/update User 'CN=UK User2,OU=UK,OU=Europe,OU=Global,DC=emeacp,DC=com' with error code 400. Error message: error.identity.user.invalidPhoneValFormat : The format of the phone number (+44) 078 123 4544 you entered is invalid. Make sure that the format is compliant with national and international standards. ukuser2のADレコードを調査すると、確かに無効な電話番号になっています。 丸括弧を削除して、正しい形式に修正しましょう。 次の同期のスケジュール実行まで待つことも、即時同期を実行することもできます。ここでは、私が待ちきれないので即時同期を実行することにします。Oracle IDCS管理コンソールのAD Bridge構成画面が開いたままですので、その画面で「 」ボタンをクリックします。 前回の同期以降の変更のみを反映するため、「Incremental Import」を選択します。 さらに、この同じAD Bridge構成画面のImportタブからジョブを監視します。少し時間が経つと、1つのユーザー・レコードがインポートされたことを確認できます。 Usersのクイック検索によって、ukuser2がOracle IDCSにインポートされたことが分かります。これで、サインインの問題は解消されました。このユーザーはEBSに問題なくサインインできるようになりました。 このように電話番号の形式が無効であるというケース以外にも、たとえば以下のように、必須の属性が不足しているという問題が起きる可能性もあります。 ERROR IDBridge - Failed to create/update User 'CN=UK User3,OU=UK,OU=Europe,OU=Global,DC=emeacp,DC=com' with error code 400. Error message: error.identity.user.primaryEmailNotSpecified : The primary email must be specified. しかし、いずれの場合も、AD Bridgeログ・ファイルのエラーを確認することで、正しい方向に進むことができます。先ほどのお客様の例では、電話番号に問題があってそれを修正した後、さらに別のユーザーでいくつかの属性が不足していることがログで判明し、存在しないユーザー約6人分のOracle IDCSへの同期が開始されました。   その他のトラブルシューティング この記事を書いている以上、Oracle IDCSの問題のトラブルシューティングに便利なツールであるDiagnosticログを紹介しないわけにはいきません。 もう1つ簡単な例を見てみましょう。同じ環境を使用して、franceuser1@emeacp.comというユーザー名でログインしようとすると、ukuser2のときと同じエラー画面が表示されました。前述のすべてのチェック項目を確認し、このユーザーはOracle IDCS内に存在しています。 そのため今回は、ユーザーが明らかにOracle IDCSに存在し、ADFSからのSAMLアサーションによって情報を送信されているはずなのにOracle IDCSで認証できない理由を把握する必要があります。このようなケースでDiagnosticのログが役に立ちます。 Diagnosticsを使用するには、有効化する必要があります。Oracle IDCS管理コンソールで、以下の項目を選択します。             「Settings」→「Diagnostics」 各項目の説明のとおり、いずれかの診断を有効化すると、診断がその時点から15分間有効になり、問題を再現できるようになります。有効化する診断のレベルについてはユーザーが指定します。私は通常は「Service View」を有効化します。最も多くのデータを取得して、結果のCSVファイルを簡単にフィルタリングできるからです。 レベルを選択して「」ボタンで保存したら、すぐに問題の再現に取り掛かってください。この例では、franceuser1での再ログインを試みました。テストの実行後、以下の場所から診断用の出力ファイルをダウンロードします。             「Reports」→「Diagnostic Data」 ここでは「15 minutes」の間隔と「Service View」を選択してください。これでCSVがローカルのデスクトップにダウンロードされます。このファイルを開いて、問題の調査を始めましょう。私のシナリオでは、ADFSから送り返されるSAMLアサーションに問題があることがかなり早い段階で明らかになりました。この問題は以下のログで特定されています。 すぐにテスト用のブラウザに戻り、テストを再実行することで、Web DeveloperツールでADFSからのSAMLレスポンスを記録できました。このレスポンス内に以下の記述が確認できます。 この例では、問題はADFS側にあるようです。ADFSで調査を行ったところ、私の環境内のフランスのユーザーがOracle IDCSへのフェデレーションを許可されていないようでした。これは、ADFS Access Control Policyに定義されています。そのため、ADFSは上記のとおりRequestDeniedステータス・コードを送り返しています。 この記事が、Oracle IDCSのトラブルシューティングの際に実行すべき論理的手順を理解するためのお役に立てば幸いです。

※本記事は、Paul Toalによる"Troubleshooting User Access in Identity Cloud Service when using AD Bridge"を翻訳したものです。 June 10, 2020 これまで、Oracle Identity Cloud Service(Oracle...

Cloud Security

Oracle Data SafeでExadata Cloud ServiceとDatabase Cloud Serviceのセキュリティをシンプルに

※本記事は、Bettina Schaeumerによる"Simplifying Security of Exadata Cloud Service and Database Cloud Service with Oracle Data Safe"を翻訳したものです。 May 6, 2020    プライベートIPアドレスを割り当てたOracle CloudデータベースをData Safeに接続する3つの簡単なステップ 大量の機密データを含むクラウド・データベースを、Oracle Cloud Infrastructure内部の独自のプライベート・ネットワーク内で運用している方も多いのではないでしょうか。Oracle Cloud Infrastructureは、インフラストラクチャ、ネットワーク、コンピュート、データベースの目標を達成するために役立つセキュリティ機能を提供していますが、データ構成やユーザーのセキュリティ、ユーザー・アクティビティの監視、データ・セキュリティなどについては皆さんも責任を負っています。データベースとセキュリティの両面において、これらに対処するには時間、労力、専門知識が必要でした。しかし、それは過去の話です。Oracle Cloud Infrastructure上のプライベート仮想クラウド・ネットワーク(VCN)内でOracle Cloudデータベースを運用している場合は、Oracle Data Safeを利用することによって、ごく簡単にセキュリティ体制を監視、統制できるようになりました。 Data Safeの最新の更新では、プライベートIPアドレスを利用するクラウド・データベースのサポートが追加されました。このデータベースにはExadata Cloud Service、Database Cloud Serviceが含まれます(仮想マシン、ベアメタルのいずれで実行されていてもかまいません)。このサポートは、パブリックIPアドレスを利用するOracle Cloudデータベース(Oracle Autonomous Transaction Processingデータベース、Oracle Autonomous Data Warehouseなど)に対するData Safeの現行のサポートを拡張したものです。 Oracle Data Safe この新機能やデータベースへの接続のセットアップ方法に進む前に、まずOracle Data Safeについて軽くご紹介します。Data Safeは、Oracle Cloud内のデータベース・セキュリティを管理するためのオラクルの統合制御センターです。Data Safeは、セキュリティ統制を評価し、ユーザー・セキュリティを評価し、ユーザー・アクティビティを監視し、データ・セキュリティのコンプライアンス要件へ対応するための製品です。その手段として、データの機密性を評価し、本番ではないデータベース用に機密データをマスキングします。Data Safeについてよくご存じでない場合は、ブログ記事のA Guided Tour of Oracle Data Safeで詳細をご確認ください。 特に良い点として、すべてのData Safeの機能がOracle Cloudデータベースのサブスクリプションに含まれており、Data Safe内での1か月、1データベースあたり最大100万監査レコードの監査収集機能も付属しています。 Data Safeのデータベースへの接続 プライベートVCN内で実行中のクラウド・データベースにData Safeを接続するには、基本的に次の3つのステップを実行します。 Data Safe用のプライベート・エンドポイントを作成する – この操作はVCNに対して1回だけ実行する Data Safeとデータベースの通信を許可する データベースをData Safeに登録する ステップ1 – プライベート・エンドポイントの作成 プライベートIPアドレスが割り当てられたクラウド・データベースが仮想クラウド・ネットワーク(VCN)内で実行されている場合、VCN内にData Safe用のネットワーク・ポイントが必要になります。そのために、プライベート・エンドポイントと呼ばれるData Safeの新機能が導入されました。Data Safeでのプライベート・エンドポイントのセットアップは非常に簡単で、データベースが実行中のVCNの名前とプライベート・エンドポイントを作成するサブネットの情報だけが必要です。このサブネットは、データベースが実行中のサブネットでも、同じVCN内の別のサブネットでもかまいません。VCNの名前とサブネットを調べるには、Oracle Cloud Infrastructureのデータベース・コンソールに移動します。以下の例は、仮想マシン上のDatabase Cloud Serviceのデータベース・コンソールです。   図1 - Oracle Cloud Infrastructureのデータベース・コンソール この例では、データベースはVCN DataSafe_VCN_PE、サブネットDataSafe_Subnet内で実行されています。 この画面のPortはステップ2で、データベースのOCIDはステップ3でそれぞれ必要になりますので、後で使用するためにコピーしておいてください。  次に、Oracle Cloud InfrastructureのData Safeコンソールに移動します。Data Safeは、左側のOracle Cloud Infrastructureメニュー内のDatabaseの下にあります。利用中のテナントでData Safeがまだ有効になっていない場合は、「Enable Data Safe」ボタンをクリックしてください。  メニューの「Private Endpoint」を選択し、「Create Private Endpoint」をクリックします。   図2 - Data Safeコンソール ここにプライベート・エンドポイントの名前、先ほど確認したデータベースのVCN、プライベート・エンドポイントを作成するサブネットの情報を入力します。前述のとおり、サブネットはデータベースと同じものでも、VCN内の別のサブネットでもかまいません。    図3 - プライベート・エンドポイントの作成 次に、この新しいプライベート・エンドポイントの名前をクリックして、詳細を確認しましょう。この詳細情報の中に、プライベート・エンドポイントに割り当てられたプライベートIPアドレスがあります。このIPアドレスは、次のステップでネットワーク・セキュリティ・ルールを構成する際に必要になります。   図4 - プライベート・エンドポイントの詳細   注:VCNに対して作成する必要のあるData Safeプライベート・エンドポイントは1つのみです。同じVCN内の複数のデータベースを接続する必要がある場合は、単純にこのData Safeプライベート・エンドポイントから、接続先となるVCN内のすべてのデータベースへの通信を許可してください。 ステップ2 – Data Safeとデータベースの通信の許可 Data Safeプライベート・エンドポイントとデータベース・ポートの通信を許可するには、VCNのセキュリティ・ルールをセットアップするか、ネットワーク・セキュリティ・グループ(NSG)を利用します。このセットアップ例ではセキュリティ・ルールを利用し、Data Safeプライベート・エンドポイント(10.0.0.6)からデータベース(10.0.0.2、ポート番号1521)への通信を許可する単純な受信/送信ルールの例を示します。   図5 - セキュリティ・ルールの例 注:データベース・ノードが複数ある場合は、それらを受信/送信ルールに追加する必要があります。また、同じVCN内の複数のデータベースをData Safeに接続する場合は、そのとおりに受信/送信ルールを更新する必要があります。 ステップ3 – データベースのData Safeへの登録  これまでにネットワーク接続をセットアップできましたので、次にデータベースをData Safeに登録する必要があります。 Oracle Cloud InfrastructureのData Safeコンソールで、「Service Console」ボタンをクリックします。Data Safe UIで、トップメニューから「Targets」を選択し、「+ Register」ボタンをクリックします。 登録ダイアログで、先ほど作成したプライベート・エンドポイントを選択し、データベースの詳細な接続情報と資格証明を入力します。データベース内にData Safe専用のデータベース・ユーザーを作成することをお勧めします。このデータベース・ユーザーに必要な権限を付与するために、登録ダイアログから権限スクリプトをダウンロードしてそれをデータベース内で実行してから、登録操作を完了することもできます。「Test Connection」をクリックすれば、すべてのセットアップが正しく完了したかを確認できます。次に、「Register Target」をクリックします。   図6 - Data Safeでのデータベース登録 詳細なステップ・バイ・ステップの説明やデータベース・サービス名の特定方法については、Data Safe User Guideを参照してください。  これで完了です。データベースをData Safeによって保護するようにセットアップできました。ここで最初にSecurity AssessmentとUser Assessmentを実行することをお勧めします。そのためにはData Safeホームページの「Security Assessment」に移動し、データベースを選択して「Assess」ボタンをクリックします。次に、この操作をUser Assessmentでも繰り返します。数分で潜在的なリスクについて示した包括的な評価レポートが作成され、リスクの軽減に取り掛かることができます。Data Safeの始め方や詳細情報についてはこちらを参照してください。   図7 - Data Safeのホームページとダッシュボード   図8 - セキュリティ評価レポートの例   ここではDatabase Cloud Serviceを例に挙げて説明しましたが、Exadata Cloud Serviceでも同様の手順になります。 Data Safeの最新情報やサポート対象データベースの拡大については、本ブログ・シリーズの今後の記事をお待ちください。Data Safeの始め方や詳細情報についてはこちらを参照してください。

※本記事は、Bettina Schaeumerによる"Simplifying Security of Exadata Cloud Service and Database Cloud Service with Oracle Data Safe"を翻訳したものです。 May 6, 2020   プライベートIPアドレスを割り当てたOracle...

津島博士のパフォーマンス講座 第76回 オプティマイザ統計の運用について(3)

津島博士のパフォーマンス講座 Indexページ ▶▶   皆さんこんにちは、今年の梅雨はすっきりしない天気の日が多いですが、これが公開される頃には関東でも梅雨が明けていますね。 今回は、オプティマイザ統計の運用の続きとして、いくつかの注意点について取り上げようと思います。後半に、Oracle Database 19c(Oracle19c)からのオプティマイザ統計収集の拡張機能についても説明していますので、参考にしてください。 1. オプティマイザ統計運用のまとめ オプティマイザ統計は、多くが自動的に収集されるようになり、使用しやすくなっていますが、オプティマイザ統計による性能問題もまだ多いように思います(自動化されたことで、意識していない方が多いのかもしれません)。そのため、効果的な運用のまとめとして、説明できていなかった以下の注意点などについて説明します。 ヒストグラムが必要な列 ヒストグラム作成の補足 共有カーソルの無効化 (1)ヒストグラムが必要な列 まずは、どのような列で列統計のヒストグラムが必要になるかについて説明します。 ヒストグラムは、フィルター条件(WHERE句の述語)や結合などで使用されている列で、以下のように使用されるものが主な候補になります(作成していない方は再検討してください)。 値の偏りがある列に対する範囲条件(RANGE、LIKE)や等価条件(EQ、EQ_JOIN) ある値だけ多いまたは少ない列になるので、条件によって件数が異なります(異なる値の数が少ない場合も、値が繰り返し使用され偏りやすくなります)。第5回でも説明しているように、これがヒストグラムの必要な理由として最も分かりやすいので、認識している方も多いと思います。 範囲の偏りがある列に対する範囲条件 ある範囲に偏りがあるため、範囲検索をすると条件によって件数が大きく異なります。よくあるのが、第46回の「文字列の範囲条件」で説明した日付をDATEデータ型以外で行っている場合です。以下の例は、どちらもヒストグラムを作成していませんが、左側がNUMBER型の年月のため見積り行数が正しくありません(これは、下2桁が01~12の値しか存在しないという偏りが発生するためです)。これを認識していないで、性能問題になっている方が多いように思います。 SQL> SELECT COUNT(*) FROM b WHERE id BETWEEN 200810  2     AND 200903; ------------------------------------------- | Id  | Operation          | Name | Rows  | ------------------------------------------- |   0 | SELECT STATEMENT   |      |     1 | |   1 |  SORT AGGREGATE    |      |     1 | |*  2 |   TABLE ACCESS FULL| B    |   108K| SQL> SELECT COUNT(*) FROM b WHERE dt BETWEEN  2  TO_DATE('200810','YYYYMM') AND TO_DATE('200903','YYYYMM'); ------------------------------------------- | Id  | Operation          | Name | Rows  | ------------------------------------------- |   0 | SELECT STATEMENT   |      |     1 | |   1 |  SORT AGGREGATE    |      |     1 | |*  2 |   TABLE ACCESS FULL| B    | 57166 | このような列は、オプティマイザの解析時に収集した、結合やフィルター条件などの情報をSYS.COL_USAGE$(列の使用状況)に登録してから、オプティマイザ統計収集(デフォルトのMETHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO')時に、データの偏りを調べて必要かを判断しています(SYS.COL_USAGE$は、第49回の「自動列グループ検出」で説明したDBMS_STATS.REPORT_COL_USAGEファンクションで確認できます)。そのため、このような列を使用しているときは、ヒストグラムを作成しないような設定はしないでしてください(CLOBやLONGデータ型などは、ヒストグラムの対象外です)。 (2)ヒストグラム作成の補足 次に、ヒストグラム作成の補足として、注意点などについて説明します。 ヒストグラムは、通常は自動的に判断して作成するので、あまり気にしていない方が多いと思います。ただし、自動的に作成されないときや最適に作成するための注意点などもあるので、そのようなことを取り上げます。 バルク・ロードのオンライン統計収集 第33回で説明したように、Oracle Database 12c(Oracle12c)からバルク・ロードでオンライン統計収集されるようになりましたが、このときヒストグラムは作成されません。そのため、以下のように指定することで(OPTIONS=>'GATHER AUTO')、基本の列統計を再収集せずに、必要なヒストグラムだけ自動作成することができます(ただし、SYS.COL_USAGE$により作成されるので、問合せが実行されていないと作成されません)。 EXEC DBMS_STATS.GATHER_TABLE_STATS('USER', 'SALES2', OPTIONS=>'GATHER AUTO'); オプティマイザ統計収集の後に、以下のSQLでヒストグラムを確認することができます。OPTIONS=>'GATHER AUTO'で作成されると、以下のようにNOTES列がHISTOGRAM_ONLYになります(STATS_ON_LOADは、オンライン統計収集が実行されたことを意味します)。 SQL> SELECT column_name,num_distinct,notes,histogram FROM user_tab_col_statistics WHERE table_name = 'SALES2'; COLUMN_NAME   NUM_DISTINCT NOTES          HISTOGRAM ------------- ------------ -------------- --------- AMOUN_SOLD             xxx STATS_ON_LOAD  NONE PROD_ID                xxx HISTOGRAM_ONLY FREQUENCY   最大バケット数 第35回で説明したように、Oracle12cからヒストグラムの最大バケット数が2048になりましたが、デフォルトは254のままになっているので、ヒストグラムを自動作成させると最大バケット数まで使用しないので注意してください。異なる値の数(NUM_DISTINCT)が大きく実行計画の見積り行数が正しくないような場合は、バケット数を増やしてヒストグラムを再作成してみてください(METHOD_OPT=>'FOR COLUMNS <列名> SIZE <バケット数>')。 上位頻度ヒストグラムとハイブリッド・ヒストグラム Oracle12cからの上位頻度ヒストグラムとハイブリッド・ヒストグラムは、ESTIMATE_PERCENTパラメータがDBMS_STATS.AUTO_SAMPLE_SIZE(デフォルト)のときだけ作成されます(AUTO_SAMPLE_SIZE以外では、これまでの高さ調整済ヒストグラムになるので注意してください)。また、AUTO_SAMPLE_SIZE の動作が、Oracle Database 11gから近似値アルゴリズム(Oracle12cからのAPPROX_COUNT_DISTINCT関数と同じ)によるコスト削減により、全表スキャン(100%のサンプル・サイズ)で行うようになり、より正確になっているので、AUTO_SAMPLE_SIZE以外を使用している方は変更を検討してください。 ヒストグラムのコピー テスト環境で作成されたヒストグラムや拡張統計を、同様の表を持つ別データベース(本番データベースなど)に適用したい場合がるかと思います。そのような方のために、別データベースからのコピー方法を紹介します(詳細は、Doc ID 2388953.1を参照してください)。これは、ヒストグラムや拡張統計が存在する表に対して、以下のようなスクリプトを生成するものになります。このようなものを一から作るのは大変なので、参考のために載せておきました。 /* ヒストグラム用のスクリプト */ EXEC dbms_stats.set_table_prefs('<スキーマ名>','<表名>','METHOD_OPT','FOR ALL COLUMNS SIZE 1,FOR COLUMNS <列> SIZE 254'); /* 拡張統計用のスクリプト */ var r VARCHAR2(50) EXEC :r := dbms_stats.create_extended_stats('<スキーマ名>','<表名>','<拡張統計情報>');   (3)共有カーソルの無効化 最後に、第27回の「共有カーソルのINVALID」の補足として、無効化のタイミングについて説明します。 共有カーソルは、オプティマイザ統計を収集すると無効化されますが、第65回の「DDLによるカーソル無効化の削減」で説明したように、大量のハード解析による性能ダウンを避けるために、DBMS_STATS.GATHER_XXX_STATSプロシージャのNO_INVALIDATEパラメータをDBMS_STATS.AUTO_INVALIDATE(デフォルト)にすることで、Oracleが自動的に無効化のタイミングを決めます(DBMS_STATS.AUTO_INVALIDATEの詳細は、Doc ID 2402871.1を参照してください)。OLTPのようなSQLが大量に実行される環境では、このようにしないとハード解析が多発して、パフォーマンス低下が発生してしまいます。この無効化されるタイミングは、デフォルトでは5時間以内のランダムな時刻に設定されるので、即時に共有カーソルを無効化したい場合でもされないため注意してください。そのため、即時に無効化したいときには、NO_INVALIDATEパラメータをFALSEにする必要があります。ただし、パラレル実行の共有カーソルは、ハード解析時間を気にしないような処理と判断して、即時無効化されるようになっているので、気にする必要はありません。 2. Oracle Database 19cの拡張機能 ここでは、Oracle19cからのオプティマイザ統計収集の拡張機能について説明します。 以下の二つの機能が、Oracle19cからオプティマイザ統計収集に拡張され、オプティマイザ統計がより正確になるようになりました。ただし、どちらもExadataだけで使用できる機能になります。 リアルタイム統計 高頻度自動オプティマイザ統計収集 (1)リアルタイム統計 これまでは、バルク・ロード(CREATE TABLE AS SELECTやダイレクト・パス・インサート)だけがオンラインでオプティマイザ統計を収集していましたが、Oracle19cのリアルタイム統計で、従来型DMLでもオンラインでオプティマイザ統計が収集されるようになりました。リアルタイム統計は、ごくわずかなオーバーヘッドで高速実行されるように、最悪な実行計画によるパフォーマンス低下を避けるため、最も重要な統計(MIN、MAX、NUM_ROWSなどのすべてのデータを参照する必要がないデータ)だけが収集されます。残りの統計は、手動または自動オプティマイザ統計収集まで延期されます。SQL文が実行されると、以下のように実行計画に出力されます(左側がINSERT文に対してリアルタイム統計が実行された場合、右側がリアルタイム統計を使用する問合せを実行した場合です)。 SQL> INSERT INTO sales2 SELECT FROM sales; ------------------------------------------------ |Id| Operation                        | Name   | ------------------------------------------------ | 0| INSERT STATEMENT                 |        | | 1|  LOAD TABLE CONVENTIONAL         | SALES2 | | 2|   OPTIMIZER STATISTICS GATHERING |        | | 3|    PARTITION RANGE ALL           |        | | 4|     TABLE ACCESS FULL            | SALES  | ------------------------------------------------ SQL> SELECT COUNT(*) FROM sales2 WHERE quantity_sold > 50; ----------------------------------- |Id| Operation           | Name   | ----------------------------------- | 0| SELECT STATEMENT    |        | | 1|  SORT AGGREGATE     |        |  |*2|   TABLE ACCESS FULL | SALES2 | ----------------------------------- Note -----    - dynamic statistics used: stats for conventional DML       明示的に統計を収集しないようにするには、以下のようにNO_GATHER_OPTIMIZER_STATISTICSヒントを指定する必要があるので、オプティマイザ統計を固定して運用しているようなシステムは注意してください。 SQL> INSERT /*+ NO_GATHER_OPTIMIZER_STATISTICS */ INTO sales2 SELECT * FROM sales; Oracle19cからALL/DBA/USER_TAB_STATISTICSビューとALL/DBA/USER_TAB_COL_STATISTICSビューにNOTES列が追加され、リアルタイム統計が実行された表や列のNOTES列に、STATS_ON_CONVENTIONAL_DMLが設定されて確認できるようになっています(以下のように、通常のオプティマイザ統計とリアルタイム統計の二つが存在しますが、パーティション・レベル統計はサポートされないので一つだけになります)。 SQL> SELECT NVL(partition_name, 'GLOBAL') partition_name, num_rows, blocks, notes  2    FROM user_tab_statistics WHERE table_name = 'SALES2’ORDER BY 1, 4; PARTITION_NAM   NUM_ROWS     BLOCKS NOTES ------------- ---------- ---------- ------------------------- GLOBAL           1837686       3315 STATS_ON_CONVENTIONAL_DML GLOBAL            918843       3315 SALES_2015             0          0 SALES_2016             0          0 … バルク・ロードのオンライン統計収集もAutonomous Databaseと同じ(表が空でなくても統計収集される)ように拡張される予定でしたが、Oracle19cでは機能拡張されませんでした。 (2)高頻度自動オプティマイザ統計収集 Oracle19cから自動オプティマイザ統計収集が、メンテナンス・ウィンドウ以外でも実行できるようになりました。以下のように、Oracle19cからのAUTO_TASK_STATUSパラメータをONにすることで、メンテナンス・ウィンドウ以外でも自動的に実行できるようになります(デフォルトはOFFです)。AUTO_TASK_INTERVALパラメータ(実行間隔)とAUTO_TASK_MAX_RUN_TIMEパラメータ(最大実行時間)も同じように設定できます exec DBMS_SPM. DBMS_STATS.SET_GLOBAL_PREFS('AUTO_TASK_STATUS', 'ON'); 以下のSQLで、AUTO_TASK_STATUSの現在の設定を確認することができます。このパラメータを有効にすると、以下のようにデフォルトで15分ごとに1時間以内で実行されるようになります。 SQL> SELECT dbms_stats.get_prefs('AUTO_TASK_STATUS') AUTO_TASK_STATUS,   2         dbms_stats.get_prefs('AUTO_TASK_INTERVAL') AUTO_TASK_INTERVAL,   3         dbms_stats.get_prefs('AUTO_TASK_MAX_RUN_TIME') AUTO_TASK_MAX_RUN_TIME   4    FROM dual; AUTO_TASK_STATUS AUTO_TASK_INTERVAL AUTO_TASK_MAX_RUN_TIME ---------------- ------------------ ---------------------- ON               900                3600 タスクの名前は、高頻度タスクがHIGH_FREQ_AUTO_TASK、標準自動タスクがAUTO_TASKになります。以下のように、DBA_AUTO_STAT_EXECUTIONSビューで自動オプティマイザ統計収集タスクのステータスを確認することができます(この出力では、高頻度収集を有効にして、最大実行時間を3分、タスクの実行間隔を4分に設定しています)。 SQL> SELECT opid, origin, status, TO_CHAR(start_time,'DD/MM HH24:MI:SS' ) AS begin_time,   2         TO_CHAR(end_time,'DD/MM HH24:MI:SS') AS end_time, completed, failed,   3         timed_out AS timeout, in_progress AS inprog 4    FROM dba_auto_stat_executions ORDER BY opid; OPID ORIGIN               STATUS   BEGIN_TIME     END_TIME       COMP FAIL TIMEO INPRO ---- -------------------- -------- -------------- -------------- ---- ---- ----- ----- 790  HIGH_FREQ_AUTO_TASK  COMPLETE 03/10 14:54:02 03/10 14:54:35  338    3     0     0 793  HIGH_FREQ_AUTO_TASK  COMPLETE 03/10 14:58:11 03/10 14:58:45  193    3     0     0 794  AUTO_TASK            COMPLETE 03/10 15:00:02 03/10 15:00:20   52    3     0     0 ... このように、いろいろ機能拡張されて、より正確なオプティマイザ統計収集ができるようになっているので、実行計画が問題になっている方などは使用を検討してみてください。 3. おわりに 今回は、オプティマイザ統計の運用の続きについて説明しましたが、少しは参考になりましたでしょうか。これからも頑張りますのでよろしくお願いします。 それでは、次回まで、ごきげんよう。     ページトップへ戻る▲    津島博士のパフォーマンス講座 Indexページ ▶▶

津島博士のパフォーマンス講座 Indexページ ▶▶   皆さんこんにちは、今年の梅雨はすっきりしない天気の日が多いですが、これが公開される頃には関東でも梅雨が明けていますね。今回は、オプティマイザ統計の運用の続きとして、いくつかの注意点について取り上げようと思います。後半に、Oracle...

しばちょう先生の試して納得!DBAへの道

.btn01 { background-color: dodgerblue; color: white !important; } .btn02 { background-color: darkorange; color: white !important; } .btnStack { font-family: Oswald; text-decoration: none; display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: normal; line-height: 1.428571429; text-align: center; white-space: nowrap !important; vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 4px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none; } a.btnStack:hover { background-color: #000; } はじめまして、こんにちは。この連載を担当する"しばちょう"こと柴田長(しばた つかさ)と申します。 日本オラクルのデータベース・スペシャリストとして、長年数多くのミッション・クリティカル案件への技術支援やお客様のシステムに最適なソリューション・デザインの提案、さらにはやパフォーマンス・トラブルの問題解決に従事してきました。数年間のマネジメントを経験ののち、2019年10月現在は、オラクルコーポレーションのデータベース開発部門の一員として、日本におけるExadataを中心としたデータベースの提案、及び、技術支援を担当しております。 これらの提案やトラブル解決を行う上で痛感していることは、SIer時代の開発現場やOracle GRID Centerでの実機検証の経験が確実に生かされているということです。経験しているからこそ、マニュアル棒読みの機能紹介では留まらず、瞬時にその機能の適用シナリオも含めて自信を持って自分の言葉(お客様に合わせた言葉)でお客様に提案できますし、早期にトラブル原因の当たりを付けたり解決のアイディアを閃いたりすることが可能になっていると思っています。 今回の連載は、正に体験して頂くことが主軸となります。単純な機能紹介ではなく手を動かして理解を深めて頂けるような連載にしていきたいと考えております。内容としては私が新人をDBAに育てる際に使用する課題をカスタマイズしたものであり、レベルとしては初級~中級を想定しております。これからDBAを目指される方、実機での作業から数年間離れられている方等々、多くの方にご活用頂ければ幸いです。 しばちょう先生(@tkssbt)をTwitterでフォローする しばちょう先生のキャリア軌跡   【最新情報】 しばちょう先生が語る!オラクルデータベースの進化の歴史と最新技術動向  #1. Oracle Databaseの進化の歴史 [YouTube][PDF]  #2. Mission Critical Systemsを実現する可用性と性能向上 [YouTube][PDF]  #3. Exadataの誕生と最新X8Mでの進化 [YouTube][PDF]   【しばちょう先生の講演資料 & 動画ダウンロード】 【Oracle Database Connect 2018】  エキスパートはどう考えるか?体感!パフォーマンスチューニングⅡ(前半) [logmi]  エキスパートはどう考えるか?体感!パフォーマンスチューニングⅡ(後半) [logmi]     【Oracle Code Tokyo 2017】  Live Challenge!! SQLパフォーマンスの高速化の限界を目指せ! [PDF] [YouTube][Iogmi]   【Oracle Database Connect 2017】  エキスパートはどう考えるか?体感!パフォーマンスチューニング [YouTube]   【Oracle DBA & Developer Day 2016】  ストレージ管理のベスト・プラクティス ~ASMからExadataまで~ [PDF]   【Oracle Database Connect 2016】  DB障害解決の極意 実体験に基づくトラブル対応と対策案 [PDF] [YouTube]   【Oracle Cloud Days Tokyo 2015】  Oracle Database 12c最新情報 ~Masimum Availability Architecture Best Practice~ [PDF]   【Oracle DBA & Developer Day 2014】  しばちょう先生による特別講義! RMANの運用と高速化チューニング [PDF]   【Oracle DBA & Developer Day 2013】  高可用性ベスト・プラクティスによるデータ破壊対策完全版 [PDF]   【Oracle DBA & Developer Day 2012】  高可用性システムに適した管理性と性能を向上させる ASM と RMANの魅力 [PDF]   【Oracle DBA & Developer Day 2011】  どこまでチューニングできるのか?最新Oracle Database 高速化手法 [PDF] 番外編 Autonomous Database = 究極のOracle Database 第55回 SQLパフォーマンスの高速化の限界を目指せ!(3) 第54回 SQLパフォーマンスの高速化の限界を目指せ!(2) 第53回 SQLパフォーマンスの高速化の限界を目指せ!(1) 第52回 AWRレポートを読むステップ4: 特定時間帯に発生する性能劣化の原因特定 第51回 AWRレポートを読むステップ3: OLTPのスループット低下の原因特定 第50回 [Oracle Database 12c R2] Oracle Database Cloud Service上にデータベースを作成 第49回 [Oracle Database 12c] ASMディスク・グループ内のディスクの置換 第48回 [Oracle Database 12c] 時間隔(インターバル)パーティション表のクセ 第47回 [Oracle Database 12c] オンライン・データファイルの移動 2016.08.26公開 第46回 [Oracle Database 12c] RMANバックアップからの表のリカバリ 第45回 Recovery ManagerのSWITCHコマンドでリストア時間ゼロ 第44回 Recovery ManagerのVALIDATEでリストア・リカバリに備える 第43回 SQL実行計画の共有とセッションのオプティマイザ関連パラメータの調査方法 第42回 [Oracle Database 12c] 拡張索引圧縮(Advanced Index Compression)を試す 第41回 [Oracle Database 12c] オンラインでのパーティション移動 第40回 AWRレポートを読むStep#2:アクセス数が多い表領域とセグメント 第39回 AWRレポートを読むStep#1:バッファキャッシュ関連の待機イベントと統計情報 第38回 Flashback Drop機能による削除表の復旧と注意点 第37回 ORAchkを使用したデータベースのヘルス・チェック 第36回 SQLのパース処理とバインド変数の理解 第35回 ASMのミラーリングによるデータ保護(2) ~高速ミラー再同期~ 第34回 ASMのミラーリングによるデータ保護(1) ~障害グループと冗長性の回復~ 第33回 ASMのリバランスの動作 第32回 標準監査の基本的な使い方 第31回 ASMのストライピングとリバランスによるI/O性能の向上 第30回 ASMディスク・グループの作成と使用量の確認 第29回 UNDO表領域の管理~パフォーマンス・チューニング~ 第28回 UNDO表領域の管理~保存期間の自動チューニング~ 第27回 パーティション表の管理~ILMにおける表データの圧縮と索引の再構成~ 第26回 パーティション化による削除処理のパフォーマンス向上 第25回 パーティション化による問合せのパフォーマンス向上 第24回 Recovery ManagerによるData Recovery Advisorの活用 第23回 Recovery Managerにおけるバックアップ時間をResource Managerで制御 第22回 Recovery Managerによる高速差分増分バックアップ 第21回 Recovery Managerによる差分増分バックアップ 第20回 フラッシュバック・データベースのオーバーヘッド 第19回 フラッシュバック・データベースによる論理障害からの復旧 第18回 表のオンライン再定義でDML文の遅延を回避 第17回 表領域の縮小とセグメントの格納位置 第16回 OLTP表圧縮によるキャッシュ・ヒット率の向上 第15回 圧縮表への変更方法と注意点 第14回 基本圧縮の効果的な活用 第13回 行移行、行連鎖を理解し性能トラブルを未然に防ぐ(2) 第12回 行移行、行連鎖を理解し性能トラブルを未然に防ぐ(1) 第11回 オプティマイザ統計情報の管理 ~ヒストグラムの効果を体験してみる~ 第10回 オプティマイザ統計情報の管理 ~保留中の統計情報を有効活用してみる~ 第9回 オプティマイザ統計情報の管理 ~統計収集の失効を制御してみる~ 第8回 オプティマイザ統計情報の管理 ~統計収集の高速化を体験してみる~ 第7回 表領域の管理方法を理解 第6回 続・SQLの実行計画からパフォーマンスの違いを読み解く 第5回 SQLの実行計画からパフォーマンスの違いを読み解く 第4回 続・データ領域管理の理解~ダイレクト・パス・インサートを試してみる~ 第3回 データ領域管理の理解~SQLチューニングにも挑戦~ 第2回 表と表領域の関係 第1回 表領域の作成と拡張 【しばちょう先生の質問コーナー】 しばちょう先生が連載記事の内容について読者の皆様からいただいたご質問にできるだけお答えします。 また、記事の感想や今後取り上げて欲しい内容などのご要望もお聞かせ下さい。お待ちしております。 しばちょう先生へのご質問方法 こちらのフォームからお願いします。 "お問い合わせ内容”欄に"しばちょう先生の記事について”と、該当する記事の号数またはURLを必ず記載してください。

はじめまして、こんにちは。この連載を担当する"しばちょう"こと柴田長(しばた つかさ)と申します。 日本オラクルのデータベース・スペシャリストとして、長年数多くのミッション・クリティカル案件への技術支援やお客様のシステムに最適なソリューション・デザインの提案、さらにはやパフォーマンス・トラブルの問題解決に従事してきました。数年間のマネジメントを経験ののち、2019年10月現在は、オラクルコーポレーショ...

第20回 DBのバックアップとリストア (DBCS/ExaCS) : もしもみなみんがDBをクラウドで動かしてみたら

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/05/18時点のものになります みなさん、こんにちは。 今回は、Database Cloud Service (DBCS) と Exadata Cloud Service (ExaCS) 上のバックアップ・リカバリについて解説していきます。 目次 Oracle Databaseのバックアップとリカバリについて Oracle Cloudでの構成パターン DBCSとExaCSの自動バックアップ機能について 自動バックアップの前提条件と設定 オンデマンド・バックアップの取得 取得されたバックアップの確認 リストア(復旧)方法 いざという時に使えるバックアップであるために まとめ よくある質問 window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-159254751-1');   1. Oracle Databaseのバックアップとリカバリについて サービスを利用していくにあたり、利用している環境のインスタンスやデータが壊れてしまった場合や、過去の時点にデータを戻したい場合など、何か起きた時のデータ復旧のためにバックアップやリカバリについての検討は重要です。ですが、設計や運用の考慮が後回しになりがちなものの1つが、バックアップ・リカバリだったりもします。とはいえ、やはりデータが壊れてしまうと作業や業務継続ができなくなり、どんなシステムでも影響は少なからずあります。こういったことから、バックアップをとるという面では「オンラインでとれること」「バックアップを素早くとれること」「簡単にとれること」が大事になってきます。 バックアップの本来の目的は、バックアップをとること自体ではなくシステムをきちんと復旧できることです。一刻も早い復旧が求められているにも関わらず、すぐに戻せないケースは少なくありません。よくあるケースとしては、「リカバリを想定した運用や手順が用意されていない」「復旧作業時のオペミスが発生し、リカバリに失敗」「いざ戻そうと思っても使えないバックアップだった」といったものがあります。そういったことからも、リカバリ面では「簡単に復旧ができる」「きちんと戻せるバックアップをとっている」ことが大事になります。Oracle Databaseのバックアップ・リカバリ手法としては、これらのことを考慮されたOracle Recovery Manager(RMAN) を使ったバックアップ・リカバリが推奨です。DBCSやExaCSでは、RMANを利用した自動バックアップが機能が利用可能で、リカバリも最新時点やPoint in Time Recovery(PITR)の任意の時点まで復旧ができます。     2. Oracle Cloudでの構成パターン Oracle Cloud上でデータベースのバックアップを取得する際の基本的な構成については、大きく分けて3つのパターンがあります。 DBシステム内に取得 : DBシステム内の、高速リカバリ領域(FRA)のRECOディスク・グループに取得するローカル・バックアップ。データベースが稼働するDBシステム上に腹持ちする形なので、リストアの速度は速いが、対応可能な障害ケースとしてはDB障害となるため範囲が限られており、DBシステムが起動しなくなった場合には使えないバックアップとなる。そのため、DBシステム内にとるとしても、システム外にバックアップをとることが推奨。 同一リージョン内のオブジェクト・ストレージに取得 : コンソールからの自動バックアップ設定での構成。バックアップは、DBシステムの障害を想定し、バックアップをDBシステムと別の場所に置くことが推奨され、リストア時間も考慮し、この構成がデフォルト。 同一リージョンと異なるリージョン内のオブジェクト・ストレージに取得 : 同一リージョン内だけでなく、遠隔地として異なるリージョン内にもバックアップを取得する構成。前述の2ケースよりも可用性が高い構成となり、大規模障害時は異なるリージョンでの復旧が可能。 クラウドの画面(コンソール)上からは、(2) 同一リージョン内のオブジェクト・ストレージに取得する構成が、簡単に設定可能です。(1)や(3)場合は、自動バックアップ設定はCLIやオブジェクト・ストレージのレプリケーション機能を利用することで設定可能となります。 マニュアル レプリケーションの使用   3. DBCSとExaCSの自動バックアップ機能について 自動バックアップは日次で初回ならびに日曜はフルバックアップ、それ以外は自動増分バックアップにて取得されます。デフォルトは、DBシステムのリージョンのタイム・ゾーンでの0時~6時に設定されますが、自動バックアップ設定時にバックアップ開始時間枠(UTC)を指定可能です。設定された時間枠内で、自動でバックアップ・ジョブが実行されます。保持期間は、7/15/30/45/60日のいずれかを選択可能です。指定した保持期間に必要なバックアップ以外の増分バックアップファイルは、自動的に削除されます。デフォルトのオブジェクト・ストレージへの取得の場合、その分のオブジェクト・ストレージの使用量が加算されます。バックアップについては、圧縮は有効、並列実行(DBCSのSE以外)など、デフォルトで設定がされています。エディションやシェイプなど、環境によって異なりますので、気になる方はRMAN の SHOW ALL コマンドで設定を確認してみてください。 なお、ExaCSのオブジェクト・ストレージでのバックアップ・リストアに関するベストプラクティスや性能に関して、下記のホワイトペーパーにまとめられているためご参照ください。 Cloud Object Storageを使用した、Oracle Cloud Infrastructure Exadataのバックアップとリストアのベスト・プラクティス 日本語 / 英語   4. 自動バックアップの前提条件と設定 まずは、設定するにあたり前提条件を確認してみましょう。オブジェクト・ストレージに取得することを前提にまとめています。DBシステム内(FRA)にとる場合など、CLI(dbcliやbkup_api)で設定する場合には、バックアップはコンソールからの管理対象外となります。 前提条件 1) 必要なエディション 自動バックアップの機能は、DBCS/ExaCSともに全エディションで利用可能 DBCSの場合、並列実行(チャネル数やセクション・サイズの指定など)や高速増分バックアップなどを使う場合にはEnterprise Edition以上が必要 特にリストア時間(RTO)の観点で、並列処理でのリストアができることはメリットになります。RTOが厳しい場合には、DBCSの場合はEnterprise Edition以上をおすすめします。 2) Oracle Cloudのインフラ側の前提条件 管理ユーザーのIAMサービス・ポリシーでの権限が付与済 DBシステムからオブジェクト・ストレージへのアクセス設定(VCNでサービス・ゲートウェイの利用がおすすめ) 3) DBシステムとデータベースの状態 自動バックアップの機能が動作するためには、データベースが下記の状態である必要があります。下記の状態ではない場合、バックアップジョブが失敗する可能性があるのでご注意ください。その他にも、DBCS・ExaCSそれぞれの前提条件の最新情報は、下記のドキュメントをご確認ください DBシステム、データベースともに使用可能な状態(起動) データベースのアーカイブログモードが有効(デフォルト有効)  Data Guardアソシエーションに含まれるデータベースの場合、プライマリ・ロールであること RMANコマンドを用いたRMANバックアップ設定を手動で変更されていないこと  など ・Oracle Cloud Infrastructure ドキュメント DBCS 前提条件 ・Oracle Cloud Infrastructure ドキュメント ExaCS 前提条件   データベース作成時の自動バックアップの設定方法 DBCSの場合はDBシステム作成時、ExaCSの場合はDBシステム作成時もしくはDB作成時に、『データベース・バックアップの構成』の項目で『自動バックアップの有効化』にチェックを入れます。そして、『バックアップ保持期間』と『バックアップ・スケジューリング(UTC)』を必要に応じて選択してください。   自動バックアップの設定内容の確認 自動バックアップの設定は、『データベース情報』から確認可能です。   データベース作成後の自動バックアップの設定・変更方法 データベースの作成後にも自動バックアップを有効化したり、設定済のバックアップ設定を変更、無効化することが可能です。 1.対象のデータベースの『データベースの詳細』ぺージの『自動バックアップの構成』をクリック 2.変更内容を選択の上、『変更の保存』をクリック   5. オンデマンド・バックアップの取得 自動バックアップとは別に、フルバックアップを任意の時点で取得することが可能です。例えば、『大きな変更を加えた直後』や『別環境への複製やリストアのため』などで、フルバックアップを取っておくことで、フル+増分バックアップをリストアするよりもリストア時間の短縮が期待できます。 1. 対象のデータベースの『データベースの詳細』ページから、リソースの中の『バックアップ』を選択し、『バックアップの作成』をクリック 2. 識別するためのバックアップの任意の『名前』を入力し、『バックアップの作成』をクリック   6. 取得されたバックアップの確認 対象のデータベースの『データベースの詳細』ページから、リソースの中の『バックアップ』を選択すると、そのデータベースのバックアップが一覧で表示されます。 また、オンデマンド・バックアップとして取得したフル・バックアップは、スタンドアロン・バックアップとして確認することができます。『ベア・メタル、VMおよびExadata』で『スタンドアロン・バックアップ』をクリックすると、スタンドアロン・バックアップの一覧が表示されます。 データベースを終了(削除)する際に、自動バックアップでとられたバックアップは削除されますが、オンデマンド・バックアップとして取得したフル・バックアップはスタンドアロン・バックアップとして残ります。上記のイメージだと、ソース・データベース"DGP"は利用中のデータベースなのでその詳細ページへのリンクがありますが、それ以外のソース・データベースは既に終了済のものになります。データベース削除後に再度バックアップを利用する可能性がある場合には、オンデマンド・バックアップを取得しておくといいでしょう。   7.リストア(復旧)方法 リストアは、リストアに必要なバックアップならびにアーカイブログやREDOログがある『最新にリストア』、任意の時間の『タイムスタンプにリストア』『システム変更番号(SCN)にリストア』のいずれかを選択可能です。また、リストア先としてバックアップ元のデータベースに対してリストアする方法と、別DBシステム上もしくはDBCS-Bare Metal(BM)やExaCSの場合は同一システム上に別データベースとしてリストアする方法があります。 バックアップ元のデータベースに対してリストア 1.対象のデータベースの『データベースの詳細』ぺージの『リストア』をクリック 2.リストアしたい地点を選択の上、『データベースのリストア』をクリック 別データベースとしてリストア 1. 「6. 取得されたバックアップの確認」のいずれかの方法でリストア対象のバックアップを表示し、『データベースの作成』をクリック 2. リストア先の『DBシステムの構成』について入力 DBCS-VMの場合は『新規DBシステムの作成』を選択し、新規でDBシステムを作成する時と同様の内容を入力。DBCS-BMの場合は『既存のDBシステムを使用』もしくは『新規DBシステムの作成』を選択し、DBシステムを選択するもしくは情報を入力 ExaCSの場合はリストア先のDBシステムを選択 3. リストア対象の『データーベースの構成』について入力   8.いざという時に使えるバックアップであるために RMANのRESTORE VALIDATEコマンドを使ったバックアップ・ファイルの検証。リストアは実行せずに、バックアップの破損などを検出することができます。検証中の負荷を考慮し、検証できるタイミングに定期的に実行していただくことが推奨です。 RESTORE VALIDATE コマンド例 [oracle@dgp ~]$ rman target / RMAN> RESTORE  DATABASE VALIDATE CHECK LOGICAL; リストア・リカバリのテスト。いざという時に復旧ができるように、定期的に復旧テストの実施をご検討ください。この場合、ソース・データベースへのリストアをしてしまうと利用中のデータベースに影響があるので、別のデータベースとして作成するという形でリストア検証をすることをおすすめします。 バックアップの・ファイルの検証についての詳細は、下記の記事もぜひご参照ください。 しばちょう先生の試して納得!DBAへの道 第44回 Recovery ManagerのVALIDATEでリストア・リカバリに備える 9.まとめ 取得されているバックアップは、元の場所にリストアするだけでなく、そのバックアップを使った新しいデータベースの作成にも有効です。「DBシステムが壊れた時の復旧方法」などの復旧手段としても使えますが、「(バックアップ地点の)複製データベースを作成したい」「開発・テスト環境を作りたい」「DBシステム間での移行の方法として使いたい」など、様々なケースで利用可能です。   よくある質問 Q1) バックアップを取る場合、別途コストがかかりますか A1) デフォルトのバックアップ取得先はオブジェクト・ストレージになりますので、オブジェクト・ストレージの使用料がかかります Q2) Oracle Database Backup Serviceというのがありますが、これはDBCSやExaCSのバックアップを利用する際に関係しますか A2) DBCSやExaCSの自動バックアップを利用する際、こちらのサービスは関係しません。Oracle Database Backup Serviceで提供されるモジュールを使ってOCIのオブジェクト・ストレージにバックアップを取得する場合、例えばオンプレミスのデータベースのバックアップなどで関係してくるサービスとなります。Oracle Database Backup Serviceについてはこちら Q3) 自動バックアップが失敗していたのですぐに対処したのですが、取れる状態になればリトライされますか A3) 自動バックアップが失敗した場合、次の日のバックアップ・ウィンドウで再試行が行われます。   関連リンク ・Oracle Cloud Infrastructure ドキュメント DBCS データベースのバックアップ ・Oracle Cloud Infrastructure ドキュメント ExaCS Exadataデータベースのバックアップの管理 もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/05/18時点のものになります みなさん、こんにちは。 今回は、Database Cloud Service (DBCS) と Exadata Cloud Service (ExaCS) 上のバックアップ・リカバリについて解説していきます。 目次 Oracle Databaseのバックアップとリカバリについて Orac...

OCI

Cloud Shell からOracle Autonomous Databaseを使う

※本記事は、Andy Tael (Senior Principal Product Manager)による"Oracle Cloud Infrastructure Cloud Shell and Oracle Autonomous Database"を翻訳したものです。 2020年4月30日 本ブログでは、Oracle Cloud InfrastructureのCLIとOracle Cloud Infrastructureのクラウド・シェルにあるSQL*Plusを使用して、Oracle Autonomous Database(無償版)を作成し、接続するステップを紹介します。なお、これを実施するには、無償アカウントに登録しておく(または既存のテナンシを使用)必要があり、また本ブログのステップに相応する、Linuxのシェルに関する基本的なスキルが必要です。 クラウド・シェルは、Oracle CloudコンソールからアクセスできるWebブラウザ・ベースのターミナルで、Oracle Cloud Infrastructureの全ユーザーが利用可能です。無償にて利用可能(月次テナンシの範囲内で)で、事前承認されたCLIにてLinuxのシェルにアクセスできます。また Oracle Cloud Infrastructureのチュートリアルおよびラボで活用できる便利なツールも提供されます。クラウド・シェルは永続フレームとしてコンソールに表示され、ユーザーがコンソールの別のエリアへ移動している間も常にアクティブです。 Oracle Cloud InfrastructureのAutonomous Databaseはフル・マネージドの事前構成済みデータベース環境で、「Autonomous Transaction Processing」と「Autonomous Data Warehouse」の2種類のワークロードがあります。ハードウェアの構成や管理も、ソフトウェアのインストールも不要です。プロビジョニング後は、可用性やパフォーマンスに影響を与えることなく、いつでもデータベースのCPUコア数やストレージ容量を増やすことができます。 無償アカウントを作成したら(必要であれば)、以下のステップにて、クラウド・シェルを使用してAutonomous Databaseを作成し、接続します。各ステップの詳細は、このブログの後半をご覧ください。 ご利用のテナンシにてクラウド・シェルを開きます。 ステップの簡略化とタイプミス回避のため、環境変数を設定します。 Autonomous Databaseをデプロイするコンパートメント用にOracle Cloud識別子(OCID)を取得します。 Autonomous Databasを作成します(この例では、「Autonomous Transaction Processing」タイプとFree)。 Autonomous Database用にOCIDを取得します。 ウォレット・ファイル用のディレクトリ構造を作成し、ウォレットをダウンロードします。 Autonomous Databaseに接続します。 以下のステップで使用するコマンドはすべて、GitHubレポジトリからダウンロードできます。クラウド・シェル環境にはGitもあるため、レポジトリをクラウド・シェル環境に複製することも可能です。 また tmuxを使用すれば、セッションを失うこともありません。こちらがその早見表になります。   ステップ1:クラウド・シェルを開く Oracle Cloud Infrastructureのテナンシにサインインし、コンソールのヘッダーにあるコマンド・プロンプト・アイコンをクリックします。 これでクラウド・シェル環境が作成され、コマンドを実行したりAutonomous Databaseに接続したりすることが可能になります。 また、以下のようにURLを作成してクラウド・シェルを直接開くという方法もあります。この場合、<region>と<tenancy>には実際の値を入れる必要があります。 https://console.us-<region>-1.oraclecloud.com/a/<tenancy>?cloudshell=true   ステップ2:Gitレポジトリを複製し、環境変数を設定する Gitレポジトリを複製します。Gitはクラウド・シェルに事前インストールされています。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ git clone https://github.com/andytael/oci_cs_adb クラウド・シェルのGitレポジトリを変更してしまわないよう、ディレクトリを作業用ディレクトリに変更してから、リモートのオリジナルを削除します。(ブランチを作成し、レポジトリに加えたり、レポジトリを改善したりすることは自由です) andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ cd oci_cs_adb andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ git remote remove origin Gitの作業用ディレクトリには以下のファイルがあるはずです。 env_vars.sh:環境変数を設定します。 get_ocid_comp.sh:コンパートメント用にOCIDを取得します。 create_atp_free.sh:Autonomous Database(Autonomous Transaction Processing)を作成します。 get_adb_ocid.sh:作成したAutonomous Database用にOCIDを取得します。 get_wallet.sh:作成したAutonomous Database用にウォレットを取得します。 get_conn_string.sh:このファイルは使用しません。 README.md:このファイルは使用しません。これはGitHubレポジトリの一部です。 「vim」を使用して「env_vars.sh」ファイルを編集し、「comp_name」変数および「db_name」変数を設定します。続いてファイルを保存します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ vi env_vars.sh 環境変数を設定します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source env_vars.sh   ステップ3:コンパートメント用にOCIDを取得する クラウド・シェルにはOracle Cloud InfrastructureのCLIが事前インストールされているので、このCLIを使用してデータを取得します(さらにリソースを作成します)。Autonomous Databaseをデプロイするコンパートメント用にOCIDを取得します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source get_ocid_comp.sh このコマンドは、コンパートメントのOCIDを返します。OCIDを返さない場合は、環境変数を確認してください。   ステップ4:Autonomous Databaseを作成する 以下のコマンドを実行して、「ocid_comp」にて指定された(ステップ3)コンパートメントにAutonomous Database(Autonomous Transaction ProcessingまたはOLTP)を作成します。名前および表示名(コンソールにて使用)は「db_name」変数にて指定します。データベースの管理者用パスワードは「db_pwd」変数にて指定します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source create_atp_free.sh   このプロセスには数分かかることがあります。Autonomous Databaseの作成状況を確認するには、「check_atp_status.sh」スクリプトまたはコンソールを使用します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source check_atp_status.sh   ステップ5:Autonomous Database用にOCIDを取得する ウォレット・ファイルをダウンロードするには、Autonomous Database用のOCIDが必要です。次のコマンドを実行します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source get_adb_ocid.sh   ステップ6:ウォレット・ファイル用のディレクトリ構造を作成し、ウォレットをダウンロードする Autonomous Databaseに接続するには、ウォレット・ファイルが必要です。次のコマンドを実行し、必要なディレクトリ構造を作成し、ウォレットを解凍します。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source get_wallet.sh   ステップ7:Autonomous Databaseに接続する 次のコマンドを実行し、ウォレット・ファイルが置かれている場所を反映するように、「sqlnet.ora」ファイルを編集します。オリジナルの「sqlnet.ora」ファイルのコピーは「$TNS_ADMIN」に保存されます。 andy_tael@cloudshell:oci_cs_adb (us-ashburn-1)$ source fix_sqlnet.sh   すべてが正しく実行された場合、クラウド・シェルにて次のコマンドを実行することで、Autonomous Databaseに接続できます。 sqlplus admin/${db_pwd}@${conn_string}   QUITコマンドにて、SQL*Plusから退出します。 お疲れさまでした。 @

※本記事は、Andy Tael (Senior Principal Product Manager)による"Oracle Cloud Infrastructure Cloud Shell and Oracle Autonomous Database"を翻訳したものです。 2020年4月30日 本ブログでは、Oracle Cloud InfrastructureのCLIとOracle...

Developers

Kubernetesセキュリティの5つのベス ト・プラクティス

※本記事は、Manish Kapur [Director(Oracle Cloud Platform)]による"5 Best Practices for Kubernetes Security"を翻訳したものです。 2020年4月15日 Kubernetesはこの3年で急速に利用者を拡大し、多くの企業にて本番環境にデプロイされています。Kubernetesは基本的にコア・ソフトウェアのセキュリティ原則に従いますが、一部のセキュリティ要素はエンド・ユーザーが責任を担うことになります。クラウド・プロバイダとユーザーとの間で、セキュリティ責任が共有されるのと同様、クラウド・プロバイダから提供されるマネージドKubernetesサービスにも、共有されるセキュリティ責任があります。Oracle Cloud Infrastructure Container Engine for Kubernetes(Oracle Kubernetes EngineまたはOKEとも表記される)やAzure Kubernetes ervice(AKS)といったマネージドKubernetesサービス・クラウド・プロバイダは一般的に、Kubernetesクラスタのコントロール・プレーン(APIサーバー、スケジューラ、etcd、コントローラ)の運用および安全性に責任を負い、一方でマネージド・サービスのユーザーは、データ・プレーン(ノード・プール、受信、ネットワーキング、サービス・メッシュなど)の安全性に責任を負います。 私はKubernetes Oracle Linux VagrantのBoxを使用して、3年前からKubernetesに携わるようになりました。そうした私の経験を基に、本ブログでは、Kubernetesの5つのセキュリティ・プラクティスを紹介したいと思います。  ロール・ベースのアクセス制御(RBAC)を使用する Secretは必ずシークレットにする ノードとポッドの安全性を確保する コンテナのセキュリティ・リスクを排除する 監査、ロギング、モニタリングは必須 では、それぞれについて説明します。   ロール・ベースのアクセス制御(RBAC)を使用する ロール・ベースのアクセス制御(RBAC)では、Kubernetes APIにアクセスできるユーザーおよびその権限レベルを制御できます。Kubernetesでは通常、RBACがデフォルトで有効になっています。しかし、RBACが有効になっていない非常に古いKubernetesからアップグレードした場合は、念のためRBACの設定にチェックを付けて、これを有効にする必要があります。ただ、RBACを有効にするだけでは十分ではありません。権限許可のポリシーも管理し、これを適切に使用する必要があります。RBACを使用すると、ユーザーやユーザー・グループを限定して、必要なアクションやタスクにしかアクセスできないようにすることができます。その際は、最小権限の原則に従い、ユーザーおよびKubernetesのサービス・アカウントには必要最小限の権限のみが付与されるようにします。またクラスタ全体を対象にした権限は付与しないようにし、絶対的に必要でない限りクラスタ管理者の権限は誰にも付与しないようにします。詳細は、Kubernetesの公式のRBACドキュメントを参照してください。Oracle Container Engine for Kubernetesを使用して作成・管理されるKubernetesクラスタの運用では、Oracle Cloud Infrastructure Identity and Access Management(OCI IAM)にて完全なアクセス制御が可能です。詳細は、こちらのドキュメントにてご確認ください。   Secretは必ずシークレットにする Secretには、パスワードやトークン、sshキーといった重要なデータが含まれています。キーやパスワード、トークンなどのアーティファクトにてポッドを安全に起動するためです。そのためポッドを起動するには、通常、そのSecretにアクセスする必要があります。サービス・アカウントが作成されると、その認証トークンを保存するSecretが自動的に生成されます。またKubernetesでは、Restでの暗号化をサポートしています。これによりetcdのSecretリソースが暗号化されるため、etcdのバックアップにアクセスされ、そのSecretの内容を閲覧されるといったことを回避できます。バックアップが暗号化されていない場合や、攻撃者によってetcdへの読み取りアクセスが取得されている場合には、暗号化によって防御レベルが強化されます。またこちらで説明されているように、SSL/TLSを使用して、ユーザーとAPIサーバー間の通信およびAPIサーバーからKubeletへの通信を保護するようにします。Secretや資格証明の使用期限を短くして、攻撃者によるこれらの使用を難しくすることも、推奨されます。資格証明の使用期限を短く設定し、資格証明のローテーションを自動化することもお勧めです。なお、KubernetesクラスタのSecretにアクセスする必要のあるサードパーティとの統合がある場合は、注意が必要です。この場合は、必要なRBACの権限とアクセス権を慎重に検討する必要があります。さもないと、クラスタのセキュリティ・プロファイルが漏洩してしまうかもしれません。Oracle Kubernetes Engineを使用している場合は、Encrypting Kubernetes Secrets at Rest in Etcdにて詳細をご確認ください。   ノードとポッドの安全性を確保する ノード:Kubernetesのノードは、仮想マシンまたは物理マシンになり得るワーカーノードで、一般的にLinux OSにて動作します。ノードにて実行されるサービスには、コンテナ・ランタイム、kubelet、kube-proxyがあります。ノードで実行されるOSの堅牢性と安全性を確保することは重要であり、これはクラウド・プロバイダとKubernetesの管理者の責任になります。Oracle Kubernetes Engine(OKE)のノードでは、Oracle Linuxイメージの強化が必要です。したがってノードがユーザーによってプロビジョニングされたら、それ以降は、Kubernetesの管理者が、このノードで実行されるOracle Linuxイメージに定期的にセキュリティ・パッチを適用していく必要があります。マネージドOKEでは、このノードにCenter for Internet Security(CIS)のKubernetesのベンチマークも使用しています。OSのセキュリティ強化に加え、ノードをプライベート・ネットワークに置き、インターネット経由でアクセスされないようにすることも重要です。必要であれば、クラスタ・ネットワーク外のその他のサービスへのアクセス用にゲートウェイを設定しても良いでしょう。またノード上のネットワーク・ポート・アクセスは、ネットワーク・アクセス・リスト経由で制御する必要があります。ノードへのセキュアシェル(SSH)アクセスを制限することもお勧めです。詳しくは、Oracle Kubernetes Engineのノード・プール・セキュリティに関するドキュメントをご覧ください。 ポッド:ポッドは、ノードで実行され、共有ストレージ/ネットワークを使用する、1つ以上のコンテナの集まりです。デフォルトでは、どのノードでポッドを実行するかに関する制限は付与されていません。クラスタ内でのポッドの通信に関するルールを定義するには、ネットワーク・ポリシーを使用します。ネットワーク・ポリシーを導入するには、ネットワーク・プラグインが必要です。またネットワーク・ポリシーの使用に際しては、これをサポートしているネットワーク・ドライバが必要な場合もあります。Oracle Kubernetes Engineには、クラスタ内のワポッドのノードへの割り当てを制御するポリシーークロードとの間の通信を保護するための オプションが複数あります。ベストなネットワーク・セキュリティ態勢を確立するには、ネットワーク・ポリシー(ポッドレベルの安全なネットワーク通信のため)とセキュリティ・リスト(ホストレベルの安全なネットワーク通信のため)を組み合わせて評価する必要があります。ポッド・セキュリティ・ポリシーでは、たとえばコンテナの特権コンテナとしての実行や、ホスト・ファイル・システムやネットワーク、ポートの使用など、ポッドのランタイム実行プロパティを制御することができます。デフォルトでは、ポッドはクラスタ内のどのノードにもスケジュールできます。Kubernetesには、ポッドのノードへの割り当てを制御するポリシーやテイント・ベースのポッドの配置および排除など、ノードへのポッドの割り当てを制御する方法が複数あります。Oracle Kubernetes Engine(OKE)を使用している場合は、こちらのドキュメントの説明にあるとおり、クラスタ向けにポッド・セキュリティ・ポリシーを設定することができます。   コンテナのセキュリティ・リスクを排除する アプリケーションはコンテナ・イメージ(一般的に、Dockerイメージという)としてパッケージされています。コンテナ・イメージ(Dockerイメージ)はコンテナ・レジストリに保存され、そこから抽出されます。またポッド内にてランタイム・コンテナとしてインスタンス化されます。セキュリティは、開発プロセスの最初の時点、つまりアプリケーション用にコンテナ・イメージを構築するためにソース・コードおよびライブラリの作業を実施している時点で、設計原則に組み込まれている必要があります。セキュリティ・プラクティスは、CI/CDツール・チェーンに、またコンテナ・イメージの構築、保存、デプロイのプロセス全体に、導入します。これには、コンテナ・イメージの安全な保存、セキュリティ上の脆弱性を検出するためのイメージのスキャン、コンテナのランタイム・セキュリティの管理などが含まれます。アプリケーションの構築にてサードパーティのライブラリを使用する場合は、DevSecOpsサイクルの一環として、そうしたライブラリの脆弱性スキャンを自動化して実行するのも良いアイデアです。Oracle Kubernetes Engineを使用している場合は、NeuVectorやTwistlockといったオラクルのパートナー・ソリューションを参照することもできます。またDockerイメージおよびコンテナの作成においては、強化されたslimのOSイメージを使用し、アプリケーションを使用するユーザーには、コンテナでのプロセスの実行に必要な最小限のOS権限のみが付与されるようにします。その他にも、ソース・イメージにセキュリティ・アップデートを定期的に適用し、それらをアップデート済みのコンテナとして再度デプロイすることも大切になります。さらに、適切なアクセス・コントロールおよびポリシーを用いて、Oracle Cloud Infrastructure RegistryのようなプライベートなDockerレジストリを使用し、コンテナ・イメージの管理を統制することも重要です。コンテナ・イメージへの署名により、コンテナのコンテンツの信頼性を体系的に維持していくことも、推奨されます。   監査、ロギング、モニタリングは必須 監査、ロギング、モニタリングはセキュリティの重要な要素であり、これらによりクラスタのセキュリティ態勢を強化できるため、見過ごすべきではありません。Kubernetes監査ログは、Kubernetes APIサーバーに対して行われた各呼び出しを詳細に記録します。こうした監査ログは、クラスタで起こったことに関する有益な情報になるだけでなく、監査やコンプライアンス、セキュリティの分析にも使用できます。Kubernetes監査のレコードには、一連のアクティビティがすべて記録されたセキュリティ・レコードが含まれているため、異常行動や重要なリソースへのアクセスを検出するのにも役立ちます。したがって不正アクセスなどが発生した場合の分析のため、監査ロギングを有効にし、監査ログを安全なレポジトリに保管することをお勧めします。Kubernetesには、コンテナのアクティビティをセントラル・ロギング・サブシステムに記録するための、クラスタ・ベースのロギング機能もあります。Kubernetesクラスタにある各コンテナの標準出力および標準エラー出力は、各ノードで実行されるFluentdなどのエージェントを使用して、Elasticsearchなどのツールに取り込み、Kibanaにて確認することが可能です。そうすることで、クラスタのコンテナ、ポッド、アプリケーション、サービスなどのコンポーネントをモニタリングすることが可能になります。クラスタのモニタリング、可視化、および追跡には、Prometheus、Grafana、Jaegerなどのツールを使用できます。 Oracle Cloud Infrastructure Container Engine for Kubernetes(Oracle Kubernetes EngineまたはOKEとも表記される)を使用している場合は、Oracle Cloud Infrastructureセキュリティ・ガイドおよびOracle Kubernetes Engineの安全性確保に関するその他の推奨事項も参照してください。OKEはOracle Cloud Infrastructure(OCI)のIdentity and Access Management(IAM)とも統合されています。IAMは、ネイティブのOCIのユーザー認証機能を使用しており、認証に関する総合的な安全性の向上に寄与します。 Kubernetesクラスタの安全性を今日から強化しましょう。  

※本記事は、Manish Kapur [Director(Oracle Cloud Platform)]による"5 Best Practices for Kubernetes Security"を翻訳したものです。 2020年4月15日 Kubernetesはこの3年で急速に利用者を拡大し、多くの企業にて本番環境にデプロイされています。Kubernetesは基本的にコア・ソフトウェアのセキュリティ原則に...

OCI

Trend Microが設計した、新しい開発 プロセスのためのセキュリティ

※本記事は、Gilson Melo (Senior Principal Product Manager)による"How Trend Micro Designs Security for the New Development Process"を翻訳したものです。 2020年4月30日 当社では以前から変わらずオープン・スタンダードを重視し、また幅広く多様なエコシステムをサポートしています。そうした当社にとって、Trend Microが同社のCloud OneのコンポーネントであるContainer Securityのサポート対象をOracle Cloud Infrastructure Container Engine for Kubernetes(OKEと表記)にまで拡大したことは、非常に喜ばしいことです。 本ブログでは、Trend Microで戦略的アライアンスのシニア・ディレクターを務めるManish Patel氏による文章を掲載しています。 コンテナは、アプリケーション開発のスピードと機動力に寄与します。その一時的な対応が可能という特徴からは、オンデマンドのリソース消費と基盤インフラストラクチャの抽象化というメリットを得られます。またコンテナ化したアプリケーションをクラウドへデプロイするうえで、拡張性と信頼性が高く、フル・マネージドのサービスを必要とする場合、Container Engine for Kubernetesを活用することで、クラウド・ネイティブのアプリケーションを高い信頼性にて構築、デプロイ、および管理することができます。 しかしコンテナを採用する場合、コンテナをデプロイする前に、また実行時にも、脆弱性を検出し、インフラストラクチャ・スタック全体を保護できる、包括的なセキュリティ・モデルを導入する必要があります。つまりこの新しい開発モデルでは、「シフトレフト」がいっそう重要になるということです。コンテナを使用した新しいアプリケーションをオンプレミスでデプロイする場合も、クラウドへデプロイする場合も、ソフトウェア開発のライフサイクル(SDLC)全体でセキュリティを考慮する必要があります。 しかし組織は一般的に、一部のアプリケーションをコンテナ化し、一部を物理的インフラストラクチャで仮想化または実行し、また一部を複数のクラウドで実行するという具合に、異種混合環境にあります。たとえばOracle Cloud Infrastructureを使用したり、仮想化されたデータセンターにてLinuxディストリビューションを使用したりといったことです。そうした環境でセキュリティ・ソリューションを個別に導入していると、コストが嵩むだけでなく、それらを管理するためのスキルとリソースも必要になり、また適切に構成・管理できなければ、セキュリティの一貫性も確保できず、脅威にさらされる結果になります。また継続的インテグレーション/継続的デリバリ(CI/CD)、コンテナ、サーバーレスといった最新の開発手法や開発技術においては、DevOpsのスピードを維持しつつ、早期検出と迅速なプロテクションを実現し、さらにクラウド・サービスにてセキュリティのベスト・プラクティスが遂行されているという確証が得られるように、セキュリティ体制を確立する必要があります。   Trend Microのソリューション Trend MicroのCloud Oneは、ソフトウェア開発プロセスの現代化に取り組む組織に適したセキュリティ・サービス・プラットフォームです。この総合的なソリューションには、データセンターやクラウド、コンテナも含め異種混合環境全体に一貫したプロテクションを提供する、統合されたセキュリティ・コンポーネントが複数あります。 図1:異種混合環境におけるCloud OneのWorkload Security   Trend MicroのContainer Securityアーキテチャ Cloud OneのコンポーネントであるContainer Securityは、Oracle Cloud Infrastructure Registry(OCIR)と組み合わせることで、自動ビルド・パイプラインのコンテナ・イメージとレジストリのスキャンが可能になります。またContainer Securityは開発および運用チーム向けに設計されているため、オープン・ソース・コードの依存関係で発見されるものも含め、マルウェア、シークレットとキー、コンプライアンス違反、脆弱性を早期に、また迅速に検出できます。 図2:Cloud OneのContainer Securityにて、開発プロセス全体にセキュリティを適用   さらにContainer Securityは、パッケージ・マネージャーにてインストールされたアプリや、Trend Microの業界トップクラスのルールー・フィードを使って直接インストールされたアプリにおける脅威の検出にも活用できます。またContainer Securityでは、Snykのオープンソース脆弱性データベースにてさらに左側を充実させることができるため、オープン・ソース・コードの依存関係における脆弱性を早期に発見し、排除することができます。このようにContainer Securityを活用することで、DevOpsチームは、本番対応のアプリケーションを継続的に提供し、ビルド・サイクルに中断を招くことなくビジネス・ニーズに応えることが可能になります。   高度なイメージ・スキャン Trend MicroのContainer Securityでは、スキャンの際、イメージの各レイヤーをアンパックし、コンテンツの詳細なスキャンを実行します。これにより、パッチ・レイヤーとそのイメージにある脆弱なパッケージとを関連付けることで、問題を早期に解決し、誤判定を排除することができます。またContainer Securityでは、Snykの検出機能を使用して、イメージをスキャンし、マルウェアを検出したり、脆弱性を評価したり、シークレット(秘密鍵やパスワードなど)、ポリシーへの準拠、オープン・ソース・コードの脆弱性を確認したりします。   継続的なプロテクション Container Securityでは、最初にイメージが構築されたときにスキャンを実施できます。さらにレジストリでも継続的にスキャンを実施して、本番対応のイメージにおける新たなマルウェアや脆弱性を発見できるようにします。したがって、イメージの安全性を構築段階から確保し、さらに将来的な未知の脅威からも保護することができます。イメージのスキャンは、複数のクラウド環境に対し、単一のContainer Securityデプロイメントから実施できます。   APIでの自動プロセス Container Securityでは、CI/CDパイプラインへの統合のために作成されたAPIの総合的なカタログを使用することで、完全な自動製品機能を提供します。また、アプリケーション・アーキテクトおよび開発者は、ビルド・パイプラインにセキュリティをコードとして組み込んで、コンテナ・イメージとレジストリのスキャンを実施することができます。ソフトウェア・ビルド・パイプラインの早期から効果的なセキュリティを組み込むことで、開発サイクルにて一貫した成果を早期に創出しやすくなり、また手動によるセキュリティ操作やアプリケーションのダウンタイムを削減することも可能になります。   コンプライアンス対応のプロテクション Container Securityにより、セキュリティ・エンジニアは生産性にもCI/CDパイプラインにも影響を与えることなく、コンプライアンス要件を遵守することができます。さらにポリシー遵守のスキャンができるだけでなく、コンプライアンス要件や行政要件に合わせてポリシーをカスタマイズすることも可能です。加えて、レポート作成や監査を容易にする、詳細なログ履歴も提供されます。 図3:わかりやすいスキャン結果   ワールドクラスの脅威フィード Container Securityでは、Trend Microのプライベート・ソース(Trend Micro Smart Protection Network)およびパブリック・ソースから最新の脅威フィードを取得し、ゼロデイ脅威を検出するための自動および学習アルゴリズムを実装します。   Cloud Oneプラットフォームの全体像 Container Securityは、Trend MicroのCloud Oneプラットフォームから提供される1つのコンポーネントにすぎません。このプラットフォームには、他にも次のようなコンポーネントがあります。    Workload Security:ワークロードのランタイム・プロテクション(仮想、物理、クラウド、コンテナ) File Storage Security:クラウド・ファイルおよびオブジェクト・ストレージ・サービスのためのセキュリティ Application Security:サーバレス機能、API、およびアプリケーション向けセキュリティ Network Security:クラウド・ネットワーク・レイヤーのIPSセキュリティ Conformity:クラウド・セキュリティとコンプライアンス体制の管理   詳細情報 詳細については、Container Engine for Kubernetesの概要およびTrend MicroのContainer Securityのページを参照してください。Trend MicroのCloud OneのWebサイトでは、ハイブリッド・クラウド・セキュリティのためのすべての機能が紹介されています。Trend MicroのContainer SecurityとContainer Engine for Kubernetesの組み合わせを体験してみたい場合は、Oracle Cloud Infrastructureのアカウントを取得し、Trend MicroのContainer Securityの無償トライアルにお申込みいただければ、すぐにテストを開始できます。

※本記事は、Gilson Melo (Senior Principal Product Manager)による"How Trend Micro Designs Security for the New Development Process"を翻訳したものです。 2020年4月30日 当社では以前から変わらずオープン・スタンダードを重視し、また幅広く多様なエコシステムをサポートしています。そうした当社に...

Java Magazine

ガベージ・コレクタを理解する

※本記事は、Christine H. Floodによる"Understanding Garbage Collectors"を翻訳したものです。 デフォルト・ガベージ・コレクタの動作の仕組み   著者:Christine H. Flood 2019年11月21日   ガベージ・コレクション(GC)は、使用されなくなった再利用可能なメモリを自動的に回収する方法の1つです。オブジェクトを手動で割り当てて破棄する他の言語とは異なり、GCがあれば、Javaプログラマーが各オブジェクトを取得してその必要性を判断するための検査を行う必要がなくなります。すべてを知り尽くした掃除係のようなGCプロセスが裏で音もなく動作して、使い道のなくなったオブジェクトを破棄し、整理します。このような整理整頓によってプログラムが効率化します。 本記事は、2016年3月/4月号に掲載のJava Magazineに掲載された「OpenJDKの新しいガベージ・コレクタ」という記事の内容を最新のものに改め、その一部を省略したものです。   GCとは JVMはプログラムのデータをオブジェクトとして構造化します。オブジェクトにはフィールド(データ)が含まれており、そのデータがヒープと呼ばれる管理されたアドレス空間に配置されます。以下のJavaクラスについて考えます。このクラスは、単純なバイナリ・ツリー・ノードを表しています。 class TreeNode { public TreeNode left, right; public int data; TreeNode(TreeNode l, TreeNode r, int d) { left = l; right = r; data = d; } public void setLeft(TreeNode l) { left = l;} public void setRight(TreeNode r) {right = r;} }   次に、このクラスに対して以下の操作を実行します。 TreeNode left = new TreeNode(null, null, 13); TreeNode right = new TreeNode(null, null, 19); TreeNode root = new TreeNode(left, right, 17);   このコードでは、位置17をルートとし、位置13に左側のサブノードを、位置19に右側のサブノードを持つバイナリ・ツリーを作成しています(図1)。 図1:3つのノードによるツリー   この後さらに、右側のサブノードを以下のように置き換えます。この操作により、位置19のサブノードはどのノードにも接続していないガベージ(ゴミ)になります。 root.setRight(new TreeNode(null, null, 21));   この結果、図2のような状況になります。 図2:図1のツリーから1つのサブノードを置き換える   ご想像のとおり、データ構造の構成と操作の過程で、ヒープは図3のようになっていきます。 図3:多くの未使用のデータ項目が含まれるヒープ   データのコンパクションとは、メモリ内のアドレスを変更することを表します。Javaプログラムは、オブジェクトが特定のアドレスにあることを想定しています。ガベージ・コレクタがオブジェクトを移動した場合、Javaプログラムはその新しい位置を把握する必要があります。もっとも簡単な方法は、すべてのJavaスレッドを停止し、すべてのオブジェクトのコンパクションを行い、古いアドレスの参照をすべて新しいアドレスを指すように更新してから、Javaプログラムを再開することです。しかし、この方法ではJavaスレッドが実行されない期間(GC一時停止時間)が長くなります。 アプリケーションが長く稼働しない状態は、Javaプログラマーにとって好ましくありません。GC一時停止時間を減らすための一般的な手法は2つあります。GC関連の文献では、それぞれコンカレント・アルゴリズム(プログラムの実行中にGC処理を行う)およびパラレル・アルゴリズム(Javaスレッド停止中のGC処理時間を短縮するため、スレッド数を増やす)と呼ばれています。JDK 8のOpenJDKのデフォルト・ガベージ・コレクタはパラレル・アルゴリズムを採用しています(このガベージ・コレクタは、コマンドラインで-XX:+UseParallelGCを追加して手動で指定できます)。パラレル・ガベージ・コレクタは、多数のGCスレッドを利用して並外れたスループットを実現します。   パラレル・ガベージ・コレクタ パラレル・ガベージ・コレクタは、オブジェクトが「生き残って」きたGCサイクル数に応じて、オブジェクトを2つの領域(若い世代の領域と古い世代の領域)に振り分けます。若いオブジェクトはまず若い世代の領域に割り当てられます。その後、コンパクションの段階で、若い世代にコレクションされてきた回数が確認され、一定回数以下であればそのまま若い世代の領域に留まります。この回数を超えて生き残っているオブジェクトは、古い世代へと移動されます。ヒープ全体のコレクションには時間がかかりすぎるため、そのために一時停止するよりも、ヒープの中で生存期間の短いオブジェクトが含まれる可能性の高い部分のみをコレクションすればよい、という論理です。最終的には、古いオブジェクトもコレクションが必要になります。 ガベージ・コレクタが若いオブジェクトのみをコレクションするためには、古い世代のどのオブジェクトが若い世代のオブジェクトを参照しているかを把握する必要があります。さらに、新しいオブジェクトの新しい位置を参照するように古いオブジェクトを更新する必要があります。そのために、JVMはカード・テーブルと呼ばれる概要データ構造を管理します。古い世代のオブジェクトに参照が書き込まれるときは常に、カード・テーブルに印が付けられます。そうすることで、次に若い世代へのGCサイクルが発生したときに、JVMがこのカードをスキャンして、古い世代から若い世代への参照を探すことができます。パラレル・ガベージ・コレクタはこのような参照を把握しているため、取り除くべきオブジェクトや更新すべき参照を特定できます。パラレル・ガベージ・コレクタは複数のGCスレッドを用いて、プログラムの一時停止中のGC処理時間を短縮します。   ガベージ・ファースト・ガベージ・コレクタ G1(ガベージ・ファースト)と名付けられたJDKガベージ・コレクタは、パラレル・スレッドとコンカレント・スレッドの両方を利用します。G1はコンカレント・スレッドを用いて、生きているオブジェクトをJavaプログラムの実行中にスキャンします。また、パラレル・スレッドを用いてオブジェクトをすばやくコピーし、一時停止時間を短縮します。 G1はヒープを多数の領域に分割します。プログラム実行中の任意の時点において、各領域は、古い世代と若い世代のいずれかの領域になります。若い世代の領域はCG一時停止時間が発生するごとにコレクションする必要がありますが、古い世代の領域については、ユーザーが指定した一時停止時間目標以内にコレクションできると予測した数だけ、柔軟にコレクションします。G1はこの柔軟性によって、ガベージのもっとも多いヒープ領域に集中して、古いオブジェクトのGC処理を実行できます。また、G1はユーザーが指定した一時停止時間に基づいて、コレクションによる一時停止時間を調整することもできます。 図4に示すとおり、G1はオブジェクトを自由にコンパクションして新しい領域に配置できます。   図4:G1実行前と実行後。領域1と領域2は領域4へとコンパクションされます。新しいオブジェクトは領域4に割り当てられます。領域3は、再利用(可能な)スペースが少なすぎる(30%)のに対してコピー処理が多すぎる(70%)ため、処理されません。 G1は、各領域で生きているデータの量と、その生きているデータのコピーにかかるおおよその時間を把握しています。ユーザーが一時停止時間を最短に抑えたい場合、G1はごく少数の領域のみをコレクションすることができます。ユーザーが一時停止時間を気にしていない場合や、一時停止時間目標をかなり長く設定している場合、G1はコレクションする領域を増やす可能性もあります。 G1は、若い世代の領域のみをコレクションできるように、カード・テーブルのデータ構造を管理する必要があります。また、他の古い世代の領域から参照されている、古い世代の領域それぞれに関するレコードも管理しなければなりません。このデータ構造は、内部記憶集合(into remembered set)と呼ばれます。 短い一時停止時間を指定することの欠点は、G1の処理がプログラムの割当て速度に追いつかない場合があることです。その場合、最終的には処理を取りやめ、代わりに全体を停止する「Full GC」モードを開始します。Full GCモードでは、スキャンとコピーの両方の処理がJavaスレッドの停止中に行われます。部分的なコレクションで一時停止時間目標を達成できない場合には、Full GCが実行され、割り当てられた時間を確実に超過することに注意が必要です。 総じて、G1はスループットの制約と一時停止時間の制約のバランスを図った、優秀な総合的コレクタであると言えます。   Shenandoahガベージ・コレクタ Shenandoahガベージ・コレクタは、OpenJDK 12ディストリビューションの一部になったOpenJDKプロジェクトです。また、JDK 8と11へのバックポートも行われています。このガベージ・コレクタは、G1と同じ領域ベースのヒープ・レイアウトを利用し、同じコンカレント・スキャン・スレッドを用いて各領域で生きているデータの量を計算します。異なる点は、コンパクション段階での処理方法です。 Shenandoahはデータのコンパクションを同時実行的に処理します。そのため、Shenandoahでは、アプリケーションの一時停止時間を最小化するために、コレクションする領域の数を制限する必要はありません。 Shenandoahはデータのコンパクションを同時実行的に処理します(鋭い読者は、アプリケーションがオブジェクトの読取りやオブジェクトへの書込みを試みるうちに、オブジェクトをあちらこちらへ移動する必要があるのではないかと思うでしょうが、心配はいりません。この点については後ほど説明します)。そのため、Shenandoahでは、アプリケーションの一時停止時間を最小化するために、コレクションする領域の数を制限する必要はありません。代わりに、成果の大きい領域、つまり生きているオブジェクトがほとんど含まれていない領域(逆に言えば「死んだ」スペースが多くある領域)をすべて選びます。一時停止の状態になるのは、スキャンの前後に実行される特定のブックキーピング・タスクに関連するステップだけです。 Shenandoahによるコンカレント・コピーで特に難しいのが、コピー処理を実行するGCスレッドとヒープにアクセスするJavaスレッドが、オブジェクトのアドレスについて一致した情報を扱う必要があることです。このアドレスは場合によって複数の場所に保存されます。また、アドレスの更新が同時に起きているように見える必要があります。コンピュータ・サイエンスにおける他の厄介な問題と同様に、間接参照を一段階追加することでこの問題を解決できます。 オブジェクトは、間接ポインタ用の追加スペースと一緒に割り当てられます。Javaスレッドがオブジェクトにアクセスするときに、まず間接ポインタを読み取って、オブジェクトが移動されたかどうかを確認します。ガベージ・コレクタは、オブジェクトを移動する場合、新しい位置を指すように間接ポインタを更新します。新しいオブジェクトは、自身を指す間接ポインタと一緒に割り当てられます。オブジェクトがGCの実行中にコピーされる場合に限り、間接ポインタが別の位置を指すようになります。 この間接ポインタには、コストがあります。ポインタを読み取ってオブジェクトの現在の位置を把握するために、スペースと時間の両方が消費されます。しかし、これらのコストは思っているほど高くはありません。スペースに関して言えば、Shenandoahでは、部分的コレクションに対応するためのヒープ外のデータ構造(カード・テーブルや内部記憶集合といったもの)が不要です。時間に関して言えば、読取りの問題を解消する各種戦略があります。最適化JITコンパイラは、プログラムが配列サイズなどの不変フィールドにアクセスしていることを認識できます。そのような場合に間接参照の読取りが不要になるように、オブジェクトの古いコピーと新しいコピーのいずれかを読み取るべきです。さらに、Javaプログラムが同じオブジェクトから複数のフィールドを読み取る場合、JITによってそのことを認識して、後の読取りではForwardingポインタの読取りをなくすことができるでしょう。 Shenandoahがコピーしている最中のオブジェクトにJavaプログラムが書き込む場合、競合状態になります。この競合は、JavaスレッドがGCスレッドに協力することで解決します。Javaスレッドがコピー対象となっているオブジェクトに書き込む直前に、まずそのオブジェクトを独自の割当て領域にコピーして、自身が先にオブジェクトをコピーしたことを確認してから、実際の書込みを行うようにします。GCスレッドが先にオブジェクトをコピーしている場合、Javaスレッドは自分で割り当てたコピーを捨てて、GCのコピーを使用できます。 このように、Shenandoahでは、生きているオブジェクトのコピー中に一時停止する必要がないため、一時停止時間が大幅に短縮されます。   まとめ 本記事が最初に公開されてから、JDKのGCは新たな方法で進化を遂げてきています。本号の他の記事では、そういった変化のいくつかについて詳しく説明しています。本記事で紹介したGCのメカニズムを頭に入れておけば、それらの記事を非常によく理解できます(編集部より)。   Christine H. Flood Red HatでのJavaプラットフォームのプリンシパル・ソフトウェア・エンジニア。Red Hatではガベージ・コレクションの開発を担当。    

※本記事は、Christine H. Floodによる"Understanding Garbage Collectors"を翻訳したものです。 デフォルト・ガベージ・コレクタの動作の仕組み   著者:Christine H. Flood 2019年11月21日   ガベージ・コレクション(GC)は、使用されなくなった再利用可能なメモリを自動的に回収する方法の1つです。オブジェクトを手動で割り当てて破棄する他の言...

Java Magazine

Elasticsearchで簡単検索

※本記事は、Henry Naftulinによる"Easy Searching with Elasticsearch"を翻訳したものです。 Elasticsearchの高水準/低水準APIを使って同期/非同期検索を行う   著者:Henry Naftulin 2020年1月10日   Elasticsearchは、Apache Luceneという全文検索ライブラリをベースに作られている、オープンソースの検索エンジンです。Apache Luceneは、索引作成および検索のテクノロジーと、スペルチェック、高度な分析、およびトークン化の機能を提供するJavaライブラリです。Luceneは1999年にSourceForgeプロジェクトとして始まり、2001年にApache Software Foundationに参加しました。そして少なくとも、人気の検索エンジンであるSolrとElasticsearchのバックボーンになっています。いずれの検索エンジンも強力で、それぞれに長所と短所があります。Solrの方が歴史があり、伝統的にドキュメントも充実しています。しかし、テキスト検索だけでなく時系列検索や集計処理も必要になるアプリケーションには、Elasticsearchがお勧めです。本記事では、Elasticsearchのみを取り上げます。SolrエンジンとElasticsearchエンジンの比較に関する詳細については、こちらの記事をご覧ください。 Elasticsearchの学習は広範なトピックです。検索に最適化されたドキュメントの設計、問合せおよび分析、マッピング、クラスタ管理、データ取り込み、セキュリティが含まれます。本記事では、Elasticsearchの中核となる概念を紹介したうえで、Elasticsearch Java APIを使って索引内のドキュメントを作成、更新、削除、検索する方法について詳しく見ていきます。これらの操作を、低水準APIおよび高水準APIの両方で行う方法について説明します。また、タスクを同期的および非同期的に実行する方法についても説明します。さらに、Elasticsearchクラスタにデータをストリーミングする方法も紹介します。この方法は、ストリームやキューなど、大きすぎてメモリにロードすることができないソースからデータを読み取る場合に必要となります。   使ってみる 最初に、Elasticsearchをダウンロードします。続いて、インストール先のbinディレクトリに移動し、elasticsearch.batを実行して起動します。Elasticsearchエンジンが起動したら、ログに「started」と出力されます。http://localhost:9200/を開くと、単一ノード・クラスタが起動していることを示すJSONレスポンスが返されます(図1参照)。 図1:Elasticsearchクラスタが起動していることを示すJSONレスポンス   中核となる概念 これでElasticsearchが起動しました。まずは、用語に親しむため、いくつかの中核的な概念について説明します。理解を助けるため、それぞれの概念を、データベース技術における同等の概念と対比させる形で表1にまとめました。この対比は必ずしも厳密ではありませんが、新しい用語を少しばかり学びやすくしています。 Elasticsearchの概念 SQL databaseの同等の概念 Elasticsearch conceptの説明 Data type Data type テキスト、キーワード、整数、データ、範囲、地理的位置などの型。文書フィールドに適用される Document Database record JSON文書。第一級オブジェクトとしてElasticsearchに格納される。フィールドを含み、各フィールドは特定のデータ型を持つ Type   非推奨。以前、似たような文書をまとめておくのに便利な方法 Index Database table (previously was similar to a database, but now that types are gone, it is similar to a table) 同型の文書を保存・管理するインデックス Node Node Elasticsearch の単一実行インスタンス Cluster Cluster 同じインデックスを検索するElasticsearchインスタンス。単一ノードであってもElasticsearchではクラスタになる Shard/primary shard   クラスタの1つのノードに存在するインデックスの一部 Replica/replica shard   高可用性のために別のノードに保存されているシャードの余分なコピー 表1:Elasticsearchとデータベースにおける概念の比較 本記事では、カタログの項目を大量に処理します。ここでは、カタログの項目として、ID、説明、価格、売上ランキング(その項目の人気順位を表した数)があるものとします。カタログの項目は、1つのカテゴリに属し、その製造元であるメーカーが存在します。カテゴリには名前と親カテゴリがあり、メーカーには名前と住所があります。   低水準同期CRUD API それでは、低水準クライアントの作成、読取り、更新、削除(CRUD)APIから見ていきます。低水準クライアントは、最低限のElasticsearch依存性しか必要としません。また、Elasticsearchが提供するRESTエンドポイントAPIに対応しています。したがって、Elasticsearchの新しいリリースでは、低水準クライアントの依存性と下位互換性が保たれることになります。このクライアントが「低水準」と呼ばれる理由は、JSONオブジェクト・リクエストを作成し、そのレスポンスを手動で解析する必要があるためです。メモリが制限されている環境では、このソリューションしか利用できない可能性があります。高水準クライアントAPIは低水準APIを使って作られているため、まず低水準APIから始めるのは合理的です。 低水準Elasticsearchライブラリは、RESTクライアントをインポートするだけで取得できます。Mavenの場合は次のようにします。今回のコードでは、JSONとモデル・クラスとの間のシリアライズおよびデシリアライズを行う際に役立つライブラリもインポートしていますが、ここには記載していません。 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.0.0</version> </dependency>   RESTクライアントを構築するために、RESTクライアント・ビルダーを使用し、クライアントをその通信相手となるホスト(複数可能)に向けています。このクライアントはスレッドセーフであるため、アプリケーションのライフサイクル全体で使用できます。内部的なHTTPクライアントのリソースを解放するためには、アプリケーションがクライアントを使い終わった際にクライアントをクローズする必要があります。一例を挙げれば、try-with-resourcesを使ってクライアントを初期化しています。初期化が完了したら、クライアントをCrudMethodsSynchronousコンストラクタに渡しています。 CrudMethodsSynchronousクラスは、Elasticsearchのcreate/update/delete APIを呼び出すためのラッパーとして使用しています。 public static void main(String[] args) { try (RestClient client = RestClient.builder( new HttpHost("localhost", 9200, "http")).build()){ CrudMethodsSynchronous scm = new CrudMethodsSynchronous( "catalog_item_low_level", client); } }   Elasticsearchの索引にドキュメントを挿入するため、PUTリクエストを作成してクライアントにリクエストを実行してもらいます。ドキュメントを作成する際に重要な点は、その際に使うHTTPメソッドです。ここで使うことにしたのはPUTです。POSTメソッドを使うこともできます。実際のところ、レコードを作成する際に望ましいメソッドはPOSTであり、レコードを更新する際に望ましいメソッドはPUTです。しかし、今回の場合、このサンプル・プログラムを何度か実行します。その際に、索引をクリアしないこともあるため、PUTの方が望ましいというわけです。ここでは、PUTメソッドをアップサート(upsert。つまり、insertまたはupdate)として使っています。 URIも重要です。例を見てみます。 http://localhost:9200/catalog_item_low_level/_doc/ URIの最初の部分は索引、つまりドキュメントの格納場所で、データベースに相当する部分です。この場合はcatalog_item_low_levelです。2番目の部分は_docとなっていて、ドキュメントを扱うことを示しています。以前は、catalogitemなどのタイプをここで指定していましたが、Elasticsearch 7では、タイプは不要になっています。最後の部分はドキュメントのIDです。なお、索引名は小文字とする必要があることに注意してください。この例では、ドキュメントを1つずつ作成します。後ほど、複数の項目をまとめて作成する方法を紹介します。 public void createCatalogItem(List<CatalogItem> items) { items.stream().forEach(e-> { Request request = new Request("PUT", String.format("/%s/_doc/%d", getIndex(), e.getId())); try { request.setJsonEntity( getObjectMapper().writeValueAsString(e)); getRestClient().performRequest(request); } catch (IOException ex) { LOG.warn("Could not post {} to ES", e, ex); } }); } 項目を作成したら、全文検索で1つの項目を探してみます。今回の例では、「flashlight」を探します。この検索は、ドキュメント内のすべてのフィールドを対象にして行い、いずれかのフィールドにトークンとして「flashlight」が含まれているレコードを返します。ここで注意すべき点は、Elasticsearchにはフィルタと検索という両方の概念があることです。フィルタは高速な検索で、関連性を評価せずに結果を返すものです。一方で検索は、結果を返すとともに、それぞれの結果について関連度スコアを評価します(本記事では、検索のみを扱います)。 List<CatalogItem> items = scm.findCatalogItem("flashlight"); LOG.info("Found {} items: {}", items.size(), items); 低水準クライアントで検索を実行するためには、索引に対してGETリクエストを発行する必要があります。URIは、/<indexname>/_searchとなります。低水準APIはElasticsearch RESTインタフェースを使うことから、REST問合せオブジェクトを手動で作成する必要があります。今回の場合は、{ "query" : {"query_string" : { "query": "flashlight" } } }となります。 Elasticsearchにリクエストを送信後、結果をResponseオブジェクトとして受け取ることになります。この結果には、戻りステータスと、JSONレスポンスを表すエンティティが含まれています。結果のCatalogItemを得るためには、レスポンスの構造を調べる必要があります。 public List<CatalogItem> findCatalogItem(String text) { Request request = new Request("GET", String.format("/%s/_search", getIndex())); request.setJsonEntity(String.format(SEARCH, text)); try { Response response = client.performRequest(request); if (response.getStatusLine().getStatusCode()==OK) { List<CatalogItem> catalogItems = parseResultsFromFullSearch(response); return catalogItems; } } catch (IOException ex) { LOG.warn("Could not post {} to ES", text, ex); } return Collections.emptyList(); } まず、検索で返されたドキュメントを探し、次に、返されたJSONドキュメントをモデルに変換しています。おわかりのように、リクエストの作成でもレスポンスの解析でも、Elasticsearch RESTドキュメントを多用する必要があります。どのようにリクエストを作成し、Elasticsearchが返す内容をテストするかを確認するもっとも簡単な方法は、ChromeのAdvanced REST Client(ARC)プラグインを使うか、Postmanアプリを使うか、Kibanaをインストールすることです。 特定のフィールド(たとえば、カタログ項目のカテゴリ名)のみを検索するように変更する場合は、先ほどの問合せを{ "query" : { "match" : { "category.category_name" :"Home" } } }に変更するだけで済みます。その後、同じ処理を使ってリクエストを送信し、結果を解析します。 この2つの検索は、IDから項目を取得する処理とは異なります。IDから取得する場合は、索引にID(たとえば、/<indexname>/_doc/5)を渡すGETリクエストを発行するだけで済みます。また、返されたオブジェクトが存在する場合に取得されるのは、見つかった項目の配列ではなく、1つの項目だけであるため、その解析も異なります。次のコードは、低水準APIを使ってIDで検索する方法を示しています。すべての低水準API呼出しと同じように、JSONレスポンスを解析し、メタデータをスキップしてCatalogItem情報を抽出する必要があります。次に例を示します。 public Optional<CatalogItem> getItemById(Integer id) { Request request = new Request("GET", String.format("/%s/_doc/%d", getIndex(), id)); try { Response response = client.performRequest(request); if (response.getStatusLine().getStatusCode() == 200) { String rBody = EntityUtils.toString(response.getEntity()); LOG.debug("find by item id response: {}", rBody); int start = rBody.indexOf(_SOURCE); int end = rBody.indexOf("}}"); String json = rBody.substring( start + _SOURCE.length(), end+2); LOG.debug(json); CatalogItem item = jsonMapper.readValue(json, CatalogItem.class); return Optional.of(item); } } catch (IOException ex) { LOG.warn("Could not post {} to ES", id, ex); } return Optional.empty(); } ドキュメントの更新:ドキュメントを更新する方法は複数あります。ドキュメント全体を更新する方法と、特定のフィールドのみを更新する方法を使うことができます。CatalogItemはかなり小さなドキュメントであるため、ここでは全体を更新することにします。 public void updateCatalogItem(CatalogItem item) { Request request = new Request("POST", String.format("/%s/_update/%d", getIndex(), item.getId())); try { request.setJsonEntity("{ \"doc\" :" + jsonMapper.writeValueAsString(item)+"}"); Response response = client.performRequest(request); LOG.debug("update response: {}", response); } catch (IOException ex) { LOG.warn("Could not post {} to ES", item, ex); } } 特定のフィールドのみを更新する場合も、同じURIにPOSTリクエストを発行します。ただし、リクエストでオブジェクトを送信するのではなく、更新が必要なフィールドのみを送信します。 public void updateDescription(Integer id, String desc) { Request request = new Request("POST", String.format("/%s/_update/%d", index, id)); try { request.setJsonEntity( String.format( "{ \"doc\" : { \"description\" : \"%s\" }}", desc)); Response response = client.performRequest(request); LOG.debug("update response: {}", response); } catch (IOException ex) { LOG.warn("Could not post {} to ES", id, ex); } } 次に例を示します。 項目の削除:項目の削除も簡単です。索引およびドキュメントID(たとえば、/<indexname>/_doc/5)を使用してDELETEリクエストを送るだけで済みます。 public void deleteCatalogItem(Integer id) { Request request = new Request("DELETE", String.format("/%s/_doc/%d", getIndex(), id)); try { Response response = client.performRequest(request); LOG.debug("delete response: {}", response); } catch (IOException ex) { LOG.warn("Could not post {} to ES", id, ex); } } これで、低水準同期クライアントにおけるCRUDメソッドの概要説明が終わりました。 非同期呼出し:低水準クライアントを使って非同期呼出しを行う場合は、performRequestメソッドの代わりにperformRequestAsyncメソッドを呼び出す必要があるだけです。非同期呼出しには、レスポンス・リスナーを提供する必要があります。レスポンス・リスナーには、onSuccessおよびonFailureという2つのメソッドを実装する必要があります。次のコードの5行目と9行目をご覧ください。この例では、Elasticsearchの索引に対して、複数の項目の非同期アップサートを行っています。 カウントダウン・ラッチはJavaのconcurrentパッケージの一部です。ここでは、スレッド同期メカニズムとして使っています。カウントダウン・ラッチは、整数値で初期化します。待機スレッドからawait()メソッドを呼び出し、その他のスレッドからcountDownを呼び出します。その結果、countDownがラッチの初期値の回数だけ呼び出されるまで、待機スレッドはブロックされます。 これを行うために、カウントダウン・ラッチを作成しています。すべての項目が送信され、Elasticsearchで処理を終えるまでcreateCatalogMethodが終了しないようにするために、作成したカウントダウン・ラッチを使用しています。今回のレスポンス・リスナーの実装では、成功した場合と失敗した場合の両方でラッチのcountDownメソッドを呼び出し、Elasticsearchで項目の処理が完了したことを示しています。 public void createCatalogItem(List<CatalogItem> items) { CountDownLatch latch = new CountDownLatch(items.size()); ResponseListener listener = new ResponseListener() { @Override public void onSuccess(Response response) { latch.countDown(); } @Override public void onFailure(Exception exception) { latch.countDown(); LOG.error( "Could not process ES request. ", exception); } }; itemsToCreate.stream().forEach(e-> { Request request = new Request( "PUT", String.format("/%s/_doc/%d", index, e.getId())); try { request.setJsonEntity( jsonMapper().writeValueAsString(e)); client.performRequestAsync(request, listener); } catch (IOException ex) { LOG.warn("Could not post {} to ES", e, ex); } }); try { latch.await(); // すべてのスレッドが終了するまで待機 LOG.info("Done inserting all the records to the index"); } catch (InterruptedException e1) { LOG.warn("Got interrupted.",e1); } } 非同期クライアントは、高水準RESTクライアントと組み合わせることで、大幅に使いやすくなります。   高水準RESTクライアント 高水準RESTクライアント 高水準RESTクライアントは、低水準クライアントを使って作られています。Elasticsearchの依存性がいくつかプロジェクトに追加されますが、これから説明するように、コーディングははるかに簡単になり、楽しくなります。この点は、同期APIと非同期APIの両方に共通します。高水準APIの使用を選択する際には、1点注意すべきことがあります。Elasticsearchクラスタのメジャー・アップデートが行われるたびに、クライアントの依存性をアップグレードすることが推奨されるということです。低水準APIを使っている場合、この依存性アップグレードは必要ありません。しかし、ベースとなるElasticsearch APIが変更された場合は、その変更に対応するために実装の調整が必要となる可能性はあります。高水準クライアントを使うことでコーディングは容易になりますが、低水準クライアントの方が制御できる範囲は広く、バイナリのフットプリントは小さくなっています。 高水準Elasticsearchライブラリは、RESTクライアントをインポートするだけで取得できます。Mavenプロジェクトの場合は次のようにします。 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId> elasticsearch-rest-high-level-client </artifactId> <version>7.4.2</version> </dependency> 高水準RESTクライアントの構築は、低水準RESTクライアントの構築ととてもよく似ています。唯一違うのは、低水準クライアントを高水準クライアントAPIでラップする必要がある点だけです。 try(RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http")))) { CrudMethodsSynchronous scm = new CrudMethodsSynchronous( "catalog_item_high_level", client); 本誌のダウンロード・サイトに掲載したコードには、低水準クライアントで実装したものと同じメソッドがすべて含まれています。しかし、高水準モデルの方がはるかに使いやすいため、ここではドキュメントの作成方法と検索方法の2つに限って説明します。 Elasticsearch高水準APIでドキュメントを作成するためには、IndexRequestを使用し、希望の索引名で初期化する必要があります。その後、リクエストにIDを設定し、ソースとしてJSONを追加します。このリクエストを使って高水準クライアントの索引APIを同期的に呼び出すと、索引レスポンスが返されます。このレスポンスにより、ドキュメントが作成されたのか、または更新されたのかを確認することができます。 try(RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http")))) { CrudMethodsSynchronous scm = new CrudMethodsSynchronous( "catalog_item_high_level", client); public void createCatalogItem(List<CatalogItem> items) { items.stream().forEach(e-> { IndexRequest request = new IndexRequest(index); try { request.id(""+e.getId()); request.source(jsonMapper.writeValueAsString(e), XContentType.JSON); request.timeout(TimeValue.timeValueSeconds(10)); IndexResponse response = client.index(request, RequestOptions.DEFAULT); if (response.getResult() == DocWriteResponse.Result.CREATED) { LOG.info("Added catalog item with id {} " + "to ES index {}", e.getId(), response.getIndex()); } else if (response.getResult() == DocWriteResponse.Result.UPDATED) { LOG.info("Updated catalog item with id {} " + " to ES index {}, version of the " + "object is {} ", e.getId(), response.getIndex(), response.getVersion()); } } catch (IOException ex) { LOG.warn("Could not post {} to ES", e, ex); } } ); } 同様に、全文検索もはるかに読みやすくなっています。ここでは、索引を渡して検索リクエストを作成し、続いて検索問合せビルダーを使って全文テキスト検索を作成しています。検索レスポンスには、JSONナビゲーション機能が含まれています。この機能により、配列を介して結果ドキュメントに簡単にアクセスすることができます。次のコードでは、SearchHitsがその配列です。 public List<CatalogItem> findCatalogItem(String text) { try { SearchRequest request = new SearchRequest(index); SearchSourceBuilder scb = new SearchSourceBuilder(); SimpleQueryStringBuilder mcb = QueryBuilders.simpleQueryStringQuery(text); scb.query(mcb); request.source(scb); SearchResponse response = client.search(request, RequestOptions.DEFAULT); SearchHits hits = response.getHits(); SearchHit[] searchHits = hits.getHits(); List<CatalogItem> catalogItems = Arrays.stream(searchHits) .filter(Objects::nonNull) .map(e -> toJson(e.getSourceAsString())) .collect(Collectors.toList()); return catalogItems; } catch (IOException ex) { LOG.warn("Could not post {} to ES", text, ex); } return Collections.emptyList(); } このコードでは、Elasticsearchの特定の索引を検索しました。すべての索引を検索するためには、パラメータなしでSearchRequestを作成する必要があります。ここで紹介した2つの例から、その他のCRUDメソッドでの応用パターンがわかります。つまり、最初に特定のリクエストを作成し、そのリクエストに索引とドキュメントIDを渡します。リクエストは、ドキュメントを作成する場合はIndexRequest、IDからドキュメントを取得する場合はGetRequest、ドキュメントを更新する場合はUpdateRequestというようになります。その後、Elasticsearchに対して適切なリクエスト(取得、更新、削除など)を発行します。その結果、ステータス(および該当する場合はソース・オブジェクト)を含むレスポンスを受け取ります。 非同期呼出し:高水準クライアントを使うことで、非同期呼出しを書くのが少し楽になります。非同期呼出しを書くためには、相当する同期メソッドに接尾辞Asyncを付けたものを呼び出します。その際、最後の引数として、ElasticsearchのActionListener、または高水準オブジェクト(PlainActionFutureなど)のいずれかを渡します。PlainActionFutureは、高水準APIの依存性としてインポートされる、Elasticsearchのクラスです。このクラスは、ElasticsearchのActionListenerインタフェースとJavaのFutureインタフェースの両方を実装しているため、レスポンス処理に最適です。 次のサンプル・コードでは、すべてのメソッドを非同期式に実装しています。基本的に同期式の例と同じですが、PlainActionFutureを作成している点が異なります。PlainActionFutureには、やがて検索レスポンスが設定されます。高水準RESTクライアントのsearchAsync APIには、このPlainActionFutureを渡します。このメソッドのコール元はFutureを調べ、検索が完了したタイミングで、同期APIとまったく同じように検索レスポンスを解析します。 非同期APIの最大のメリットは、検索結果が必要になるまで、ワーキング・スレッドで他の操作を行うことができることです。 public PlainActionFuture<SearchResponse> findItem(String text) { SearchRequest request = new SearchRequest(getIndex()); SearchSourceBuilder ssb = new SearchSourceBuilder(); SimpleQueryStringBuilder mqb = QueryBuilders.simpleQueryStringQuery(text); ssb.query(mqb); request.source(ssb); PlainActionFuture<SearchResponse> future = new PlainActionFuture<>(); client.searchAsync(request, RequestOptions.DEFAULT, future); return future; }   Elasticsearchへのデータ・ストリーミング 本記事の最後に、Elasticsearchにデータをストリーミングする方法と、一括処理について紹介します。一括処理により、1回のリクエストで複数の索引、更新、削除操作を行うことができます。一括リクエストのメリットは、Elasticsearchサーバーとの間をリクエストのたびに往復するのではなく、1回の往復だけですべてを行う点です。また、一括処理は、Elasticsearchへのデータ・ストリーミングとの相性が非常によいものです。唯一の注意点は、余分な待機時間が発生しないように、適切なバッチ・サイズを決定する方法を見つける必要があるということです。バッチが大きすぎて、作業が完了する前にリクエスト全体がタイムアウトとなることがないようにします。 バッチ・リクエストには、異なる操作を含めることもできます。しかし、次の例では、Elasticsearchの索引にデータを挿入する索引リクエストのみを含めています。次のルーチンでは、1つの一括リクエストを作成しています。この一括リクエストでは、バッチに渡す1つの項目につき1つの索引リクエストを追加しています。コール元には、適切なバッチ・サイズを使用する責任があります。すべての項目を追加したら、同期クライアントから一括リクエストを送信します。 一括リクエストを呼び出すと、一括レスポンスが返されます。一括レスポンスには、複数の一括レスポンス項目が含まれます。それぞれの項目はリクエストの項目に対応しており、リクエストされた操作や、その操作が成功したかどうかなどを示します。簡略化するため、この例では一括レスポンスは使用していません。 private void sendBatchToElasticSearch( List<LineFromShakespeare> linesInBatch, RestHighLevelClient client, String indexName) throws IOException { BulkRequest request = new BulkRequest(); linesInBatch.stream().forEach(l -> { try { request.add(new IndexRequest(indexName) .id(l.getId()) .source(jsonMapper.writeValueAsString(l), XContentType.JSON)); } catch (JsonProcessingException e) { LOG.error("Problem mapping object {}", l, e); }}); LOG.info("Sending data to ES"); client.bulk(request, RequestOptions.DEFAULT); }   上記のメソッドは、別の処理から起動します。この処理では、ファイルのストリーミングを使ってElasticsearchに1,000個ずつドキュメントをロードし、バッチを作成しています。 public void loadData(String file, String index) throws IOException, URISyntaxException { Path filePath = Paths.get( ClassLoader.getSystemResource(file).toURI()); List<String> errors = new ArrayList<>(); List<LineFromShakespeare> lines = new ArrayList<>(); final int maxLinesInBatch = 1000; try(RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http")))) { Files.lines(file).forEach( e-> { try { LineFromShakespeare line = jsonMapper.readValue(e, LineFromShakespeare.class); // 修飾する... linesInBatch.add(line); if (linesInBatch.size() >= maxLinesInBatch) { sendBatchToElasticSearch(linesInBatch, client, indexName); linesInBatch.clear(); } } catch (IOException ex) { errors.add(e); linesInBatch.clear(); }}); if (linesInBatch.size() != 0) { sendBatchToElasticSearch(linesInBatch, client, indexName); linesInBatch.clear(); } } LOG.info("Errors found in {} batches", errors.size()); }   ここでは、JavaのFiles.lines APIを使ってファイルを1行ずつストリーミングし、テキストをオブジェクトに変換し、修飾したうえで、Elasticsearchに送信予定のバッチに追加しています。バッチ・サイズが1,000になったら、sendBatchToElasticSearchメソッドを使ってバッチをElasticsearchに送信しています。 おわかりのように、高水準APIを使うことでコードは簡素化され、その可読性もはるかに向上します。そのため、バイナリ・フットプリントが問題にならず、Elasticsearchクラスタがメジャー・アップグレードされるたびに依存性をアップグレードできるのであれば、高水準APIを使い続けることを強くお勧めします。   まとめ 本記事では、Elasticsearchを紹介しました。低水準クライアントおよび高水準クライアントの両方で使用されるJava CRUD APIを中心に、CRUDアプリケーションに必要なほとんどの機能に触れました。APIと、Elasticsearchへのデータ・ストリーミングは、本格的にElasticsearchを使う前に必要な基礎知識です。Elasticsearchが扱う領域には、ドキュメント設計やデータ分析機能に加え、複数フィールド検索、近似マッチング、ページング、サジェスチョン、ハイライト、結果のスコアリングを含む高度な検索、さまざまなタイプのデータ集計、位置情報、セキュリティ、クラスタ管理などが含まれます。この土俵はとても広いのです。ぜひお楽しみください。     Henry Naftulin Henry Naftulin:15年以上にわたってJava EE分散システムの設計に携わる。現在は、米国有数の金融会社で、受賞歴のあるプロプライエタリな債券取引プラットフォームのリード開発者を努めている。      

※本記事は、Henry Naftulinによる"Easy Searching with Elasticsearch"を翻訳したものです。 Elasticsearchの高水準/低水準APIを使って同期/非同期検索を行う   著者:Henry Naftulin 2020年1月10日   Elasticsearchは、Apache Luceneという全文検索ライブラリをベースに作られている、オープンソースの検索エンジ...

Java Magazine

Javaにレコードが登場

※本記事は、Ben Evansによる"Records Come to Java"を翻訳したものです。 Java 14のデータ・レコードがコーディングにもたらす変革を初解説   著者:Ben Evans 2020年1月10日   本記事では、Javaにおけるレコードの概要について紹介します。レコードはJavaクラスの新しい形態で、以下の目的で設計されています。 データのみを組み合わせた構造をモデリングするためのファーストクラスな手段を提供する Javaの型システムに生じる可能性があるギャップを解消する 一般的なプログラミング・パターンに言語レベルの構文を提供する クラスの定型挿入文を減らす 現在、レコード機能は活発に開発が行われており、Java 14のプレビュー機能として登場する予定です(2020年3月リリース予定)。本記事の内容を活用するためには、Javaプログラミングについてのかなりの知識と、プログラミング言語の設計や実装についての興味が必要です。 それでは、Javaのレコードとは何かという基本的な考え方の説明から始めます。 Javaのレコードとは Javaに関して特によく耳にする不満の1つは、便利なクラスを作るためには大量のコードを書く必要があるという点です。以下に挙げるものは、特に頻繁に書くことになります。 toString() hashCode()とequals() getterメソッド パブリック・コンストラクタ 簡単なドメイン・クラスの場合、こういったメソッドは反復的で退屈であることが一般的です。また、機械的に簡単に生成できるタイプのものでもあります(多くの場合、この機能はIDEで提供されています)。それにもかかわらず、現時点において言語自体では上記のようなメソッドを生成する方法を一切提供していません。 このギャップはフラストレーションです。実際、他人のコードを読むときよりもひどいかもしれません。たとえば、コードの作成者が、IDEで生成され、クラスのすべてのフィールドを対象としたhashCode()およびequals()を使っているように見えても、どうすれば実装のすべての行をチェックせずにそのように確信できるでしょうか。また、リファクタリングの際にフィールドが追加され、メソッドが再生成されなかった場合はどうなるでしょうか。 レコードの目的は、Java言語の構文を拡張し、あるクラスが「フィールドの集合であり、フィールドの集合でしかなく、フィールドの集合以外の何者でもない」ことを宣言できるようにすることです。クラスに対してその宣言を行うことにより、コンパイラですべてのメソッドが自動作成され、すべてのフィールドがhashCode()などのメソッドに含まれるようにすることができます。   レコードを使わない場合の例 本記事では、レコードを説明するためのサンプル・ドメインとして、外国為替(FX)取引を使用します。レコードを使ってドメインのモデリングを改善し、結果として、冗長性が減少してクリーンでシンプルになったコードを得ることができる方法を紹介します。 なお、取引を完全に実現した本番グレードのシステムについて、短い記事で説明するのは不可能であるため、いくつかの基本的な側面に注目することにします。 FX取引の際に注文を行う方法について考えてみます。基本的な注文のタイプは、以下から構成されると考えることができます。 売買の単位を表す数(100万通貨単位) 「サイド」、すなわち買う側か売る側か(通常はそれぞれ「BID」、「ASK」と呼ばれます) 交換する通貨(「通貨ペア」) 注文を行った時刻 注文がタイムアウトするまでの有効期間(「TTL」(Time To Live:生存期間)) したがって、1ポンドあたり1.25ドルで100万ポンドを売って米ドルにしたい場合、FX取引の業界用語では「レート1.25ドルでGBP/USDを100万買う」と言います。トレーダーは注文の作成時刻(通常は現在時刻)と注文の有効期間(通常は1秒以下)も示します。 Javaでは、次のようなドメイン・クラスを宣言することになるでしょう(現在のクラスでこの宣言が必要なことを強調するため、「古典的」という意味を持つ「Classic」という名前にしています)。 public final class FXOrderClassic { private final int units; private final CurrencyPair pair; private final Side side; private final double price; private final LocalDateTime sentAt; private final int ttl; public FXOrderClassic(int units, CurrencyPair pair, Side side, double price, LocalDateTime sentAt, int ttl) { this.units = units; this.pair = pair; // CurrencyPairはシンプルなenum this.side = side; // Sideはシンプルなenum this.price = price; this.sentAt = sentAt; this.ttl = ttl; } public int units() { return units; } public CurrencyPair pair() { return pair; } public Side side() { return side; } public double price() { return price; } public LocalDateTime sentAt() { return sentAt; } public int ttl() { return ttl; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FXOrderClassic that = (FXOrderClassic) o; if (units != that.units) return false; if (Double.compare(that.price, price) != 0) return false; if (ttl != that.ttl) return false; if (pair != that.pair) return false; if (side != that.side) return false; return sentAt != null ? sentAt.equals(that.sentAt) : that.sentAt == null; } @Override public int hashCode() { int result; long temp; result = units; result = 31 * result + (pair != null ? pair.hashCode() : 0); result = 31 * result + (side != null ? side.hashCode() : 0); temp = Double.doubleToLongBits(price); result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (sentAt != null ? sentAt.hashCode() : 0); result = 31 * result + ttl; return result; } @Override public String toString() { return "FXOrderClassic{" + "units=" + units + ", pair=" + pair + ", side=" + side + ", price=" + price + ", sentAt=" + sentAt + ", ttl=" + ttl + '}'; } }   注文は次のようにして作成できます。 var order = new FXOrderClassic(1, CurrencyPair.GBPUSD, Side.Bid, 1.25, LocalDateTime.now(), 1000);   しかし、このクラスを宣言しているコードのうち、実際に必要なのはどれほどでしょうか。現在のバージョンのJavaでは、フィールドだけを宣言し、IDEを使ってすべてのメソッドを自動生成している開発者がほとんどではないでしょうか。この状況がレコードによってどのように改善されるかを見ていきます。 補足ですが、Javaにおいてデータの組合せを表現する手段は、クラスの定義によって行うこと以外、何も提供されていません。そのため、「フィールドのみ」を含む型がクラスにしかなり得ないことは明らかです。   レコードを使った場合の例 新しい概念であるレコード・クラス(通常は、単にレコードと呼びます)は、不変(通常の「浅い」Java的な意味です)で透過的なキャリア(入れもの)であり、レコード・コンポーネントと呼ばれる、決まった個数の値が格納されます。各コンポーネントは、提供された値と、その値を取得するためのアクセッサ・メソッドを保持するfinalフィールドになります。フィールドの名前とアクセッサの名前は、コンポーネントの名前に対応します。 フィールドのリストは、そのレコードの状態記述です。クラスでは、フィールドx、コンストラクタ引数x、アクセッサx()の間に何の関係もないことがあります。しかし、レコードでは、定義上、同じものを表すことになります。つまり、レコードとは、レコードの状態を表すものです。 レコード・クラスの新しいインスタンスを作れるように、カノニカル・コンストラクタと呼ばれるコンストラクタも生成されます。このコンストラクタのパラメータ・リストは、宣言されている状態記述に厳密に一致します。 Java言語(Java 14のプレビュー機能の時点)では、レコードを宣言するための簡潔な構文が提供されます。プログラマーが行う必要があるのは、レコードを構成するコンポーネントの名前と型を宣言することだけです。次に例を示します。 public record FXOrder(int units, CurrencyPair pair, Side side, double price, LocalDateTime sentAt, int ttl) {}   このレコード宣言を書くことで、タイプする量が節約されるだけではありません。はるかに強力で意味のある文を作っていることになります。つまり、FXOrder型は提供される状態にすぎず、どのインスタンスもフィールドの値を透過的に組み合わせただけのものだということを表しています。 その結果の1つとして、フィールドの名前がそのままAPIになります。そのため、適切な名前を選ぶことがますます重要になります(たとえば、Pairは型の名称として適切ではありません。靴のペアを表すかもしれないからです)。 この新しい言語機能を使うためには、レコードを宣言したコードを、プレビュー・フラグを指定してコンパイルする必要があります。 javac --enable-preview -source 14 FXOrder.java javapでクラス・ファイルを調べてみると、コンパイラが大量の定型コードを自動生成していることがわかります(以下では、逆コンパイルしたメソッドとそのシグネチャのみを示します)。 $ javap FXOrder.class Compiled from "FXOrder.java" public final class FXOrder extends java.lang.Record { public FXOrder(int, CurrencyPair, Side, double, java.time.LocalDateTime, int); public java.lang.String toString(); public final int hashCode(); public final boolean equals(java.lang.Object); public int units(); public CurrencyPair pair(); public Side side(); public double price(); public java.time.LocalDateTime sentAt(); public int ttl(); } これは、クラスベースの実装をしたコードに含まれる一連のメソッドとうり二つです。実際に、コンストラクタとアクセッサ・メソッドは、すべて前のものと同じように動作します。   レコードの特異性 ただし、toString()やequals()などのメソッドには、一部の開発者が驚くような実装が使われています。 public java.lang.String toString(); Code: 0: aload_0 1: invokedynamic #51, 0 // InvokeDynamic #0:toString:(LFXOrder;)Ljava/lang/String; 6: areturn   なぜかと言えば、toString()メソッド(equals()やhashCode()も同様です)は、invokedynamicベースのメカニズムを使って実装されているからです。この方法は、最近のバージョンのJavaにおいて、文字列結合でinvokedynamicを使うように移行されたときと同様のものです。 java.lang.Recordという新しいクラスがあることもわかります。このクラスは、すべてのレコード・クラスのスーパータイプになる抽象クラスで、抽象メソッドとしてequals()、hashCode()、toString()が宣言されています。 java.lang.Recordクラスを直接拡張することはできません。そのことは、次のようなコードをコンパイルしようとすればわかります。 public final class FXOrderClassic extends Record { private final int units; private final CurrencyPair pair; private final Side side; private final double price; private final LocalDateTime sentAt; private final int ttl; // ... クラスの残りの部分は省略 }   コンパイラでは、次のメッセージを出してコンパイルを失敗させます。 $ javac --enable-preview -source 14 FXOrderClassic.java FXOrderClassic.java:3: error: records cannot directly extend Record public final class FXOrderClassic extends Record { ^ Note:FXOrderClassic.java uses preview language features. Note:Recompile with -Xlint:preview for details. 1 error   つまり、レコードを取得する唯一の方法は、明示的にレコードを宣言してjavacでクラス・ファイルを作成することだけです。この方法により、すべてのレコード・クラスがfinalとして作成されることも保証されます。 レコードに適用されることで特殊な性質が生じるJavaの中核機能は、他にもいくつかあります。 まず、レコードはequals()メソッドに関連する特殊な契約に従わなければなりません。 レコードRのコンポーネントにc1、c2、...、cnがあり、レコードのインスタンスが次のようにしてコピーされたとします。 R copy = new R(r.c1(), r.c2(), ..., r.cn()); この場合、r.equals(copy)はtrueでなければなりません。この不変式は、equals()とhashCode()に関するおなじみの契約に追加されるものであり、それを置き換えるものではない点に注意してください。 次に、Javaでのレコードのシリアライズは、通常のクラスのシリアライズとは異なります。これは望ましいことだと言えます。今では広く認識されているように、Javaのシリアライズ・メカニズムは一般的に欠点が多いからです。Java言語アーキテクトのBrian Goetzは、「シリアライズは、見えないにもかかわらずパブリックなコンストラクタと、見えないにもかかわらずパブリックな一連の、内部状態へのアクセッサでできています」と述べています。 ありがたいことに、レコードは非常にシンプルに設計されています。レコードはフィールド用の透過的なキャリアにすぎないため、シリアライズ・メカニズムの詳細にまつわる奇妙な状況を引き起こす必要はありません。レコードのシリアライズとデシリアライズには、常にパブリックAPIとカノニカル・コンストラクタを使うことができます。 さらに、レコード・クラスのserialVersionUIDは、明示的に宣言されない限り0Lになります。レコード・クラスには、serialVersionUIDの値が一致しなければならないという要件も適用されません。 次のセクションに進む前に、新しいプログラミング・パターンと、少ない定型挿入文でクラスを宣言するための新しい構文があり、このパターンと構文はProject Valhallaで開発されているインライン・クラス機能とは関係ないということを強調しておきたいと思います。   設計レベルでの考慮事項 次は、設計レベルにおける、レコード機能の側面のいくつかについて考えてみます。考察にあたり参考にするため、Javaの列挙型の仕組みを振り返ってみます。Javaの列挙型は、パターンを実装するにもかかわらず、最低限の構文しか必要としないという特殊な形態のクラスです。コンパイラが、私たちに代わって多くのコードを生成してくれます。 同じように、Javaのレコードも、最低限の構文でデータ・キャリア・パターンを実装するという特殊な形態のクラスです。要求される定型コードは、すべてコンパイラが自動生成してくれます。 しかし、フィールドを保持するだけのデータ・キャリア・クラスという単純な考え方は、直感的にはわかるものの、厳密に意味するのはどういうことでしょうか。 初めてレコードが議論されたとき、さまざまな設計が検討されました。次に例を示します。 Plain Old Java Object(POJO)の定型挿入文の削減 JavaBeans 2.0 名前付きタプル 直積型(代数データ型の1形態) こういった可能性は、Brian Goetzのオリジナル設計草案で多少詳しく議論されています。それぞれの設計オプションには、レコードの設計の中心として選択したものから生じる二次的な問いがさらに伴います。たとえば、次のようなものです。 Hibernateはレコードのプロキシとなることができるか レコードには従来のJavaBeansとの完全な互換性があるか 同じフィールドを同じ順番で宣言している2つのレコードを同じ型と見なすか 将来的にパターン・マッチングや分割代入などのパターン・マッチング技術に対応させるか それぞれのアプローチに利点と欠点があるため、そのうちのどれがレコード機能のベースになってもおかしくはありません。しかし、最終的な設計では、レコードは名前付きタプルと決定されています。 この選択には、名前的型付けと呼ばれる、Javaの型システムにおいて設計上重要な考え方が影響した部分がありました。名前的型付けとは、Javaストレージのすべての部品(変数、フィールド)には明確な型があり、それぞれの型に付けられる名前は人間にとって意味のあるものとすべきだという考え方です。 たとえ匿名クラスの場合でも、型には名前があります。名前を割り当てるのがコンパイラであり、Java言語での型としては有効な名前でない(しかし、それでもVM内では問題ありません)というだけのことです。次の例をご覧ください。 jshell> var o = new Object() { ...> public void bar() { System.out.println("bar!"); } ...> } o ==> $0@37f8bb67 jshell> var o2 = new Object() { ...> public void bar() { System.out.println("bar!"); } ...> } o2 ==> $1@31cefde0 jshell> o = o2; | Error: | incompatible types: $1 cannot be converted to $0 | o = o2; | ^^   まったく同じように宣言された匿名クラスであっても、コンパイラで$0および$1という2つの異なる匿名クラスが生成されたことと、これらの変数はJavaの型システムでの型が異なるために代入が認められないことに注目してください。 その他(Java以外)の言語の中には、明示的な型名の代わりに、クラス全体の形(たとえば、クラスのフィールドやメソッド)を型として使うことができるものもあります。これを構造的型付けと呼びます。 レコードがJavaの伝統を破って構造的型付けを持ち込むことになっていたとすれば、大幅な変更になっていたでしょう。結果的に、「レコードは名前付きタプルである」という設計の選択から想定すべき点は、レコードが一番役立つのは他の言語でタプルを使うような場合であるということです。これには、複合マップ・キーや、メソッドから疑似的に複数の値を返却するといったユースケースが含まれます。複合マップ・キーとは、次のようなものです。 record OrderPartition(CurrencyPair pair, Side side) {} なお、現在JavaBeansを使っている既存のコードをレコードに置き換えても、必ずしもうまく動作するとは限りません。その理由はいくつかあります。JavaBeansは可変ですが、レコードは不変です。また、この2つはアクセッサの規約が異なります。 レコードは純粋なクラスであるため、許可されるのは1行での単純な宣言形態のみにとどまりません。具体的に言えば、自動生成されたデフォルトのもの以外に、追加のメソッドやコンストラクタ、静的フィールドを定義できます。しかし、こういった機能は注意して使うべきです。レコードの設計上の意図は、開発者が、関連するフィールドをグループ化し、1つの不変データ項目を作れるようにすることである点を思い出してください。 経験則として、次のことが言えます。基本的なデータ・キャリアにメソッドを追加したくなればなるほど(または、インタフェースを実装したくなればなるほど)、レコードではなく完全なクラスを使うべきである可能性が高くなります。   コンパクト・コンストラクタ このルールの重要な例外と言えるかもしれないことの1つは、コンパクト・コンストラクタの使用です。Java仕様には、コンパクト・コンストラクタについて次のように書かれています。 コンパクト・コンストラクタを宣言する目的は、カノニカル・コンストラクタの本体で必要となる、検証や正規化のみを行うコードを提供することにあります。その他の初期化コードはコンパイラが提供します。 たとえば、注文を検証し、負の数量の売買や無効なTTL値の設定が行われないようにしたいとします。 public record FXOrder(int units, CurrencyPair pair, Side side, double price, LocalDateTime sentAt, int ttl) { public FXOrder { if (units < 1) { throw new IllegalArgumentException( "FXOrder units must be positive"); } if (ttl < 0) { throw new IllegalArgumentException( "FXOrder TTL must be positive, or 0 for market orders"); } if (price <= 0.0) { throw new IllegalArgumentException( "FXOrder price must be positive"); } } } コンパクト・コンストラクタを宣言しても、コンパイラで生成されるものとは別のコンストラクタができることはありません。コンパクト・コンストラクタに指定したコードは、カノニカル・コンストラクタの最初に追加されたコードとして扱われます。コンストラクタのパラメータをフィールドに代入するように指定する必要はありません。その部分はコンストラクタに自動生成され、通常どおり扱われます。 Javaのレコードが他の言語で見られる匿名タプルよりも優れている点の1つは、レコードを作成するときに実行されるコードをレコードのコンストラクタ本体に書くことができる点です。これにより、検証を行うことができます(そして、無効な状態が渡されたときは例外をスローすることができます)。純粋な構造的タプルで、この検証を行うことはできません。   代替コンストラクタ レコード本体の中で静的ファクトリ・メソッドを使うことも可能です。たとえば、これにより、Javaにパラメータのデフォルト値がないという問題を回避できます。為替取引の例で言えば、次のような静的ファクトリを追加することで、デフォルト・パラメータを使って簡単に注文を作成できるようになります。 例で言えば、次のような静的ファクトリを追加することで、デフォルト・パラメータを使って簡単に注文を作成できるようになります。 public static FXOrder of(CurrencyPair pair, Side side, double price) { return new FXOrder(1, pair, side, price, LocalDateTime.now(), 1000); } もちろん、これを代替コンストラクタとして宣言することもできます。それぞれの状況で、いずれのアプローチが適切かを選択する必要があります。 代替コンストラクタの別の用途は、複合マップ・キーとして使うレコードを作成する場合です。次の例をご覧ください。 record OrderPartition(CurrencyPair pair, Side side) { public OrderPartition(FXOrder order) { this(order.pair(), order.side()); } } こうすることで、OrderPartition型をマップのキーとして簡単に使えるようになります。たとえば、取引マッチング・エンジンで使う注文台帳を作りたいとします。 public final class MatchingEngine { private final Map<OrderPartition, RankedOrderBook> orderBooks = new TreeMap<>(); public void addOrder(final FXOrder o) { orderBooks.get(new OrderPartition(o)).addAndRank(o); checkForCrosses(o.pair()); } public void checkForCrosses(final CurrencyPair pair) { // 現在、この売り注文に一致する買い注文は存在するか? } // ... } 新しい注文を受け取ると、addOrder()メソッドで適切な注文パーティション(通貨ペアと売買サイドのタプルで構成されるもの)を抽出します。新しい注文は、価格でランク付けされた適切な注文台帳に追加されますが、その際にこのパーティションを使います。新しい注文は、台帳にすでに存在する、反対サイドの注文に一致するかもしれません(これを注文の「食い合い」と言います)。そのため、checkForCrosses()メソッドで食い合いの有無を確認する必要があります。 場合によっては、コンパクト・コンストラクタを使わずに、明示的な完全版のカノニカル・コンストラクタを持たせたくなる場合もあるかもしれません。これは、コンストラクタで何らかの実作業を行う必要があることを示しています。単純なデータ・キャリア・クラスの場合、このようなユースケースは少数です。しかし、受け取ったパラメータを念のためにコピーしておく必要がある場合など、状況によってはこのオプションも必要です。そのため、コンパイラは明示的なカノニカル・コンストラクタを作ることも許可します。しかし、実際にこれを使う前には十分慎重に考えてください。 まとめ レコードは単純なデータ・キャリアを意図しており、論理性と一貫性の点で定評がある、Javaの型システムになじんだタプルの一種です。レコードの存在は、多くのアプリケーションでドメイン・クラスをクリーンかつ小さくするために役立つでしょう。また、チームはベースとなるパターンの実装をハードコードする大量の作業から解放され、Lombokなどのライブラリは、必要性が低下するか、または不要になるでしょう。 しかし、シールド型と同じように、レコードのもっとも重要なユースケースの一部は、これから登場する機能になるでしょう。パターン・マッチング、その中でも、レコードをコンポーネントに分解できる分割代入パターンは特に有望で、多くの開発者のJavaプログラミング方法を大きく変えることになるかもしれません。シールド型とレコードの組合せにより、代数データ型と呼ばれる、言語機能の一形態がJavaで実現することにもなります。 他のプログラミング言語でこういった機能をよく知っているなら、すばらしいことです。そうでない方も心配しないでください。前述の機能は、皆さんがすでにご存じのJava言語になじむように、そしてコードで使い始めることが容易なように、設計が行われています。 ただし、特定の言語機能を含む確定版のJavaが提供されるまで、そのバイナリに頼るべきではないという点には常に注意しなければなりません。本記事のように、将来導入される見込みの機能について取り上げている場合、純粋に探求目的のみで機能を説明していることを常に理解しておいてください。 Ben Evans Ben Evans(@kittylyst):Java Champion。New Relicのプリンシパル・エンジニア。先日発刊された『Optimizing Java』(O'Reilly)を含め、プログラミングに関する5冊の書籍を執筆している。jClarity(Microsoftにより買収)の創業者の1人であり、Java SE/EE Executive Committeeの元メンバーである。      

※本記事は、Ben Evansによる"Records Come to Java"を翻訳したものです。 Java 14のデータ・レコードがコーディングにもたらす変革を初解説   著者:Ben Evans 2020年1月10日   本記事では、Javaにおけるレコードの概要について紹介します。レコードはJavaクラスの新しい形態で、以下の目的で設計されています。 データのみを組み合わせた構造をモデリングするためのフ...

Java Magazine

JavaによるGPUプログラミング

※本記事は、Dmitry Aleksandrovによる"Programming the GPU in Java"を翻訳したものです。 JavaからGPUにアクセスすることで発揮される圧倒的な能力:GPUの動作の仕組みとJavaからのアクセス方法を解説   著者:Dmitry Aleksandrov 2020年1月10日   GPU(グラフィックス・プロセッシング・ユニット)プログラミングと聞くと、Javaプログラミングとはかけ離れた世界のことのように感じる方もいるでしょう。その気持ちもわかります。Javaのユースケースのほとんどは、GPUを適用できるものではないからです。しかしながら、GPUではテラフロップ級のパフォーマンスが実現します。ここでは、GPUの可能性を探究してみます。 このトピックの理解を助けるため、まずはGPUのアーキテクチャや簡単な歴史について説明します。この説明を読めば、GPUプログラミングに手を付けやすくなるでしょう。そしてGPUでの計算がCPUとどのように違うかについて説明し、その後はいよいよ、Javaの世界でGPUを使う方法に入ります。最後に、Javaコードを書く際やそのコードをGPUで実行する際に役立つ、代表的なフレームワークやライブラリについて触れます。また、いくつかのコード・サンプルも提示したいと思います。   簡単な背景 GPUを最初に普及させたのはNVIDIAで、1999年のことでした。GPUは、ディスプレイに転送する前のグラフィック・データを処理するために設計された特別なプロセッサです。ほとんどの場合、GPUを使用して、一部の計算をCPUからオフロードすることができます。これにより、CPUリソースが解放される一方で、GPUにオフロードされた計算は高速になります。その結果、処理できる入力データが増加し、出力解像度もはるかに大きくなることで、視覚表現の魅力は増し、フレーム・レートもより滑らかになります。 2D/3D処理の本質はほとんどが行列演算であるため、大規模な並列化アプローチによって処理できます。それでは、どうすれば効率的に画像を処理できるでしょうか。この質問に答えるため、標準的なCPUのアーキテクチャ(図1参照)とGPUのアーキテクチャを比較してみます。   図1:CPUのブロック・アーキテクチャ CPUでは、フェッチャ、算術論理演算装置(ALU)、実行コンテキストといった実際の処理の要素は、システム全体の一部でしかありません。予測できない順番でやってくる不規則な計算を高速化するため、CPUには高速で高価な大容量キャッシュ、さまざまな種類のプリフェッチャ、分岐予測器が搭載されています。 GPUには、これらすべてが必要になるわけではありません。受信するデータは予測可能であるうえ、極めて限られた種類のデータ演算だけを行うからです。そのため、非常に小さく安価なプロセッサを作ることができます。ブロック・アーキテクチャは図2のようなものになります。   図2:シンプルなGPUコアのブロック・アーキテクチャ こういったタイプのプロセッサは安価なうえ、データがまとめて並列処理されるため、多数のプロセッサを並列に動作させることも容易です。この設計は、複数命令/複数データ(MIMD。英語では「ミムディ」と発音します)と呼ばれます。 2つ目のアプローチは、1つの命令が複数のデータ項目に適用されることが多い点に着目したもので、単一命令/複数データ(SIMD。「シムディ」と発音します)と呼ばれています。この設計では、1つのGPUに複数のALUと実行コンテキストを含めたうえで、共有コンテキスト・データ専用の小さな領域も持たせます。図3をご覧ください。 図3:MIMDスタイルのGPUブロック・アーキテクチャ(左)とSIMDの設計(右)との比較 SIMDとMIMD処理とを組み合わせることで、処理スループットが最大になります。この点については、後ほど触れたいと思います。この設計では、図4に示すように複数のSIMDプロセッサを並列に実行します。 図4:複数のSIMDプロセッサの並列実行。この例では16個のコア、合計128個のALUを使用 シンプルで小さいプロセッサが大量にあるため、プロセッサのプログラミングにより、出力に特別な効果を加えることができます。   GPUでプログラムを実行する ゲームにおける初期の視覚効果のほとんどは実際のところ、GPUで動作する小さなプログラムをハードコードし、CPUから提供されるデータ・ストリームに適用して実現していました。 しかしその当時でも、視覚表現がまさに主なセールス・ポイントの1つであるゲームの設計においては特に、ハードコードされたアルゴリズムでは不十分なことは明らかでした。そこで、大規模ベンダーはGPUにアクセスする方法を公開し、GPUを使うコードをサードパーティの開発者が書けるようにしました。 その典型的な方法は、特別な言語(通常はCのサブセット)を使ってシェーダと呼ばれる小さなプログラムを書き、アーキテクチャに対応した特別なコンパイラでコンパイルするというものでした。シェーダという用語が選ばれたのは、シェーダが照明(ライティング)や陰影(シェーディング)の効果を制御するために使われることが多かったからです。しかし、その他の特殊効果を行えない理由はありません。 それぞれのGPUベンダーでは、自社のハードウェアに対応したシェーダを作成するための、特別なプログラミング言語およびインフラストラクチャを独自に持っていました。そのような流れの中で、さまざまなプラットフォームが生まれてきました。主なものを紹介します。 DirectCompute:Microsoft独自のシェーダ言語/API。Direct3Dの一部で、DirectX 10以降に対応 AMD FireStream:ATI/Radeonの独自テクノロジー。AMDは開発を終了 OpenACC:複数ベンダーのコンソーシアムによる並列コンピューティング・ソリューション C++ AMP:C++でのデータ並列化に用いられるMicrosoft独自のライブラリ CUDA:NVIDIAの独自プラットフォーム。C言語のサブセットを使用 OpenCL:共通標準。もともとはAppleが設計したものであるが、現在はKhronos Groupというコンソーシアムが管理 GPUを使う場合、ほとんどの状況で低レベル・プログラミングとなります。開発者がコーディングする際に少しでもわかりやすくするため、いくつかの抽象化も行われています。もっとも有名なものは、MicrosoftのDirectXと、Khronos GroupのOpenGLです。これらのAPIは、高レベルなコードを書くためのものです。開発者は、書いたコードをほぼシームレスにGPUへとオフロードすることができます。 筆者の知る限り、DirectXをサポートするJavaインフラストラクチャはありません。しかし、OpenGLにはすばらしいバインディングが存在します。GPUプログラミングへの対応を行うJSR 231は、2002年に開始されましたが、2008年に中断され、OpenGL 2.0のみのサポートにとどまりました。OpenGLのサポートは、JOCLと呼ばれる独立したプロジェクト(OpenCLのサポートも行われています)で継続されており、一般にも公開されています。ちなみに、有名なゲームであるMinecraftの内部処理は、JOCLで書かれています。   GPGPUの登場 それでも、JavaとGPUはシームレスに結合されているわけではありません(そうなっているべきなのですが)。Javaは、企業やデータ・サイエンス、金融部門で多用されています。こういった用途では、多くの計算や大きな処理能力が必要とされます。そこから登場したのが、GPGPU(汎用GPU)という考え方です。 画像データの処理以外にGPUを使おうという考え方が出現したのは、ビデオ・アダプタのベンダーがフレーム・バッファのプログラミングを開放し、開発者がフレーム・バッファの内容を読み取れるようになり始めたときでした。一部のマニアが、GPUを汎用計算にフル活用できることに気づきました。その方法は簡単でした。 データをエンコードしてビットマップ配列にする ビットマップ配列を処理するシェーダを書く ビットマップ配列とシェーダをビデオ・カードに送信する フレームバッファから結果を取得する ビットマップ配列をデコードしてデータを取り出す この説明は非常に簡略化したものです。この処理が実際の環境で多用されたことがあったかどうかはわかりませんが、動作したことは確かです。 続いて、スタンフォード大学の研究者たちが、GPGPUを使いやすくする方法を探し始めました。2005年、その研究者たちがBrookGPUをリリースしました。これは、言語とコンパイラ、そしてランタイムを含んだ小さなエコシステムでした。 BrookGPUは、Brookというストリーム・プログラミング言語で書かれたプログラムをコンパイルするものでした。この言語はANSI Cを拡張したもので、計算バックエンドとしてOpenGL v1.3以降、DirectX v9以降、AMDのClose to Metalをターゲットにすることができ、Microsoft WindowsとLinuxの両方で動作しました。また、BrookGPUではデバッグ用にCPUで仮想グラフィックス・カードをシミュレートすることもできました。 しかし、BrookGPUは成功しませんでした。その理由は、当時のハードウェアにあります。GPGPUの世界では、データをデバイス(ここでのデバイスは、GPUと、GPUが搭載されたボードを指しています)にコピーし、GPUがデータを処理するのを待ち、その処理が終わってからデータをメイン・ランタイムにコピーする必要があります。この処理には、長い待機時間が伴います。このプロジェクトでの開発が活発だった2000年代中盤当時は、この待機時間が障害となり、GPUを広く汎用計算に使うことはほぼ不可能でした。 それでも、多くの企業がこのテクノロジーに将来を見ました。ビデオ・アダプタのベンダーからは、独自テクノロジーとともにGPGPUを提供し始めるところも出てきました。また、アライアンスを組んだベンダーが、いっそう幅広いハードウェア・デバイスで動作する、汎用性を高めた柔軟なプログラミング・モデルを提供したこともありました。 背景の説明は以上です。次は、GPUコンピューティングで特に成功を収めたと言える2つのテクノロジーである、OpenCLとCUDAに迫り、Javaでこの2つを使う方法について確認します。   OpenCLとJava その他多くのインフラストラクチャ・パッケージと同様に、OpenCLのベース実装はCで提供されています。そのため、Javaネイティブ・インタフェース(JNI)やJavaネイティブ・アクセス(JNA)を使ってアクセスすることも技術的には可能ですが、ほとんどの開発者にとってこのようなアクセス方法はやや過度な負担となるでしょう。ありがたいことに、JOCL、JogAmp、JavaCLというライブラリでこの作業がすでに行われています。JavaCLはすでに終了したプロジェクトですが、JOCLプロジェクトはかなり活発に活動しています。次のサンプルでは、JOCLを使用します。 本題に入る前に、OpenCLとは何かについて説明しておきます。先ほども触れたように、OpenCLでは非常に汎用的なモデルを提供しています。このモデルは、GPUやCPUだけでなく、デジタルシグナルプロセッサ(DSP)やField-Programmable Gate Array(FPGA)にまでわたる、あらゆる種類のデバイスのプログラミングに適しています。 それでは、一番簡単な例であり、おそらくもっとも代表的で単純な例である、ベクトルの加算から考えてみます。加算する2つの整数配列と、結果を格納する1つの配列があります。図5に示すように、1つ目の配列と2つ目の配列から要素を1つずつ取得し、その合計を結果の配列に格納します。 図5:2つの配列の内容を加算し、合計を結果の配列に格納 おわかりのように、この演算はすべて同時に進めることができます。つまり、非常に並列度が高いということです。それぞれの加算演算は、別々のGPUコアにプッシュすることができます。すなわち、コアが2,048個(NVIDIA 1080グラフィックス・カードのコア数です)あれば、2,048個の加算演算を同時に実行できます。つまり、テラフロップ級の計算能力が皆さんを待っているということです。次に示すのは、1000万個の整数の配列を処理するコードです。このコードは、JOCLのサイトに掲載されているサンプルです。[訳注:実際のコメントは英語です。] public class ArrayGPU { /** * OpenCLプログラムのソース・コード */ private static String programSource = "__kernel void "+ "sampleKernel(__global const float *a,"+ " __global const float *b,"+ " __global float *c)"+ "{"+ " int gid = get_global_id(0);"+ " c[gid] = a[gid] + b[gid];"+ "}"; public static void main(String args[]) { int n = 10_000_000; float srcArrayA[] = new float[n]; float srcArrayB[] = new float[n]; float dstArray[] = new float[n]; for (int i=0; i<n; i++) { srcArrayA[i] = i; srcArrayB[i] = i; } Pointer srcA = Pointer.to(srcArrayA); Pointer srcB = Pointer.to(srcArrayB); Pointer dst = Pointer.to(dstArray); // 使用するプラットフォーム、デバイス・タイプ、 // デバイス番号 final int platformIndex = 0; final long deviceType = CL.CL_DEVICE_TYPE_ALL; final int deviceIndex = 0; // このサンプルのこれ以降でのエラー・チェックを省くため、例外を有効化する CL.setExceptionsEnabled(true); // プラットフォームの数を取得する int numPlatformsArray[] = new int[1]; CL.clGetPlatformIDs(0, null, numPlatformsArray); int numPlatforms = numPlatformsArray[0]; // プラットフォームIDを取得する cl_platform_id platforms[] = new cl_platform_id[numPlatforms]; CL.clGetPlatformIDs(platforms.length, platforms, null); cl_platform_id platform = platforms[platformIndex]; // コンテキスト・プロパティを初期化する cl_context_properties contextProperties = new cl_context_properties(); contextProperties.addProperty(CL.CL_CONTEXT_PLATFORM, platform); // プラットフォームのデバイス数を取得する int numDevicesArray[] = new int[1]; CL.clGetDeviceIDs(platform, deviceType, 0, null, numDevicesArray); int numDevices = numDevicesArray[0]; // デバイスIDを取得する cl_device_id devices[] = new cl_device_id[numDevices]; CL.clGetDeviceIDs(platform, deviceType, numDevices, devices, null); cl_device_id device = devices[deviceIndex]; // 選択したデバイスのコンテキストを作成する cl_context context = CL.clCreateContext( contextProperties, 1, new cl_device_id[]{device}, null, null, null); // 選択したデバイスのコマンド・キューを作成する cl_command_queue commandQueue = CL.clCreateCommandQueue(context, device, 0, null); // 入出力データ用のメモリ・オブジェクトを割り当てる cl_mem memObjects[] = new cl_mem[3]; memObjects[0] = CL.clCreateBuffer(context, CL.CL_MEM_READ_ONLY | CL.CL_MEM_COPY_HOST_PTR, Sizeof.cl_float * n, srcA, null); memObjects[1] = CL.clCreateBuffer(context, CL.CL_MEM_READ_ONLY | CL.CL_MEM_COPY_HOST_PTR, Sizeof.cl_float * n, srcB, null); memObjects[2] = CL.clCreateBuffer(context, CL.CL_MEM_READ_WRITE, Sizeof.cl_float * n, null, null); // ソース・コードからプログラムを作成する cl_program program = CL.clCreateProgramWithSource(context, 1, new String[]{ programSource }, null, null); // プログラムをビルドする CL.clBuildProgram(program, 0, null, null, null, null); // カーネルを作成する cl_kernel kernel = CL.clCreateKernel(program, "sampleKernel", null); // カーネルに渡す引数を設定する CL.clSetKernelArg(kernel, 0, Sizeof.cl_mem, Pointer.to(memObjects[0])); CL.clSetKernelArg(kernel, 1, Sizeof.cl_mem, Pointer.to(memObjects[1])); CL.clSetKernelArg(kernel, 2, Sizeof.cl_mem, Pointer.to(memObjects[2])); // ワーク・アイテムの次元を設定する long global_work_size[] = new long[]{n}; long local_work_size[] = new long[]{1}; // カーネルを実行する CL.clEnqueueNDRangeKernel(commandQueue, kernel, 1, null, global_work_size, local_work_size, 0, null, null); // 出力データを読み取る CL.clEnqueueReadBuffer(commandQueue, memObjects[2], CL.CL_TRUE, 0, n * Sizeof.cl_float, dst, 0, null, null); // カーネル、プログラム、メモリ・オブジェクトを解放する CL.clReleaseMemObject(memObjects[0]); CL.clReleaseMemObject(memObjects[1]); CL.clReleaseMemObject(memObjects[2]); CL.clReleaseKernel(kernel); CL.clReleaseProgram(program); CL.clReleaseCommandQueue(commandQueue); CL.clReleaseContext(context); } private static String getString(cl_device_id device, int paramName) { // 照会する文字列の長さを取得する long size[] = new long[1]; CL.clGetDeviceInfo(device, paramName, 0, null, size); // 適切なサイズのバッファを作成し、情報を格納する byte buffer[] = new byte[(int)size[0]]; CL.clGetDeviceInfo(device, paramName, buffer.length, Pointer.to(buffer), null); // バッファ(末尾にある\0のバイトは除く)から文字列を作成する return new String(buffer, 0, buffer.length-1); } } Javaらしくありませんが、確かにJavaのコードです。以降でこのコードを解説しますが、あまり時間をかけないでください。後ほどもう少し簡単なソリューションを紹介するからです。 コードには細かいコメントが付いていますが、順を追って見ていきます。ご覧のとおり、コードは非常にCに近い形になっています。それもそのはずです。というのも、JOCLはOpenCLのバインディングにすぎないからです。冒頭では、コードが文字列に格納されています。実は、このコードこそがもっとも重要な部分です。このコードはOpenCLでコンパイルされ、ビデオ・カードに送信されて実行されます。このコードをカーネルと呼びます。この用語をOSのカーネルと混同しないでください。ここでのカーネルは、デバイス・コードのことです。このカーネル・コードはCのサブセットで書かれています。 カーネルの後には、Javaバインディングのコードがあります。このコードで行っているのは、デバイスの設定およびオーケストレーション、データ・チャンクの作成、デバイスでの適切なメモリ・バッファ(入力データ用および結果データ用)の作成です。 つまり、コードには、「ホスト・コード」(通常は言語バインディングであり、今回の場合はJava)と、「デバイス・コード」があります。ホストで実行されるものとデバイスで実行されるべきものは必ず区別します。ホストがデバイスを制御するからです。 先ほどのコードは、GPU版の「Hello World!」であると考えてください。ご覧のとおり、「おまじない」はかなりの量になっています。 忘れてはいけないのが、SIMDの機能です。お使いのハードウェアがSIMD拡張をサポートしている場合、算術演算コードの実行を大幅に高速化することができます。例として、行列の乗算を行うカーネル・コードを見てみます。次に示すのは、Javaアプリケーションに文字列として書かれるコードです。 __kernel void MatrixMul_kernel_basic(int dim, __global float *A, __global float *B, __global float *C){ int iCol = get_global_id(0); int iRow = get_global_id(1); float result = 0.0; for(int i=0; i< dim; ++i) { result += A[iRow*dim + i]*B[i*dim + iCol]; } C[iRow*dim + iCol] = result; } 技術的に言えば、このコードでは、OpenCLフレームワークで設定されたデータ・チャンクに対して、「おまじない」の中で指定した命令を実行します。 お使いのビデオ・カードがSIMD命令をサポートしており、4次元浮動小数点数ベクトルを処理できる場合、先ほどのコードを少しばかり最適化して次のようにすることができます。 #define VECTOR_SIZE 4 __kernel void MatrixMul_kernel_basic_vector4( size_t dim, // 次元は単精度浮動小数点数 const float4 *A, const float4 *B, float4 *C) { size_t globalIdx = get_global_id(0); size_t globalIdy = get_global_id(1); float4 resultVec = (float4){ 0, 0, 0, 0 }; size_t dimVec = dim / 4; for(size_t i = 0; i < dimVec; ++i) { float4 Avector = A[dimVec * globalIdy + i]; float4 Bvector[4]; Bvector[0] = B[dimVec * (i * 4 + 0) + globalIdx]; Bvector[1] = B[dimVec * (i * 4 + 1) + globalIdx]; Bvector[2] = B[dimVec * (i * 4 + 2) + globalIdx]; Bvector[3] = B[dimVec * (i * 4 + 3) + globalIdx]; resultVec += Avector[0] * Bvector[0]; resultVec += Avector[1] * Bvector[1]; resultVec += Avector[2] * Bvector[2]; resultVec += Avector[3] * Bvector[3]; } C[dimVec * globalIdy + globalIdx] = resultVec; } このコードを使うことで、パフォーマンスを2倍にすることができます。 すばらしいですね。Javaの世界でGPUのパワーを解き放ちました。しかし、Java開発者である皆さんは、このようなバインディングを実行してCコードを記述し、低レベルの詳細を扱う作業を実際に行いたいと思うでしょうか。当然ながら、私は遠慮したいと思います。ここまででGPUアーキテクチャの使い方に関するある程度の知識は得られたため、先ほどのJOCLコード以外のソリューションも見てみることにします。 CUDAとJava CUDAはNVIDIAのソリューションであり、先に述べたコーディングの問題に対処するものです。CUDAでは、標準的なGPU演算のためにすぐに使用できる、数多くのライブラリが提供されます。たとえば、行列、ヒストグラム、さらにはディープ・ニューラル・ネットワークなどの演算に対応しています。比較的新しいライブラリ・リストには、多くの便利なバインディングがすでに含まれています。以下に挙げるのは、JCudaプロジェクトによるものです。 JCublas:行列全般 JCufft:高速フーリエ変換 JCurand:乱数全般 JCusparse:疎行列 JCusolver:因数分解 JNvgraph:グラフ全般 JCudpp:CUDA Data Parallel Primitives Libraryといくつかのソート・アルゴリズム JNpp:GPUによる画像処理 JCudnn:ディープ・ニューラル・ネットワーク・ライブラリ ここでは、乱数を生成するJCurandの使用について説明します。他の特別なカーネル言語は不要で、直接Javaコードから使用できます。次に例を示します。 ... int n = 100; curandGenerator generator = new curandGenerator(); float hostData[] = new float[n]; Pointer deviceData = new Pointer(); cudaMalloc(deviceData, n * Sizeof.FLOAT); curandCreateGenerator(generator, CURAND_RNG_PSEUDO_DEFAULT); curandSetPseudoRandomGeneratorSeed(generator, 1234); curandGenerateUniform(generator, deviceData, n); cudaMemcpy(Pointer.to(hostData), deviceData, n * Sizeof.FLOAT, cudaMemcpyDeviceToHost); System.out.println(Arrays.toString(hostData)); curandDestroyGenerator(generator); cudaFree(deviceData); ... ここでは、数学的に非常に強固な理論に基づき、高品質な乱数をより多く作成するためにGPUを使用しています。 いくつかのJARファイルをクラスパスに追加しておけば、JCudaを使って書いた汎用的なCUDAコードをJavaから呼び出すこともできます。その他の例については、JCudaのドキュメントをご覧ください。 低レベルなコードを回避する 前述の方法はいずれもすばらしいものに見えますが、実行するための「おまじない」、設定、そして言語数が多すぎます。一部だけでもGPUを使う方法はないのでしょうか。 こういったOpenCLやCUDAなどの内部処理の隅々まで考えなくてもよい方法はないのでしょうか。内部処理について考えずに、Javaのコードを書くだけの方法はないでしょうか。そういったニーズに役立つかもしれないのが、Aparapiプロジェクトです。Aparapiは、「a parallel API」(並列API)を表しています。筆者は、内部的にOpenCLを使うGPUプログラミング向けのHibernateのようなものだと考えています。ベクトル加算の例を見てみます。 public static void main(String[] _args) { final int size = 512; final float[] a = new float[size]; final float[] b = new float[size]; /* 配列に乱数を設定する */ for (int i = 0; i < size; i++){ a[i] = (float) (Math.random() * 100); b[i] = (float) (Math.random() * 100); } final float[] sum = new float[size]; Kernel kernel = new Kernel(){ @Override public void run() { I int gid = getGlobalId(); sum[gid] = a[gid] + b[gid]; } }; kernel.execute(Range.create(size)); for(int i = 0; i < size; i++) { System.out.printf("%6.2f + %6.2f = %8.2f\n", a[i], b[i], sum[i]) } kernel.dispose(); } これは純粋なJavaコード(Aparapiのドキュメントから引用しています[訳注:実際のコメントは英語です])ですが、KernelやgetGlobalIdといったGPU分野特有の用語があちこちに散りばめられています。GPUプログラミングの仕組みを理解しておく必要がある点は変わりませんが、これまで紹介した方法よりはJavaフレンドリな形でGPGPUを取り扱うことができます。さらに、AparapiではOpenGLコンテキストを下層にあたるOpenCLレイヤーに簡単にバインドする方法を提供しています。これにより、データを完全にビデオ・カード上で保持できるようになるため、メモリの待機時間の問題も解消されます。 独立した計算を大量に行う必要がある場合は、Aparapiを検討してください。多数の例の中から、大規模並列計算にぴったりなユースケースが見つかります。 また、適切な計算をCPUからGPUに自動オフロードしてくれるプロジェクト(TornadoVMなど)も複数存在します。こういったものにより、大規模な最適化をすぐに行うことができます。 まとめ GPUがゲーム・チェンジャーの役割を果たすようなメリットを発揮できる用途は数多くありますが、まだ障害がいくつかあると感じる方もいらっしゃるでしょう。しかし、JavaとGPUを組み合わせれば、すばらしい可能性を開くことができます。本記事は、この広大なトピックの表面をなぞっただけにすぎません。本記事の目的は、JavaからGPUにアクセスするための、さまざまな高レベルおよび低レベルな選択肢を紹介することでした。この領域を突き詰めていけば、パフォーマンス上の大きなメリットが得られることでしょう。並列に実行できる大量の計算が必要になる複雑な問題の場合、そのメリットは特に大きくなります。 ドミトリー・アレクサンドロフ Dmitry Aleksandrov(@bercut2000):T-Systemsのチーフ・アーキテクト、Java Champion、Oracle Groundbreaker。ブロガーとしても活躍。主に銀行/電気通信分野のJava Enterpriseで10年超の経験を持ち、JVM動的言語やGPUによる大規模並列計算などの機能にも関心を持つ。Bulgarian Java User Groupの共同リーダーの1人で、jPrime Confの共同主催者でもある。地元のイベントや、JavaOne/Code One、Devoxx、JavaZone、Joker/JPointなどのカンファレンスで、頻繁に講演を行っている。

※本記事は、Dmitry Aleksandrovによる"Programming the GPU in Java"を翻訳したものです。 JavaからGPUにアクセスすることで発揮される圧倒的な能力:GPUの動作の仕組みとJavaからのアクセス方法を解説   著者:Dmitry Aleksandrov 2020年1月10日   GPU(グラフィックス・プロセッシング・ユニット)プログラミングと聞くと、Javaプログ...

Java Magazine

Epsilon:JDKの「何もしない」ガベージ・コレクタ

※本記事は、Andrew Binstockによる"Epsilon: The JDK’s Do-Nothing Garbage Collector"を翻訳したものです。 ガベージ・コレクションを行わないJavaメモリ・アロケータのメリット   著者:Andrew Binstock 2019年11月21日   JDKのパフォーマンス・チューニングは、高度な技術です。多くの場合、最適なガベージ・コレクタを選択してその設定を微調整し、与えられたワークロードに及ぼす影響の最小化を実現することが中心となります。この作業には、常に困難がつきまといます。ガベージ・コレクションをまったく行わなかった場合のワークロード実行速度を知ることが難しいのです。このニーズに対処するため(そして後ほど触れるその他の理由のため)、JDK 11でEpsilonガベージ・コレクタ(GC)が導入されました。このGCでは、JEP 318で定義されているように、メモリの割当ては行いますが、リサイクルは行いません。つまり、ガベージ・コレクションを行いません。 JVMにEpsilon GCを使わせたい場合、コマンドラインで次の2つのランタイム・スイッチを指定する必要があります。   -XX:+ UnlockExperimentalVMOptions -XX:+ UseEpsilonGC 次のコードについて、上記のスイッチを指定して実行した場合と指定せずに実行した場合とを比較すれば、Epsilonによる影響を確認できます。 パブリッククラスEpsilonDemo { public static void main(String [] args){ final int GIGABYTE = 1024 * 1024 * 1024; final int ITERATIONS = 100; System.out.println( "開始割り当て..."); // 一度に1 GBのメモリを割当て for(int i = 0; i <ITERATIONS; i ++){ var array =新しいバイト[ギガバイト]; } System.out.println( "正常に完了しました"); } } このコードでは、100 GBのメモリを割り当てようとしています。行っているのはそれだけです。単に割当てだけを行い、終了しています。Epsilonを指定せずに上記のコードを実行した場合、最初と最後のprintln文に含まれる2つのリテラルが表示されます。つまり、デフォルトのGCが、1 GBのブロックを100個割り当ててから、システムで利用可能なメモリに収まるように、割当て済みブロックのガベージ・コレクションを必要に応じて行ったことを意味します。 しかし、前述のコマンドライン・スイッチを指定してEpsilon GCを使い、上記のコードを実行した場合、プログラムの出力は次のようになります。 割り当てを開始しています... java.lang.OutOfMemoryError:Java heap spaceによる終了 Epsilonでは以前に割り当てられたメモリのガベージ・コレクションを行わなかったため、ヒープは利用可能なメモリを超過し、あまり見ることがない「Out of Memory」エラーがJDKで発生しました。 このコードを実行するためには、JDK 11以降が必要です。また、利用可能なヒープが100 GB未満でない場合、このとおりの実行結果は得られません。それよりも大きなシステムで実行する場合は、必要に応じてITERATIONS変数を増やすだけで、エラーの発生が可能になります。 パフォーマンスのチューニング作業に限定せずに、デプロイするプログラムにもEpsilonを使いたいという強いニーズがあります。Epsilonチームは、基本的にそのような用途を推奨していませんが、2つの例外は存在します。実行時間が短いプログラムでも、すべてのプログラムと同様、実行終了時にガベージ・コレクタが起動します。しかし、JEP 318で説明されているように、「ヒープの無意味なクリーンアップのためのガベージ・コレクション・サイクルを受け入れることは時間の無駄です。結局のところ、プログラムが終了すればヒープは解放されるからです」。 Epsilonチームは、遅延による影響が特に大きい特定の状況において、Epsilonが使用される可能性があることを予見していました。JEPには、「遅延による影響が非常に大きく、開発者がメモリの割当てを意識してメモリのフットプリントを完全に把握しているアプリケーション、または(ほぼ)完全にガベージ・コレクションが行われないアプリケーションでは、GCサイクルを受け入れることが設計上の問題になる可能性があります」と書かれています。 しかし、上記以外での使用は推奨されていません。ほとんどヒープ領域を使わないことがわかっているプログラムであっても、予期しないメモリの制約があるシステムでEpsilonを使って実行すれば、うまく動作しないリスクがあります。そのため、Epsilonでは、試験運用版機能であることを示すコマンドライン・スイッチが必要となっています。厳密に言えば、EpsilonはJDK 11(およびそれ以降のJDK)に必ず含まれるものですが、コマンドライン・スイッチに含まれる「Experimental」という言葉で、試験運用版機能であることを思い出すはずです。 最後にパフォーマンス・チューニングに話を戻します。ガベージ・コレクションによってプログラムのパフォーマンスがどのくらいの影響を受けているかを知りたいと思ったことがあったのなら、その解決策はEpsilonです。 アンドリュービンストック Andrew Binstock(@platypusguy):Java Magazineの前編集長。Dr.Dobb's Journalの元編集長。オープンソースのiText PDFライブラリを扱う会社(2015年に他社によって買収)の共同創業者。16刷を経て、現在もロング・テールとして扱われている、Cでのアルゴリズム実装についての書籍を執筆。以前はUNIX Reviewで編集長を務め、その前にはC Gazetteを創刊し編集長を務めた。妻とともにシリコン・バレーに住んでおり、コーディングや編集をしていないときは、ピアノを学んでいる。          

※本記事は、Andrew Binstockによる"Epsilon: The JDK’s Do-Nothing Garbage Collector"を翻訳したものです。 ガベージ・コレクションを行わないJavaメモリ・アロケータのメリット   著者:Andrew Binstock 2019年11月21日   JDKのパフォーマンス・チューニングは、高度な技術です。多くの場合、最適なガベージ・コレクタを選択してその...

Java Magazine

JDKの超高速な新ガベージ・コレクタを理解する

※本記事は、Raoul-Gabriel Urma と Richard Warburtonによる"Understanding the JDK’s New Superfast Garbage Collectors"を翻訳したものです。 ZGCとShenandoah、そして改善版のG1により、今までになく停止しないJavaが実現   著者:Raoul-Gabriel Urma、Richard Warburton 2019年11月21日   ここ半年間で行われてきた開発のうち、特にエキサイティングなものがJDKのガベージ・コレクタ(GC)の内部処理に関するものです。本記事ではさまざまな改善について説明しますが、その多くはJDK 12で初登場し、JDK 13でも引き続き行われているものです。まずは、Shenandoahについて説明します。Shenandoahは低遅延GCで、アプリケーションとほぼ同時に処理されます。また、JDK 12リリースに含まれるZGC(Java 11で導入された低遅延コンカレントGC)に対して行われた最新の改善にも触れたいと思います。さらに、Garbage First(G1)GCにおける2つの改善点について詳しく説明します。G1はJava 9以降でのデフォルトGCです。   GCの概要 Javaは非常に生産性が高い言語です。CやC++などの古い言語と比べた場合、開発者にとってJavaの利点の1つとなるのは、ガベージ・コレクションを使えることです。Java開発者である皆さんが、メモリの場所を明示的に解放せずに、メモリ・リークを起こしてしまうことについて心配する必要はほとんどありません。また、メモリを使い終わる前に解放してしまい、アプリケーションをクラッシュさせることを心配する必要もありません。ただし、ガベージ・コレクションは大きな生産性をもたらす反面、開発者は幾度となくパフォーマンスへの影響を懸念してきました。ガベージ・コレクションによってアプリケーションの速度が低下することはないでしょうか。また、アプリケーションが何度も止まってユーザー・エクスペリエンスが悪化することはないでしょうか。 多数存在するガベージ・コレクション・アルゴリズムについて、何年もかけて試行やテストが行われ、パフォーマンスの改善が繰り返されています。そのようなアルゴリズムのパフォーマンスは、一般的に2つの部分に分けて考えることができます。1つ目は、ガベージ・コレクションのスループットです。これは、アプリケーションのCPUタイムのうち、アプリケーションのコードの実行ではなく、ガベージ・コレクション作業の実行に使われる時間を意味します。2つ目は、その結果として生じる遅延、すなわち個々の一時停止による待ち時間です。 多くの停止型GC(たとえば、Java 9より前のデフォルトGCだったParallel GC)では、アプリケーションのヒープ・サイズを増やすことで、スループットは向上しますが、最長の一時停止時間は長くなります。このタイプのGCでは、ヒープの増加によりガベージ・コレクション・サイクルの実行頻度が低下するため、ガベージ・コレクションの作業効率は向上します。しかし、それぞれのサイクルで実行する作業が増えることから、個々の一時停止時間は長くなります。つまり、大きなヒープでParallel GCを使用した場合、一時停止時間が長くなる可能性があります。割り当てられた古い世代のオブジェクトを回収するためにかかる時間は、世代のサイズ、すなわちヒープのサイズに応じて増加するからです。しかし、インタラクティブではないバッチ・ジョブなどを実行している場合は、Parallel GCが効率的なコレクタとなる可能性もあります。 Java 9以降のOpenJDKとOracle JDKでは、G1コレクタがデフォルトのGCとなっています。G1がガベージ・コレクションを行う際のアプローチを大まかに表現するなら、ユーザーが提供する目標時間に応じて、GCによる一時停止を細かく分割するというものです。つまり、一時停止時間を短くしたい場合は、目標時間を短くすればよいことになります。逆に、GCに使われるCPUを減らしてアプリケーションに使われるCPUを増やしたい場合は、目標時間を長くします。Parallel GCはスループット重視のコレクタでしたが、G1は万能型を目指しており、スループットを下げて一時停止時間を短くする仕組みを提供しています。 ただし、G1を使えば一時停止時間を自在に操れるというわけではありません。ヒープのサイズが巨大であること、または短時間に多数のオブジェクトが割り当てられることが原因で、1回のガベージ・コレクション・サイクルで行うべき作業量が増加するにつれて、一時停止時間を分割するというアプローチにも限界が見えてきます。たとえるなら、大きな塊の食べ物を細かく切れば、消化しやすくはなるものの、皿の上の食べ物が多すぎれば、食べ終えるまでに長い時間がかかるということです。ガベージ・コレクションにも同じことが言えます。 この点が、JDK 12のShenandoah GCによって解決しようとしている問題の領域です。Shenandoahは遅延に関するスペシャリストで、ヒープが大きくても一時停止時間を一貫して短く抑えることができます。Parallel GCと比べた場合、ガベージ・コレクション作業の実行にかかるCPUタイムがわずかに増加する可能性がありますが、一時停止時間は大幅に短縮されます。これは、金融やギャンブル、広告といった業界の低遅延システムには非常に有効です。インタラクティブなWebサイトでも、一時停止時間が長いことによってユーザーがいらいらする可能性がある場合は、同じことが言えます。 本記事では、これらのGCの最新バージョンと、G1の最新アップデートについて説明します。皆さんのアプリケーションに最適な機能バランスを実現するうえで、参考になれば幸いです。   Shenandoah Shenandoahは、JDK 12の一部としてリリースされた新しいGCです。実は、Shenandoahの開発作業は、JDK 8uおよび11uリリースの改善としてバックポートされています。これまでJDK 12にアップグレードする機会がなかったという方にとっては朗報でしょう。 Shenandoahに切り替える方がいい場合と、その理由について考えてみます。Shenandoahの内部の仕組みについて詳しく触れることはできませんが、このテクノロジーに興味がある方は、本号の記事や、OpenJDK WikiのShenandoahのページをご覧ください。 ShenandoahがG1より優れている主な点は、アプリケーション・スレッドに対するガベージ・コレクション・サイクルの同時実行性が高いことです。G1では、アプリケーションを一時停止しなければヒープ領域を退避(オブジェクトを移動)できません。一方のShenandoahでは、アプリケーションを実行しながらオブジェクトを再配置することができます。この同時再配置を実現するために使われているのが、ブルックス・ポインタと呼ばれるものです。このポインタは、Shenandoahヒープ内のすべてのオブジェクトが持つ追加フィールドで、それぞれのオブジェクト自体を指しています。 なぜこのようなことをしているのでしょうか。オブジェクトを移動する場合、ヒープ内にあってそのオブジェクトを参照しているすべてのオブジェクトを修正する必要があります。Shenandoahでは、オブジェクトを新しい場所に移動するとき、古いブルックス・ポインタはそのままにして、参照をオブジェクトの新しい場所に転送します。オブジェクトが参照された場合、アプリケーションでは転送ポインタをたどって新しい場所を参照します。転送ポインタを持つ古いオブジェクトは、最終的にはクリーンアップする必要があります。しかし、オブジェクト自体を移動する手順からクリーンアップ操作を切り離すことにより、Shenandoahではオブジェクトの同時再配置が容易に実現可能となっています。 Java 12以降のアプリケーションでShenandoahを使うためには、次のオプションを使ってShenandoahを有効化します。 -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC 現時点でいきなりJava 12への移行はできないものの、Shenandoahを試してみたいという方は、Java 8とJava 11へのバックポートを利用できます。なお、Oracleが提供するJDKビルドではShenandoahを利用できない点は覚えておいてください。しかし、他のOpenJDKディストリビュータによるビルドでは、デフォルトの状態でShenandoahを使うことができるようになっています。Shenandoahについての詳細は、JEP 189をご覧ください。 コンカレントGCの選択肢は、Shenandoahだけではありません。ZGCもまた、OpenJDK(Oracleのビルドも含む)で提供されているGCです。ZGCもJDK 12で改善が行われています。そのため、ガベージ・コレクションによる一時停止問題の影響を受けているアプリを抱えており、Shenandoahを試してみたいと考えている方は、次に説明するZGCも確認してみることをお勧めします。   同時クラス・アンロード機能が搭載されたZGC ZGCの主な目的は、低遅延、スケーラビリティ、使いやすさです。これを実現するため、ZGCでは、スレッド・スタックのスキャンを除くすべてのガベージ・コレクション操作を行っている間も、Javaアプリケーションの実行を継続できるようにしています。さらに、数百MBからTBサイズまでのJavaヒープに対応しつつも、通常は2 ms以内という非常に短い一時停止時間を一貫して維持しています。 一時停止時間が短く予測可能であることは、アプリケーション開発者とシステム・アーキテクトの両方にとって重要なことでしょう。開発者は、ガベージ・コレクションによる一時停止を避けるため、巧妙に設計しようと悩む必要がなくなります。また、確実に短い一時停止時間を実現することは、非常に多くのユースケースにおいて大変重要なことですが、システム・アーキテクトも、そのためにGCパフォーマンス・チューニングに特化した専門知識を求められることがなくなります。ビッグ・データを扱うものなど、大量のメモリを必要とするアプリケーションにZGCが適しているのはそのためです。ただし、ヒープが小さくても、一時停止時間を予測可能な極めて短い時間にとどめる必要がある場合は、ZGCが有望な候補になります。 ZGCは、JDK 11に試験運用版機能として追加されました。JDK 12のZGCには、同時クラス・アンロードのサポートが追加されました。このサポートにより、使われていないクラスをアンロードする間もJavaアプリケーションは一時停止せずに実行を続けられるようになりました。 同時クラス・アンロードの実行には複雑な処理が必要です。そのため、クラスのアンロードは、すべてを一時停止させたStop-The-Worldの状態で行われるのが昔からの慣例でした。使われなくなった一連のクラスを特定するためには、まず参照処理を行う必要があります。続いて、ファイナライザの処理があります。私たちにとっては、Object.finalize()メソッドの実装です。参照処理の一部として、ファイナライザから到達できる一連のオブジェクトを検索する必要があります。ファイナライザでは、無制限にチェーンできるリンクを介して、クラスを推移的に生存させている可能性があるからです。ファイナライザから到達できるすべてのオブジェクトにアクセスした場合、膨大な時間がかかる可能性があります。最悪のケースでは、1つのファイナライザからJavaヒープ全体に到達する可能性すらあります。ZGCでは、Javaアプリケーションを実行しながら参照処理を行います(JDK 11でZGCが導入された時点でこの動作になっています)。 参照処理の終了後、不要になったクラスがZGCで認識されます。こういったクラスが破棄された結果、無効な古いデータを含むデータ構造が生まれます。次のステップでは、そういったデータ構造をすべて削除します。利用中のデータ構造から無効になったデータ構造へのリンクは削除されます。このリンク解除操作を行うためにたどる必要があるデータ構造には、いくつかの内部JVMデータ構造が含まれています。たとえば、コード・キャッシュ(すべてのJITコンパイルされたコードを含む)、クラス・ローダーのデータ・グラフ、文字列表、シンボル表、プロファイル・データなどです。無効なデータ構造へのリンクを解除する操作が終了した後は、無効なデータ構造を再度たどって削除し、最終的にはメモリを再利用できるようにする操作が行われます。 現在に至るまで、すべてのJDKのGCでは、以上のすべてをStop-The-World操作の中で行ってきました。Javaアプリケーションの遅延問題の原因はそこにあります。低遅延GCにとって、この動作は問題です。そこでZGCでは、このすべてをJavaアプリケーションと同時に実行するようになっています。そのため、クラスのアンロードをサポートする代わりに遅延というペナルティを払うことはありません。実際のところ、同時クラス・アンロードを行うために導入されたメカニズムによって、遅延はさらに少なくなりました。現在、ガベージ・コレクションに伴うStop-The-Worldの一時停止時間は、アプリケーションのスレッド数にのみ比例するようになっています。このアプローチが一時停止時間に及ぼす大きな影響を示したのが図1です。 図1:他のGCと比較した、ZGCの一時停止時間 現在、ZGCは、Linux/x86 64ビット・プラットフォームとJava 13以降のLinux/Aarchで試験運用版GCとして利用できます。次のコマンドライン・オプションを使うことで、ZGCを有効化することができます。 -XX:+UnlockExperimentalVMOptions -XX:+UseZGC ZGCについての詳細は、OpenJDK Wikiをご覧ください。   G1の改善 組織によっては、ランタイム・システムで試験運用版GCを使用できないことがあります。そういう方にうれしいお知らせなのは、G1にもいくつかの改善が行われていることです。G1コレクタでは、ガベージ・コレクション・サイクルを複数の一時停止時間に分割します。 割り当てられたオブジェクトは当初、「Young」世代の一部と見なされます。複数回のガベージ・コレクション・サイクルを生き残ると、やがて「身分保障」を得て、「Old」と見なされるようになります。G1内の各領域には、1つの世代のオブジェクトしか含まれません。そのため、Young領域、Old領域と呼ぶことができます。 G1が一時停止時間の目標を満たすためには、目標とする一時停止時間内に行うことができる作業の量を特定し、時間切れになる前にその作業を終えることができなければなりません。G1には、適切なサイズの作業を特定するための一連の複雑な経験則が組み込まれています。この経験則により、必要な作業時間を巧みに予測しますが、経験則が常に正確とは限りません。さらに事態を複雑にしているのは、G1ではYoung領域の一部のみを回収することはできないという事実です。G1では、1回のガベージ・コレクション・パスでYoung領域全体を回収します。 Java 12では、G1の回収による一時停止を中断できる機能が追加され、この状況が改善されています。G1では、回収する領域数を経験則によって予測する際の正確さを追跡し、必要な場合に中断可能なガベージ・コレクションのみを続行します。具体的には、回収セット(1回のサイクルでガベージ・コレクションが行われる一連の領域)を、必須領域とオプション領域という2つのグループに分割して続行します。 必須領域は、GCサイクルで必ず回収されます。オプション領域は時間が許せば回収されますが、回収されずに時間切れになった場合、回収パスは中断されます。必須領域には、すべてのYoung領域に加え、一部のOld領域が含まれる場合があります。Old世代の領域が必須領域に追加される条件は、オブジェクトの退避を確実に続行できるようにする場合と、予測されている一時停止時間を使い切る場合の2つです。 追加する領域数を計算するための経験則では、回収セット候補内の領域数を-XX:G1MixedGCCountTargetの値で割った数が使用されて続行されます。G1では、Old世代の領域をさらに回収する時間があると予測した場合、他のOld領域も必須領域セットに追加します。この追加は、利用可能な一時停止時間の80%を使い切ると見込まれるまで行われます。 この作業の結果、G1では混合GCサイクルの中断も終了も可能ということになります。それにより、GCに起因する一時停止遅延は少なくなり、G1では高い確率で、一時停止時間の目標の達成頻度増加が可能になります。この改善点についての詳細は、JEP 344をご覧ください。   コミット済みの未使用メモリを即時返却 Javaに非常によく向けられる批判は、メモリを大量に消費するというものですが、もはやそのようなことはなくなります。JVMに必要以上のメモリが割り当てられているのは、コマンドライン・オプションによる指定のためであることもあります。しかし、メモリ関連のコマンドライン・オプションを指定していなければ、必要以上のメモリを割り当てているのはJVMでしょう。結局使われないRAMを割り当てるのはお金の無駄です。すべてのリソースが計測され、その量に応じて課金されるクラウド環境では、特にそうです。しかし、どうすればこの事態を解決し、Javaのリソース消費量を改善することができるのでしょうか。 一般的には、JVMが処理しなければならないワークロードは時間に応じて変化します。多くのメモリを必要とするときもあれば、必要としないときもあります。実際には、この点が影響することは多くありません。JVMは、起動時に大量のメモリを割り当て、必要ない場合でもそのメモリを確保し続ける傾向にあるからです。理想的には、他のアプリケーションやコンテナが使用できるように、未使用のメモリがJVMからオペレーティング・システムに返却されるようにすることもできます。Java 12ではこのような、未使用メモリの返却が可能になっています。 G1には、未使用メモリを解放する機能がすでに搭載されています。しかし、その解放が行われるのは、完全なガベージ・コレクション・パスの間のみです。多くの場合、完全なガベージ・コレクション・パスの実行頻度は低く、実行が好まれるものでもありません。Stop-The-Worldによる長時間のアプリケーション一時停止を伴う可能性があるからです。JDK 12のG1は、同時ガベージ・コレクション・パスの間に未使用メモリを解放できるようになりました。この機能は、ヒープがほぼ空の場合に特に有用です。ヒープがほぼ空の場合、GCサイクルがメモリを回収してオペレーティング・システムに返却する時間がかかる可能性があります。Java 12のG1では、メモリが即座にオペレーティング・システムへと確実に返却されるように、コマンドラインのG1PeriodicGCInterval引数で指定された期間にガベージ・コレクション・サイクルが発生しなかった場合に限り、同時ガベージ・コレクション・サイクルを呼び出そうとします。そして、この同時ガベージ・コレクション・サイクルの終了時に、メモリがオペレーティング・システムに返却されます。 この定期的な同時ガベージ・コレクション・パスによって不要なCPUオーバーヘッドが加わらないことが保証されるようにするため、システムの一部がアイドル状態であるときに限って実行されるようになっています。この同時実行サイクルを呼び出すか呼び出さないかを判断する基準となる測定値は、1分当たりのシステム負荷の平均値です。この値が、G1PeriodicGCSystemLoadThresholdで指定された値よりも低くなければなりません。 詳細については、JEP 346をご覧ください。   まとめ 本記事では、アプリケーションでのGCによる一時停止時間についての悩みを解消できるいくつかの方法を紹介しました。G1の改善は続くものの、ヒープ・サイズが増加し、一時停止時間が許容されにくくなるにつれて、ShenandoahやZGCなどの新しいGCによって、スケーラブルで一時停止時間の短い未来が実現することを覚えておいて損はありません。 Raoul-Gabriel Urma Raoul-Gabriel Urma(@raoulUK):イギリスのデータ・サイエンティストや開発者の学習コミュニティをリードするCambridge SparkのCEO/共同創業者。若いプログラマーや学生のコミュニティであるCambridge Coding Academyの会長/共同創設者でもある。ベストセラーとなったプログラミング関連書籍『Java 8 in Action』(Manning Publications、2015年)の共著者として執筆に携わった。ケンブリッジ大学でコンピュータ・サイエンスの博士号を取得している。   Richard Warburton Richard Warburton(@richardwarburto):Java Championであり、ソフトウェア・エンジニアの傍ら、講師や著述も行う。ベストセラーとなった『Java 8 Lambdas』(O'Reilly Media、2014年)の著者であり、Iteratr LearningとPluralsightで開発者の学習に貢献し、数々の講演やトレーニング・コースを実施している。ウォーリック大学で博士号を取得している。

※本記事は、Raoul-Gabriel Urma と Richard Warburtonによる"Understanding the JDK’s New Superfast Garbage Collectors"を翻訳したものです。 ZGCとShenandoah、そして改善版のG1により、今までになく停止しないJavaが実現   著者:Raoul-Gabriel Urma、Richard...

第19回 DBの可用性を高めよう - Data Guard編 (DBCS/ExaCS) | もしもみなみんがDBをクラウドで動かしてみたら

window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-159254751-1'); もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/04/20時点のものになります みなさん、こんにちは。 先日、Database Cloud Service(DBCS)とExadata Cloud Service(ExaCS)で、リージョン間での自動Data Guard機能がリリースされました!日本国内であれば、東京-大阪リージョン間での冗長構成が簡単に組めるということですね。今回のテーマは、その自動Data Guard機能について解説していきます。 目次 Oracle Data Guard/Active Data Guard について Oracle Cloudでの構成パターン スタンバイ・データベースを作ってみよう-前提条件 DBCSのスタンバイDBシステムの作成方法 ExaCSのスタンバイ・データベースの作成方法 Data Guardの構成について Data Guardの切り替えについて Data Guard構成に含まれるDBの削除 まとめ よくある質問   1. Oracle Data Guard/Active Data Guard について Oracle Data Guardとは、変更履歴(REDO)を利用して自動でリアルタイム・データベース複製を持つことが出来る機能です。この機能を利用することによって、データベース障害やリージョン障害などのRTO/RPOを短くすることができ、広範囲な計画停止(メンテナンス)においても切り替えることによって停止時間を極小化することが可能です。バックアップを取得していても、有事の際の復旧において、大量データのリストアが必要になる場合ではRTOを満たせないケースもあります。こういったケースに備えて、バックアップだけでなく、すぐに切り替えられるスタンバイを持つことは重要です。災害対策(DR)としてのデータ保護はもちろんのこと、移行やアップグレードの停止時間短縮といった利用用途もあります。また、参照専用として利用可能なActive Data Guardにしたり、一時的に読み書き可能なスナップショット・スタンバイとして利用したりと、普段から利用できるスタンバイ・データベースを持つことができます。参照処理をオフロードしたり、この仕組みを応用してデータ破損が検知された場合にクライアントにエラーを返すことなく自動修復をしてくれる自動メディア・ブロックリカバリ機能も使えるため、Data Guardであればスタンバイのリソースも有効活用してROIを高めつつ、きちんと切り替えられるスタンバイを持つということが可能です。   2. Oracle Cloudでの構成パターン Oracle Cloud上でData Guardを利用する際の基本的な構成については、大きく分けて3つのパターンがあります。 同一リージョン内でのData Guard : 主に、データベース障害やDBシステム障害やメンテナンスなどを考慮したローカル・スタンバイ 別リージョン間でのData Guard : 主に、リージョン障害やメンテナンス時の切り替え先としてローカル・スタンバイ環境をを持たない場合のリモート・スタンバイ ハイブリッドData Guard : オンプレミスとクラウド間で構成する、ハイブリッド型のオフサイト・スタンバイ クラウドの画面上から、(1) 同一リージョン内と(2) 別リージョン間でのData Guard構成が、簡単に構築・管理が可能です。(3)のハイブリッドの場合は、手動で構成が必要となりますが手順を解説したホワイト・ペーパーがあるのでご参照ください。 DBCS : Hybrid Data Guard to Oracle Cloud Infrastructure 英語 / 日本語 ExaCS : Hybrid Data Guard to Exadata Cloud Services 英語   3. スタンバイ・データベースを作ってみよう まずは、構築するにあたり前提条件を確認してみましょう。 前提条件 1) Data Guard/Active Data Guardを使うのに必要なエディション DBCSでは、Data GuardはEnterprise Edition以上、Active Data GuardはExtream Performanceが必要 Exadata Cloud Serivce(ExaCS)はExtream Perfomaneのため、どちらもデフォルトで利用可能 2) Oracle Cloudのインフラ側の前提条件 管理ユーザーのIAMサービス・ポリシーでの権限が付与済 プライマリDBシステムとスタンバイDBシステム間での通信設定(最低限TCPのポート1521を有効化。別リージョン間であればVCN間のピアリング設定が必要) 3) DBCSとExaCSのDBシステム側の前提条件 同一コンパートメント内 同一DBバージョン・パッチ間 (*) 同一エディション同士 同一サービス間 作成・管理できるスタンバイは1つのフィジカル・スタンバイ 3のDBシステム側の前提条件を満たせない構成の場合、手動でData Guardを構築することは可能です(1と2は必須)。例えば、2つ以上のスタンバイを持ちたい場合や、DBCSとExaCS間などです。なお、(*)が付いている条件はOracle Data Guardとしての前提条件になります。 DBCSとExaCSで作成手順が少し異なるので、それぞれの大まかな流れを最初に説明します。 ・DBCSの場合 プライマリ用のDBシステムを作成 プライマリのDBシステムの管理画面から、Data Guardアソシエーションを有効化して、スタンバイDBシステムとスタンバイ・データベースを作成 ・ExaCSの場合 プライマリ用のDBシステムと、スタンバイ用のDBシステムを作成 プライマリのDBシステムの管理画面から、Data Guardアソシエーションを有効化して、スタンバイ・データベースを作成 それでは、それぞれのサービスごとに作成していきましょう。上記の手順1のDBシステムは作成済として、2の部分を解説します。   DBCSのスタンバイDBシステムの作成方法 1.プライマリDBシステム上のプライマリ・データベースの『データベース詳細』ページから、『Data Guardアソシエーション』を選択 2.『Data Guardの有効化』をクリック 3.スタンバイDBシステムとデータベースの情報を入力 ピアDBシステムの作成(スタンバイDBシステム) 表示名 : スタンバイDBシステムの表示名 リージョン : スタンバイDBシステムを作成するリージョン 可用性ドメイン : スタンバイDBシステムを作成する可用性ドメイン シェイプの選択 : スタンバイDBシステムのシェイプ ネットワーク情報の指定 スタンバイ・データベースの構成 データベース・パスワード : プライマリDBのパスワードと同じものを入力 4.作成された構成を確認 今回は、同一リージョン内(アシュバーン)で作成してみました。下の画面イメージは、東京リージョンのプライマリDBシステム側でData Guardアソシエーションを確認した画面です。   ExaCSのスタンバイ・データベースの作成方法 1.プライマリDBシステム上のプライマリ・データベースの『データベース詳細』ページから、『Data Guardアソシエーション』を選択 2.『Data Guardの有効化』をクリック 3.スタンバイ・データベースの情報を入力 ピアDBシステムの選択(スタンバイDBシステム) リージョン : スタンバイDBシステムのあるリージョン 可用性ドメイン : スタンバイDBシステムのある可用性ドメイン シェイプ : スタンバイDBシステムのシェイプ ピアDBシステム : スタンバイDBシステム名を選択 スタンバイ・データベースの構成 データベース・パスワード : プライマリDBのパスワードと同じものを入力 4.作成された構成を確認 DBCSでは同一リージョン間で構成したので、ExaCSは別リージョン間(東京-大阪間)で作成してみました。下の画面イメージは、東京リージョンのプライマリDBシステム側でData Guardアソシエーションを確認した画面です。ピア・データベースとして、大阪リージョンにあるDBシステムにスタンバイ・データベースが作成されていることを示しています。   4.Data Guardの構成について 自動Data Guard機能で構築されると、下記の状態で構成されます(2020/04時点) スタンバイへのREDO適用が開始されている状態 保護モード: 最大パフォーマンスモード 同期モード : 非同期 REDO圧縮 : 無効 REDO適用の自動パラレル化 (12.2以降) : 有効 Oracle Data Guard Broker : 有効 自動切り替え(ファスト・スタート・フェイルオーバー) : 無効 DBCSのOS上のコマンドツールはdbcli、ExaCSのOS上のコマンドツールはdbaascliが用意されており、rootユーザーでData Guard関連の管理や操作も可能です。 ・DBCSのコマンドツール dbcliでの、Data Guard構成確認 マニュアル # /opt/oracle/dcs/bin/dbcli list-dgconfigs # /opt/oracle/dcs/bin/dbcli describe-dataguardstatus -i <ID>     # /opt/oracle/dcs/bin/dbcli list-dgconfigs ID                                       Name                             Database Name        Role       Protection Mode    Apply Lag       Transport Lag   Apply Rate      Status     ---------------------------------------- -------------------------------- -------------------- ---------- ------------------ --------------- --------------- --------------- ----------   f468c07c-ccb6-4f0f-b740-a242659c93d9     DGP_iad17t_DGP_iad23s            DGP                  Primary    MaxPerformance     0 seconds       0 seconds       13.00 KByte/s   Configured # /opt/oracle/dcs/bin/dbcli describe-dataguardstatus -i f468c07c-ccb6-4f0f-b740-a242659c93d9   Dataguard Status details                                         ----------------------------------------------------------------                      ID: f468c07c-ccb6-4f0f-b740-a242659c93d9                    Name: DGP_iad17t_DGP_iad23s           Database Name: DGP                    Role: Primary         Protection Mode: MaxPerformance               Apply Lag: 0 seconds           Transport Lag: 0 seconds              Apply Rate: 11.00 KByte/s                  Status: Configured   ・ExaCSのコマンドツール dbaascliでの、Data Guard構成確認 # dbaascli dataguard status --dbname <DB名>   # dbaascli dataguard status --dbname emdgp DBAAS CLI version 19.4.4.0.0 Executing command dataguard status SUCCESS : Dataguard is up and running DETAILS:   Connected to "emdgp_nrt1mv" Connected as SYSDG. Configuration - fsc   Protection Mode: MaxPerformance   Members:   emdgp_nrt1mv - Primary database     emdgp_kix1qh - Physical standby database    Properties:     FastStartFailoverThreshold      = '30'     OperationTimeout                = '30'     TraceLevel                      = 'USER'     FastStartFailoverLagLimit       = '30'     CommunicationTimeout            = '180'     ObserverReconnect               = '0'     FastStartFailoverAutoReinstate  = 'TRUE'     FastStartFailoverPmyShutdown    = 'TRUE'     BystandersFollowRoleChange      = 'ALL'     ObserverOverride                = 'FALSE'     ExternalDestination1            = ''     ExternalDestination2            = ''     PrimaryLostWriteAction          = 'CONTINUE'     ConfigurationWideServiceName    = 'emdgp_CFG' Fast-Start Failover:  Disabled Configuration Status: SUCCESS   ・ocicli でのData Guard構成の確認 $ oci db data-guard-association list --database-id <DBのOCID> $ oci db data-guard-association list --database-id ocid1.database.oc1.ap-tokyo-1.abxhiljr34y6k27mzesnia7qr3xxhmyevgwqguxbuz3djahugmmn4ej4oejq {   "data": [     {       "apply-lag": "0 seconds computed 2 seconds ago",       "apply-rate": "2.46 MByte/s",       "database-id": "ocid1.database.oc1.ap-tokyo-1.abxhiljr34y6k27mzesnia7qr3xxhmyevgwqguxbuz3djahugmmn4ej4oejq",       "id": "ocid1.dgassociation.oc1.ap-tokyo-1.abxhiljrybsfqmz3amypzbxp6r4f6pib4q546n25fxw6gwqtoqcqykuvu77q",       "lifecycle-details": null,       "lifecycle-state": "AVAILABLE",       "peer-data-guard-association-id": "ocid1.dgassociation.oc1.ap-osaka-1.abvwsljru3pnn2kjfyi3gi5cu3eckkuz2trrx2tn6bnbzqo6ewtcwc5cvyoq",       "peer-database-id": "ocid1.database.oc1.ap-osaka-1.abvwsljraquduk67djge3zusvipkidjnbyorzpe3bawknsogihoh5q3sxqhq",       "peer-db-home-id": "ocid1.dbhome.oc1.ap-osaka-1.abvwsljrrxyfdcqvgel4n576snhcky3p3asqt7jvkle2rtmd22nfsfcw6fpq",       "peer-db-system-id": "ocid1.dbsystem.oc1.ap-osaka-1.abvwsljrgockrpzpvx3e236egr3xqqkkeqg4c5bffuwux77wmlixjx66cgyq",       "peer-role": "PRIMARY",       "protection-mode": "MAXIMUM_PERFORMANCE",       "role": "STANDBY",       "time-created": "2020-04-03T11:47:29.417000+00:00",       "transport-type": "ASYNC"     }   ] }   DBCSとExaCSの自動Data Guardの環境は、管理の簡易化からOracle Data Guard BrokerとOracle Flashback Databaseの機能が有効になっています。Data Guard Brokerは、Data Guardの管理フレームワークです。これを有効にすることで、常時Data Guard構成や状態がチェックされ、通常プライマリとスタンバイそれぞれで実行する必要がある処理を、DGMGRLというData Guard Brokerのコマンドツールで簡単に管理・運用が出来るようになります。コンソールやdbcliやdbaascliなどのクラウドツールでのData Guardの管理操作では、裏ではData Guard Brokerが動いています。そのため、Data Guard Brokerを無効にしてしまうと、クラウドツールが動作しなくなる可能性があるので無効化しないようにしましょう。 Flashback Databaseは、フェイルオーバー実施後に旧プライマリ・データベースを簡単にスタンバイとして回復させるために有効になっています。詳細は、次の章でお話ししますが、こちらも無効化してしまうと「回復」機能が使えなくなるので有効化のままがおすすめです。上記のようなコマンドラインで詳細情報も確認可能ですが、最低限必要なデータベース・ロールと適用ラグは、コンソールからも確認できます。 DBCSのコマンドツール dbcli OCI CLI コマンドリファレンス    5.Data Guardの切り替えについて コンソールやCLIから、簡単にData Guardの切り替え(スイッチオーバー、フェイルオーバー)や旧プライマリの回復(フェイルオーバー実施後に旧プライマリ・データベースを簡単にスタンバイとして復旧)が可能です。前述したクラウドツールのCLIでももちろん実行可能です。 スイッチオーバー スイッチオーバーは主に計画停止用途のもので、スタンバイにREDOを転送・適用をしきった状態で、プライマリとスタンバイを切り替えます。そのため、切り替え後には旧プライマリは新スタンバイとしてData Guard構成を保った状態となります。スイッチオーバーは、プライマリDBシステムのData GuardアソシエーションのスタンバイDBが表示されている箇所から実行します。 フェイルオーバー フェイルオーバーは主に計画外停止用途のもので、プライマリ側が利用できない状態の際にスタンバイ側に切り替える際に用いられます。旧プライマリは壊れている状態で切り替えられ、非同期転送をしている場合には未転送分データがない可能性もあり、基本的には切り替え後にスタンバイがない構成となります。そのため、フェイルオーバー後にもData Guardでの可用性構成を組むために、スタンバイを作成して再度Data Guardを構成することが必要となります。フェイルオーバーは、スタンバイDBシステムのData GuardアソシエーションのプライマリDBが表示されている箇所から実行します。 回復 フェイルオーバー後に活用されるのがFlashback Database機能です。旧プライマリを障害発生直前(スタンバイが切り替わる前の時点)までデータを戻し(フラッシュバック)、スタンバイにロールを変換してData Guard構成に組み込まれ、フラッシュバックしたことで生じる差分も自動で同期されるため、一からスタンバイを構築する必要はありません。そのため、DBCSやExaCSのData Guard機能では、コンソール上の『回復』というボタンをクリックするだけで簡単にData Guardが再構成されます。回復は、プライマリDBシステムのData GuardアソシエーションのスタンバイDB(旧プライマリ)が表示されている箇所から実行します。 6.Data Guard構成に含まれるDBの削除 Data Guardアソシエーションに含まれるデータベースもしくはDBシステムを削除する場合、最初にスタンバイ・データベース(DBシステム)を削除しましょう。スタンバイ・データベースが紐づけられている状態の時に、プライマリ・データベースを削除しようとするとエラーが表示され、削除できません。もし、プライマリ・データベースの環境のみを削除したい場合には、一度ロールを切り替えて削除対象の環境をスタンバイ・ロールにしてから削除という形をとって頂ければと思います。   7. まとめ 業務継続性の担保のために、計画停止/計画外停止を考慮した可用性構成をとることは大切です。ただ、可用性構成をとることが目的ではなく、必要な時にすぐに切り替えられ、きちんと使えるスタンバイを持つことが重要です。そのために、単にデータファイルをコピーしているわけではないデータを意識した同期方法、日頃から利用することで切り替え時にスタンバイも利用できないという事態を防ぎ、いざ切り替える際には簡単に切り替え・再構成できる環境であることが重要で、そういった観点で実装・機能拡張を続けているのがOracle Data Guardです。クラウド上であれば、ご紹介したように簡単に構築・管理ができるので、データベースはData Guardでの冗長構成を検討してみてください。   よくある質問 Q1) プライマリとスタンバイを異なるシェイプ同士で利用可能ですか A1) はい、可能です。以前は同一シェイプである必要がありましたが、現在は異なるシェイプ間も対応しています Q2) スタンバイの時だけ小さいシェイプにして、切り替えてプライマリにした時に大きいシェイプにすることは可能ですか A2) DBCSの場合、可能です。切り替え後に、スケール・アップ(シェイプの変更)をすることによって大きいシェイプに変更可能です。ExaCSの場合は、シェイプの変更ができません。そのため、異なるシェイプ間での構成は組むことはできますが、切り替え後に旧プライマリとは異なることを考慮する必要があります   関連リンク ・Oracle Cloud Infrastructure ドキュメント DBCS Oracle Data Guardの使用 ・Oracle Cloud Infrastructure ドキュメント Exadata DBシステムでのOracle Data Guardの使用 もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/04/20時点のものになります みなさん、こんにちは。 先日、Database Cloud Service(DBCS)とExadata Cloud Service(ExaCS)で、リージョン間での自動Data Guard機能がリリースされました!日本国内であれば、東京-大阪リージョン間での冗長構成が簡単に組めるという...

津島博士のパフォーマンス講座 第75回 SQL Plan Managementについて

津島博士のパフォーマンス講座 Indexページ ▶▶   皆さんこんにちは、疲れたときの気分転換や息抜きなどに読んでみてください。 今回は、実行計画の変化で苦労しているような方に、正しく理解して効果的に管理して欲しいと思い、SQL Plan Management(SPM:SQL計画管理)を取り上げることにしました。後半に、Oracle Database 19c(Oracle19c)からのSPMの拡張機能についても説明していますので、参考にしてください。   1. SQL Plan Managementの基礎 SPMは、実行計画の予測不可能な変化(新しいオプティマイザ統計、初期化パラメータの変化、アップグレードなど)によって、パフォーマンスが低下するのを防止する(遅い実行計画を使用しない)便利な機能ですが、大変そうに感じて使用するのをためらっている方も多いように思います。そのような方のために、SPMの基本的なこと(SPMのコンポーネント、計画の取得、選択、展開)を説明しながら、使用方法や問題点などを紹介していきます。 (1)SPMのコンポーネント まずは、SPMがどのように管理されているかについて説明します。 SPMは、それぞれのSQLの情報を、SYSAUX表領域上のSQL Management Base(SMB)に作成して、実行計画の管理を行います。そのため、SMBの領域は、使用可能なSYSAUX領域の割合として、SPACE_BUDGET_PERCENTパラメータ(デフォルトは10%)で制限されています(このようなSPMパラメータは、DBMS_SPM.CONFIGUREプロシージャで変更できます)。そのSMBには、以下のものが格納されています。 SQLステートメント・ログ 初期計画の自動取得で、繰返し実行されたSQL文かを確認するために、実行されたSQL文のSQLシグネチャ(大/小文字および空白が正規化されているSQLテキストで計算されたハッシュ値)をログに追加します。 SQL計画履歴 SQL文に対して生成された一連の実行計画で、SQL計画ベースライン(SQL文に対して使用が許可された計画)と未承認の計画が含まれます。計画ベースラインには、選択のときに優先させる固定計画(FIXEDパラメータがYES)も指定できます。未使用の計画は、保存期間(PLAN_RETENTION_WEEKSパラメータ、デフォルトは53週)が過ぎると自動的に削除されるので、基本は手動で削除する必要がありません。   SQL文は、SQLシグネチャを使用して特定するので、大/小文字や空白数が異なっても同じSQLとして扱うことが可能になっています。また、ユーザーAPI(DBMS_SPMパッケージなど)で使用するために、検索キーのSQLハンドル(SQLシグネチャから導出された文字列)と複数の実行計画から特定する計画名(plan_name)があります。 (2)計画の取得(Plan Capture) 次に、計画ベースラインに実行計画を取得する方法として、自動取得と手動取得を使用しますが、それぞれの特徴について説明します(SPMは、計画ベースラインを設定すると、その後の処理がすべて自動的に行えるようになっているので、計画ベースラインをどのように設定するかが重要になります)。 (a)自動取得 OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES初期化パラメータをTRUE(デフォルトはFALSE)にすることで、複数回実行されたSQLの最初の実行計画が自動的に計画ベースラインとして取得されます(初期計画の自動取得とも呼びます)。SPMを簡単に使用することができますが、複数回実行されたすべてのSQLが対象になり、多くなってしまうのが問題のため、第63回で説明したフィルター機能が追加されましたが、条件を決めるのが簡単ではなく、あまり改善されていません(これも使用するのを難しくしているように思います)。 (b)手動取得 対象のSQLを特定できるような場合に、DBMS_SPMパッケージを使用して実行計画のロードを行います(管理者がパフォーマンスを認識して行うものとして、すべて承認済みとして計画ベースラインに追加します)。これは、以下のソースから手動でロードできるので、いろいろな用途で使用することができます。 SQLチーニング・セット(アップグレードによるフォーマンス低下の防止など) ストアド・アウトライン(プラン・スタビリティからの移行) カーソル・キャッシュとAWR(継続的なシステムやデータの変化によるパフォーマンス低下の防止など) カーソル・キャッシュは、ロード先のSQLハンドルを指定できるので、異なるSQL(ヒントを指定したSQL)を登録することで、第38回で説明した「SQLを変更できないときのSQLチューニング」にも使用できます。 ステージング表からのアンパック(テスト環境で作成された実行計画の移行) 手動取得されたSQLでも、計画ベースラインにない実行計画は自動的に取得されるので、対象のSQL数が多くなければ、手動取得で使用するだけでも効果的です(「(3)計画の選択」で動作を説明しています)。これを知らない方が多いようなので、簡単な例を載せておきます(以下は、手動でカーソル・キャッシュから実行計画をロードした後に、異なる実行計画がAUTO-CAPTUREとして登録されています)。 SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = FALSE; -- <初期計画の自動取得がFALSE> … <カーソル・キャッシュからの手動ロード(実行計画が変化するようにヒントでフル・スキャンさせた実行計画)> … SQL> SELECT SQL_HANDLE, SQL_TEXT, PLAN_NAME, ORIGIN, ENABLED, ACCEPTED FROM DBA_SQL_PLAN_BASELINES 2 WHERE SQL_TEXT LIKE 'SELECT * FROM tab01%'; SQL_HANDLE SQL_TEXT PLAN_NAME ORIGIN ENA ACC -------------------- --------------------------------- ------------------------------ ----------------------------- --- --- SQL_cea4ad125e107766 SELECT * FROM tab01 WHERE c2 = 1 SQL_PLAN_cx95d29g10xv633f47f0e MANUAL-LOAD-FROM-CURSOR-CACHE YES YES -- <再度SQLを実行すると、計画ベースラインの計画が使用されるが、新しい計画は計画履歴(ACCepted=NO)に追加されている> SQL> SELECT * FROM tab01 WHERE c2 = 1; Execution Plan ---------------------------------------------------------- … Note ----- - SQL plan baseline "SQL_PLAN_cx95d29g10xv633f47f0e" used for this statement SQL> SELECT ... FROM DBA_SQL_PLAN_BASELINES WHERE SQL_TEXT LIKE 'SELECT * FROM tab01%'; SQL_HANDLE SQL_TEXT PLAN_NAME ORIGIN ENA ACC -------------------- --------------------------------- ------------------------------ ----------------------------- --- --- SQL_cea4ad125e107766 SELECT * FROM tab01 WHERE c2 = 1 SQL_PLAN_cx95d29g10xv613e5c288 AUTO-CAPTURE YES NO SQL_cea4ad125e107766 SELECT * FROM tab01 WHERE c2 = 1 SQL_PLAN_cx95d29g10xv633f47f0e MANUAL-LOAD-FROM-CURSOR-CACHE YES YES (3)計画の選択(Plan Selection) 次に、使用する実行計画をどのように選択するかについて説明します。 オプティマイザは、SQL文のハード解析で最適なコストとして実行計画を生成しますが、OPTIMIZER_USE_SQL_PLAN_BASELINES初期化パラメータをTRUE(デフォルト)にすると計画ベースラインが使用可能になり、計画ベースラインを使用した最適な実行計画の選択を行います(最もコストの低い承認済みの計画を選択します)。このとき、計画ベースラインの状態や内容によって、以下のように実行計画が決定されます。 計画ベースラインが存在しない(取得されていない) 通常のハード解析として、新たに生成した実行計画を最適なコストとして使用します。 新しい計画が計画ベースラインに含まれる(最適なコストとして生成した計画が存在する) その実行計画を使用します。 新しい計画が計画ベースラインに含まれない(一致するものがない) 新しい計画を計画履歴(未承認計画)に追加して、計画ベースラインの中から、一緒に格納されているアウトライン(ヒントの集まり)で再現でき、最もコストが低い実行計画(固定計画があれば優先)が使用されます。そのため、索引が削除された場合などでも問題ないようになっています。 そして、計画ベースラインのどの計画も再現できない(すべての計画が削除された索引を使用しているなど) 新たに生成された実行計画を使用します。 つまり、新しい計画が計画ベースラインに含まれないと、再現するためのハード解析が少し増えてしまうのが欠点ですが、SQL実行時間の低下よりは問題にはならないかと思います。 (4)計画の展開(Plan Evolution) 最後に、実行計画を展開する方法(未承認計画の検証と承認)について説明します。 展開処理は、計画履歴の未承認計画を計画ベースライン(最もコストが低い計画)と検証して、承認された(計画ベースラインよりパフォーマンスが優れている)ものだけを計画ベースラインに入れる処理になります(このパフォーマンス条件は、ハード解析のときのコスト比較ではなく、SQLチューニング・アドバイザなどと同じ条件を使用します)。これによりパフォーマンスの低下を防止しています。Oracle Database 12cからは、第35回で説明したSPM展開アドバイザの自動タスクと手動タスクで行うことができるので、より簡単になっています。 SPM展開アドバイザの自動タスクは、未承認計画の検証をメンテナンス・ウィンドウで自動的に行います(デフォルトで動作します)が、計画ベースラインや未承認計画が存在しないと何もしないので、無効化しなくても問題ありません。そのため、処理させるには、自動取得または手動取得で計画ベースラインを設定して、計画ベースラインにない計画を計画履歴に設定される必要があります。これにより、定期的に展開アドバイザを実行して、効果的な実行計画かを判断するような使い方が可能になります(第35回で説明したように、自動的に承認するか、レポートを見て判断するかも指定できます)。 ただし、SPMは、遅い実行計画を使用しないようにする機能なので、存在した実行計画にすることまでしかできません。そのため、より最適な実行計画にしたければ、オプティマイザ統計をもっと正確にするか、ヒントで補正して実行計画を手動取得することになります。 Oracle Database In-Memory(DBIM)のヒントとSPMについて 誤解されている方が多いようなので、ここでDBIMのヒントとSPMについて簡単に説明します。 DBIMの実行計画は、"TABLE ACCESS INMEMORY FULL"になりますが、ヒントで強制的にDBIMにアクセスさせることはできません(どちらを使用するかは、オプティマイザが判断します)。そのため、ヒントを使用するSPMでも、"TABLE ACCESS INMEMORY FULL"に固定化することができません。これを知らない方が意外と多いようですので、間違わないようにしてください。 オプティマイザの判断に使用するDBIMの統計は、ハード解析フェーズ中に次の統計情報を自動計算して収集されます(IMCU数とブロック数は、DBA_TAB_STATISTICSビューでも確認できます)。 #IMCUs ポピュレートされたIMCUの数 IMCRowCnt ポピュレートされた行の数 IMCJournalRowCnt 現在使用されていない(更新された)行の数 #IMCBlocks ポピュレートされたデータベース・ブロック数 IMCQuotient インメモリ列ストアにポピュレートされた割合(0~1)   また、INMEMORYヒントも勘違いしている方が多いようですが、INMEMORYヒントはINMEMORY_QUERY初期化パラメータがDISABLEのときに、"INMEMORY FULL"を行うときに使用します。 非DBIMアクセス(索引スキャン)を"INMEMORY FULL"にしたいときに指定しても効果はありません(このときは、FULLヒントを使用します)。   2. Oracle Database 19cの拡張機能 ここでは、Oracle19cからのSPMの拡張機能について説明します。 以下の二つの機能が、Oracle19cからSPMに拡張され、SPM展開アドバイザがより使いやすくなっています。ただし、どちらもExadataだけで使用できる機能になります。 自動SQL計画管理(AUTOMATIC SQL PLAN MANAGEMENT) 高頻度自動SPM展開アドバイザ・タスク(High-Frequency Automatic SPM Evolve Advisor Task) (1)自動SQL計画管理 これまでは、いくつかの制限(検証の対象は計画履歴の未承認計画だけ、問題のSQLを簡単に自動取得できないなど)により、必要なSQLを的確に改善することができませんでしたが、Oracle19cからは、SQL展開アドバイザが完全に自動化されました。以下のように、二つのパラメータ(ALTERNATE_PLAN_BASELINE、ALTERNATE_PLAN_SOURCE)をAUTOにすることで、パフォーマンスが低下したSQLを自動的に判断して、改善することができるようになります(このパラメータは、Oracle Database 12cR2からですが、AUTOの指定はありませんでした)。 BEGIN DBMS_SPM.SET_EVOLVE_TASK_PARAMETER (task_name => 'SYS_AUTO_SPM_EVOLVE_TASK', parameter => 'ALTERNATE_PLAN_BASELINE', value => 'AUTO'); DBMS_SPM.SET_EVOLVE_TASK_PARAMETER (task_name => 'SYS_AUTO_SPM_EVOLVE_TASK', parameter => 'ALTERNATE_PLAN_SOURCE', value => 'AUTO'); /* The Default */ END; / ALTERNATE_PLAN_BASELINE(対象のSQL計画ベースライン) SQL計画ベースラインがないSQLでも、パフォーマンスが低下した上位SQLを対象にします(デフォルトはEXISTINGで、既存の計画ベースラインを持つSQL文に対して代替計画をロードします)。 ALTERNATE_PLAN_SOURCE(代替計画を検索するソース) 計画履歴にない計画についても、すべての使用可能なソース(カーソル・キャッシュ、AWR、SQLチューニング・セット)から代替計画(未承認の計画)を検索して、計画履歴に追加します(デフォルトはAUTOです)。 この自動SPM展開アドバイザは、以下のような動作になります。Oracle19cで①~③が追加になり、初期計画の自動取得がFALSEでも(計画ベースラインや未承認計画が登録されていなくても)動作することができます。 ①.SQL文に計画ベースラインがないときに、AWRまたはASTS(Automatic SQL Tuning Set)からパフォーマンス低下の上位SQLをチェックする(AWRやASTSから上位SQLの実行計画を計画ベースラインに登録する) ②.代替計画を検索する(ALTERNATE_PLAN_SOURCEパラメータで指定されたリポジトリから検索する) ③.未承認の計画を計画履歴に追加する ④.未承認の計画を検証する ⑤.パフォーマンスが高くなる計画はSQL計画ベースラインに入れる(高くならない計画は未承認計画に残る) 関連するパラメータ値は、以下のように確認することができます。 SQL> SELECT PARAMETER_NAME, PARAMETER_VALUE AS "VALUE" FROM DBA_ADVISOR_PARAMETERS 2 WHERE ( (TASK_NAME = 'SYS_AUTO_SPM_EVOLVE_TASK') AND 3 (PARAMETER_NAME IN ('ACCEPT_PLANS','TIME_LIMIT') OR PARAMETER_NAME LIKE '%ALT%') ); PARAMETER_NAME VALUE ------------------------- ------------------------------------------ TIME_LIMIT 3600 ALTERNATE_PLAN_LIMIT UNLIMITED ALTERNATE_PLAN_SOURCE AUTO ALTERNATE_PLAN_BASELINE AUTO ACCEPT_PLANS TRUE (2)高頻度自動SPM展開アドバイザ・タスク Oracle19cから自動SPM展開アドバイザが、メンテナンス・ウィンドウ以外でも実行できるようになりました。 以下のように、Oracle19cからのAUTO_SPM_EVOLVE_TASKパラメータをONにすることで、メンテナンス・ウィンドウ以外でも自動的に実行できるようになります(デフォルトはOFFで、Oracle19cではAUTOはOFFと同じです)。 exec DBMS_SPM.CONFIGURE('AUTO_SPM_EVOLVE_TASK', 'ON'); 以下のSQLで、AUTO_SPM_EVOLVE_TASKの現在の設定を確認することができます。このパラメータを有効にすると、以下のように1時間ごとに30分以内で実行されるようになります。 SQL> SELECT PARAMETER_NAME, PARAMETER_VALUE FROM DBA_SQL_MANAGEMENT_CONFIG WHERE PARAMETER_NAME LIKE '%SPM%'; PARAMETER_NAME PARAMETER_VALUE -------------------------------- -------------------------------- AUTO_SPM_EVOLVE_TASK ON AUTO_SPM_EVOLVE_TASK_INTERVAL 3600 AUTO_SPM_EVOLVE_TASK_MAX_RUNTIME 1800 タスクの名前は、SPM展開アドバイザの標準自動タスクと高頻度タスクのどちらもSYS_AUTO_SPM_EVOLVE_TASKになるので、実行名によって識別します(標準タスクの実行名がEXEC_<number>という形式に対し、高頻度の実行名はSYS_SPM_<timestamp>という形式になります)。以下のように、DBA_ADVISOR_EXECUTIONSビューでタスク実行のステータスを確認することができます(この出力では、EXEC_6が標準自動タスクの実行、それ以外は高頻度タスクの実行です)。 SQL> SELECT TASK_NAME, EXECUTION_NAME, STATUS FROM DBA_ADVISOR_EXECUTIONS 2 WHERE TASK_NAME LIKE '%SPM%' AND (EXECUTION_NAME LIKE 'SYS_SPM%' OR EXECUTION_NAME LIKE 'EXEC_%') 3 ORDER BY EXECUTION_END; TASK_NAME EXECUTION_NAME STATUS ------------------------------ ------------------------------ --------- SYS_AUTO_SPM_EVOLVE_TASK SYS_SPM_2020-04-03/13:15:26 COMPLETED SYS_AUTO_SPM_EVOLVE_TASK SYS_SPM_2020-04-03/14:16:04 COMPLETED SYS_AUTO_SPM_EVOLVE_TASK EXEC_6 COMPLETED SYS_AUTO_SPM_EVOLVE_TASK SYS_SPM_2020-04-03/15:16:32 COMPLETED このように、いろいろ機能拡張されて使いやすくなっているので、実行計画を固定化運用している方、実行計画が問題になっている方などは使用を検討してみてください。   3. おわりに 今回は、SQL Plan Managementについて説明しましたが、少しは参考になりましたでしょうか。これからも頑張りますのでよろしくお願いします それでは、次回まで、ごきげんよう。 ページトップへ戻る▲    津島博士のパフォーマンス講座 Indexページ ▶▶

津島博士のパフォーマンス講座 Indexページ ▶▶   皆さんこんにちは、疲れたときの気分転換や息抜きなどに読んでみてください。 今回は、実行計画の変化で苦労しているような方に、正しく理解して効果的に管理して欲しいと思い、SQL Plan Management(SPM:SQL計画管理)を取り上げることにしました。後半に、Oracle...

Autonomous Database

Autonomous Data Warehouse を活用したデータの可視化 - 小売業を例に

※本記事は、Philip Li (Product Marketing Manager - Cloud Business Group)による"Six Retail Dashboards for Data Visualizations"を翻訳したものです。 Philip Li Product Marketing Manager - Cloud Business Group 小売業界は急速に変革を遂げています。消費者は、世界のどこからでも買い物ができるよう、オムニチャネルのサービスを期待し、小売企業は、そうした世界的需要に応えるため、従来の実店舗販売を補完する形でeコマース・プラットフォームを開発しています。このeコマースにより、顧客の購買行動データを収集し、重要な行動を特定していく方法にも大きな変化が生まれています。 Deloitteのコラム、Beyond Marketingでも、「小売企業の分析担当者は、自社がコントロールできる環境にて幅広く膨大なデータを獲得し、これらを活用することで、顧客についても、その個々の選好や行動についても、理解を深めつつある」と指摘されています。目先の利く小売企業ならきっと、ターゲット層の好みに沿った商品開発を行うために、こうした膨大なデータを活用するはずです。しかし問題は、それをどうやって実現するかです。 データウェアハウスで分析力を強化 小売企業は、データを活用して自社の疑問に対する答えを見つけようとしています。分析担当者はそうしたデータ・インサイトを見出そうとし、また管理層は、明確でわかりやすいダッシュボードからすぐにインサイトを獲得して、事業の実情を理解できるようになることを望んでいます。そうしたなか分析的なインサイトを提供するのが、Oracle Autonomous Databaseです。ここでは、専門的なスキルがなくても世界全体の事業状況を瞬時に視覚化することが可能です。そこで本ブログでは、データに基づいた事業の理解と運営の一助となるよう、グローバル小売企業向けのセールス・ダッシュボードを作成することにしました。 リテール・ダッシュボードの分析   リテール・ダッシュボードでは、売上、在庫回転率、返品率、顧客の獲得および維持のための費用といったKPIを追跡することが可能です。こうしたダッシュボードでは、わかりやすいグラフィックにてKPIをモニタリングでき、またそのグラフィックを経営陣に共有して事業の意思決定に役立たせることもできます。 本ブログでは、売上と利益を以下の基準にて分析できるリテール・ダッシュボードにフォーカスします。 商品 地域 顧客セグメント 各ダッシュボードでは、注目すべきエリアを特定し、それらを抜き出していきます。また、より多くのデータをインプットすることで、ダッシュボードを継続的にアップデートし、事業の道しるべとなるデータドリブンのインサイトを獲得することができます。    リテール・データセットの理解 小売企業が自社のセールス・ダッシュボードにデータを組み入れた場合をシミュレーションできるよう、ここではあるグローバル小売企業の売上および利益のデータを(少し手を加えて)使用しています。下のサンプル注文明細表(Excel)では、注文番号、顧客番号、顧客セグメント、商品カテゴリ、割り引き、利益、および注文追跡情報などのデータ要素を確認できます。 Autonomous Data Warehouseに無料で付与されるツールData Visualization Desktopでは、毎月の販売データを簡単にアップロードしてダッシュボードを継続的に更新することができます。そうしてより多くのデータをインプットすることで、事業の変化を理解し、その変化に適応しやすくなります。 ダッシュボードを継続的にアップデートする方法については、「Loading Data into Autonomous Data Warehouse Using Oracle Data Visualization Desktop」をご覧ください。 ここからは、以下について検討します。 商品別の売上および利益の現状 売上トップの都市 動きが活発な地域 売上に基づく、商品と地域との関係性 もっとも利益性の高い市場セグメント 利益を牽引する商品 以下は、Excel上にまとめられたデータです。 以下は、上のデータをData Visualization Desktopにアップロードしたものです。   商品別の売上および利益の現状 このセールス・ダッシュボード・サマリーでは、さまざまな商品セグメントの売上と利益の全体像を表示しています。ここからすぐに得られるインサイトは以下のとおりです。 タイル(画面左上)からは、トータルの売上が850万ドル、利益が130万ドル、つまり利益率が15.3%であることがわかる。 円グラフ(右上)からは、売上と利益の商品カテゴリ別の内訳がわかる。テクノロジー商品は、すべての商品カテゴリのなかで、売上でも(40.88%)、利益でも(56.15%)、トップであることから、もっとも重要な商品ラインであることがわかる。円グラフの下のピボット・テーブルには、実際の数値が示されている。 折れ線グラフ(左下)からは、どの商品カテゴリも成長していること、およびテクノロジー商品の成長がもっとも速いことがわかる。テクノロジー商品の売上の急成長は2018年8月に始まり、11月にピークに達している。 売上の商品別および顧客セグメント別の内訳(右下)が示されており、顧客セグメント別の購買傾向についてより深く理解できる。 下の図は、セグメント別の分析を進めるため、ビジネス全体の状況(上図)から大企業顧客セグメントだけを抜き出したものです。     売上トップの都市  この図では、各地域のオフィスごとのパフォーマンスと、全体の傾向を見ることができます。また積み上げ横棒グラフとドーナツ・グラフも表示されています。この散布図では、利益(横軸)と売上(縦軸)を指標とし、そこに各都市を表す円を置いています。また円の大きさは各都市の顧客規模を示しています。 たとえば黄色の円(一番右)はブラジルのサンパウロで、127の顧客にて、200,193ドルの売上と、44,169ドルの利益を計上しています。サンパウロは利益率が22%ともっとも高く、顧客当たりの購入総額は1,576ドルとなっています。この散布図で点線の右側に示されるのは、利益額が10,000ドル以上の都市です。 積み上げ横棒グラフ(左上)には、売上が大陸ごとに示されているため、売上の高い地域を確認することができます。ドーナツ・グラフ(右下)には、全地域の総売上(900万ドル)と地域別のパーセンテージが示されています。これにより、以下の地域で売上が高いことがわかります。 米州(38.64%) 欧州(28.81%) アジア(18.05%) さらに分析を進めるには、「keep selected」オプションを使用します。これにより欧州などの特定の地域にフォーカスすることができます(下図)。欧州の売上は250万ドル足らずで、そのなかでも欧州北側の都市の割合がもっとも高いことがわかります。このように散布図は、欧州の都市だけを表示するように変更できるため、欧州でもっとも利益額の高い都市がアイルランド島のベルファスト(27,729ドル)で、売上額がもっとも高い都市がロシアのサンクトペテルブルク(127,521ドル)であることもわかります。こうして成功している都市を特定することで、その成功例を他の地域に広げることも可能になります。  動きが活発な地域  分析担当者は、迅速に注力すべき市場を特定する必要もあります。この場合、ヒートマップを使用すると、売上の高い地域(色で表示)および売上のない地域(灰色)が一目でわかります。この例では、以下のように先進国市場での売上が特に高くなっています。  米国(150万ドル超) 英国(88.7万ドル) オーストラリア(69.5万ドル) さらに分析を進めて、英国の都市別の状況を確認することもできます(下図)。以下を含む複数の都市で売上があることがわかります。 ベルファスト リーズ マンチェスター シェフィールド ヒートマップを使用すると、店頭へのアクセスのしやすさだけでなく、需要に基づいて事業を拡大すべき都市も一目でわかります。 売上に基づく、商品と地域との関係性   売上や商品、地域といった異なる要素がどのように関連しあっているかを理解することは、往々にして難しいものです。しかしネットワーク・マップを活用すれば、商品カテゴリ(テクノロジー、家具、オフィス用品)と大陸(さらに各国にリンク)との関係を把握することができます。点と点を結ぶ線の太さは売上を示し、色の濃さは利益を示しています。アフリカとアフリカ南部を結ぶ線にマウスポインタを合わせると(上)、アフリカ南部の売上(24.2万ドル)と利益(3.4万ドル)がわかります。 特定の地域にフォーカスする方法は他にもあります。特定の点にマウスポインタを合わせて、「keep selected」オプションを使用する方法です(下図)。この例では、欧州にリンクされる点のみが示されています。これにより、欧州の売上および利益の大半は、テクノロジー商品(売上103万ドル、利益21.3万ドル)によってもたらされ、地域としては欧州北部(売上97.4万ドル、利益16.2万ドル)、特に英国(売上88万ドル、利益16.2万ドル)が主力であることがわかります。このように売上および利益に貢献している地域がわかるだけでなく、商品と地域との関係性をマクロな視点で捉えることができるのです。   もっとも利益性の高い市場セグメント   拡大のスピードがもっとも速く、売上および利益にもっとも貢献している顧客グループを把握することも重要です。この例では、積み上げ棒グラフ(左)と散布図(右)を使用して、2018年度の市場セグメント別の利益性を確認しています。顧客セグメントは以下のとおりです。 個人消費者 大企業 個人事業者 中小企業 積み上げ棒グラフでは、第2四半期から第4四半期にかけて売上が拡大し、主に大企業(第1四半期から61%成長)と中小企業(同じく53%成長)が全体の売上拡大を後押していることがわかります。大企業と中小企業を合わせると、第1四半期から売上が19.1万ドル拡大しています。これら2つのセグメントが2018年第4四半期の売上の63%を占めているわけですが、このグラフからは、個人事業者の売上が第3四半期から第4四半期にかけて2倍以上に拡大していることもわかります。 散布図(右)からは、各市場セグメントの利益率の推移がわかります。利益率は、同一四半期の純利益を純売上で除することで求められます。その結果、2018年度の成長スピードがもっとも速いセグメントおよび利益率のもっとも高いセグメントが以下のとおりであることもわかりました(4分割した右上)。 大企業 中小企業 大企業セグメントの利益性のみを抜き出すこともできます(下図)。ターゲット・セグメントに関するインサイトを獲得することで、的を絞った商品開発およびマーケティング活動が可能になります。 利益を牽引する商品   小売企業では、数千とはいかないまでも、数百もの商品を抱えていることも珍しくありません。このように商品数が多いと、個々の商品の利益性を特定および追跡することも難しくなります。しかし利益性の経時的変化を簡単にビジ