X

A blog about Oracle Technology Network Japan

Recent Posts

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にレコードが登場

※本記事は、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クラスの新しい形態で、以下の目的で設計されています。 データのみを組み合わせた構造をモデリングするためのフ...

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にまとめました。この対比は必ずしも厳密ではありませんが、新しい用語を少しばかり学びやすくしています。 表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による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 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プログ...

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

※本記事は、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つです。オブジェクトを手動で割り当てて破棄する他の言...

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による影響を確認できます。 public class EpsilonDemo { public static void main(String[] args) { final int GIGABYTE = 1024 * 1024 * 1024; final int ITERATIONS = 100; System.out.println("Starting allocations..."); // 一度に1 GBのメモリを割当て for (int i = 0; i < ITERATIONS; i++) { var array = new byte[GIGABYTE]; } System.out.println("Completed successfully"); } } このコードでは、100 GBのメモリを割り当てようとしています。行っているのはそれだけです。単に割当てだけを行い、終了しています。Epsilonを指定せずに上記のコードを実行した場合、最初と最後のprintln文に含まれる2つのリテラルが表示されます。つまり、デフォルトのGCが、1 GBのブロックを100個割り当ててから、システムで利用可能なメモリに収まるように、割当て済みブロックのガベージ・コレクションを必要に応じて行ったことを意味します。 しかし、前述のコマンドライン・スイッチを指定してEpsilon GCを使い、上記のコードを実行した場合、プログラムの出力は次のようになります。 Starting allocations... Terminating due to 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 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のパフォーマンス・チューニングは、高度な技術です。多くの場合、最適なガベージ・コレクタを選択してその...

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...

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

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分割した右上)。 大企業 中小企業 大企業セグメントの利益性のみを抜き出すこともできます(下図)。ターゲット・セグメントに関するインサイトを獲得することで、的を絞った商品開発およびマーケティング活動が可能になります。 利益を牽引する商品   小売企業では、数千とはいかないまでも、数百もの商品を抱えていることも珍しくありません。このように商品数が多いと、個々の商品の利益性を特定および追跡することも難しくなります。しかし利益性の経時的変化を簡単にビジュアル化し、それを特定の商品と比較することは、実は可能なのです。ここでは複合グラフ(左上)を使用して、売上と利益率の経時的変化を確認します。 このグラフからは、毎年総じて売上(および利益)が第1四半期から第4四半期にかけて増加し、翌年の第1四半期に減少していることがわかります。利益の経時的変化については、滝グラフで確認できます(左下)。2013年から2018年の末にかけて利益が16.7万ドル純増していることがわかります。 注力すべき利益性の高い商品と、削減すべき利益性の低い商品の特定も必要でしょう。画面右側では、商品ごとの売上と利益率を確認することができます。これらのグラフからは、以下の商品の売上がもっとも高いことがわかります。 電話/通信ツール(138万ドル) オフィス・マシン(107.7万ドル) 椅子(104.6万ドル) また以下の商品の利益率がもっとも高いことがわかります。 バインダ(35%) 封筒(32.4%) ラベル(31.6%) これは、バインダが1つ売れるごとに、売上の35%が利益となることを意味します。また、書棚(-5.2%)、タブレット(-5.3%)、はさみ/定規(-8.3%)などは利益率がマイナス、つまり販売するたびに損失が発生する商品であることがわかります。さらに下図のように、上位5商品の売上だけを抜き出すことも可能です。 まとめ Autonomous Data Warehouseを活用したデータ・ビジュアル化ダッシュボードでは、規模の大きなグローバル小売企業でも、事業の現状を簡単に把握し、流動的な市場環境にどのように適用していくかを決定していくことが可能になります。 Oracle Autonomous Databaseなら、専門的なスキルがなくてもクラウドにセキュアなデータ・マートを簡単に作成し、有益なビジネス・インサイトを創出することができます。データベースをプロビジョニングし、分析用にデータをアップロードするまでの時間も、わずか5分足らずです。 クラウドのトライアル・サービスにてAutonomous Data Warehouseを活用していただくことも可能になりました。 Autonomous Data Warehouseの無償トライアルへのご登録はこちらから 以下のブログには、クラウドの無償トライアルのスタートに関するステップ・バイ・ステップの説明が掲載されています。どうぞ、OCI Object Storeへのデータのアップロード方法、Object Store Authentication Tokenの作成方法、Database Credentialの作成方法、SQL DeveloperのData Import Wizardを使用したデータの読み込み方法などをご確認ください。   Data Warehouse 101:概要 Data Warehouse 101:プロビジョニング Data Warehouse 101:Object Storeのセットアップ フィードバックやご質問もお待ちしています。皆さんが作成されたダッシュボードについても、どうぞお知らせください。

※本記事は、Philip Li (Product Marketing Manager - Cloud Business Group)による"Six Retail Dashboards for Data Visualizations"を翻訳したものです。 Philip Li Product Marketing Manager - Cloud Business Group 小売業界は急速に変革を遂げています...

OCI

Oracle Cloud InfrastructureでのOSの管理

※本記事は、Julie Wong (Product Management Director)による"OS Management with Oracle Cloud Infrastructure"を翻訳したものです。 Julie Wong Product Management Director   多くの企業で行われている従来型のオペレーティング・システムの管理は、手作業で時間がかかり、複雑で、コストも高くなります。Oracle Cloud Infrastructureで提供されているOS Managementサービスは、そのような課題を解決します。Oracle Cloudで展開されているOracle Linuxのコンピュート・インスタンス向けに、パッチ管理やパッケージ管理など、オペレーティング・システム管理の一般的なタスクを自動化するツールや、セキュリティおよびコンプライアンスのレポート作成を自動化するツールが用意されています。OS Managementによって複雑さが大幅に解消され、エラーも格段に減少するため、Oracle CloudのOracle Linux運用環境でさらにコスト削減を進め、セキュリティと可用性を強化できます。このサービスは、Oracle Cloud Infrastructureのサブスクライバーであれば追加料金が不要です。   自動パッチ管理 オペレーティング・システム環境でセキュリティと信頼性を維持するには、パッチ管理が不可欠です。複雑さと緊急度の異なるパッチが毎日数多くリリースされる状況では、パッチ管理の必要性はさらに高まります。OS Managementサービスでは、統合され、強化されたツールを使用して、クラウド内のOracle Linuxインスタンス全体のパッチ適用を簡素化し、管理し、自動化できます。 OS ManagementはOracle Cloud Infrastructure Console内で統合されたサービスで、コマンドライン・インタフェースが用意されています。ダッシュボートから、Oracle Linuxインスタンス用に利用できるセキュリティ、バグ、機能強化のアップデートを確認できます。数度クリックするだけで、短時間で簡単に、そうしたアップデートをインスタンスに適用可能です。   アップデートを手動で適用したり、自動アップデートをスケジュールに組み入れたりするための、自由度の高いパッチ適用オプションがあります。自動アップデートを設定しておけば、最新の状態を保ち、セキュリティとパッチ適用のポリシーに準拠できます。人手による操作が不要なため、時間とコストの削減になります。   フリート管理による時間の節約 Oracle Cloud Infrastructureのコンピュート・インスタンスは個別に管理することも、グループ化してシンプルなフリート管理の対象にすることもできます。インスタンス・グループにすると、オペレーティング・システム別、目的別など、ニーズに合わせてインスタンスを管理できます。多数のインスタンスを管理している場合は、パッケージのインストールやアップデート、ソフトウェア・ソースの管理の際に、インスタンス・グループが非常に役立ちます。ソフトウェア・ソースとは、インスタンスに提供されるパッケージのコレクションのことです。インスタンス・グループを使うと、手動でログインしなくても、インスタンスにどのパッケージをインストールするのかを管理したり、各インスタンスでYumリポジトリを構成したりできます。   簡単なパッケージ管理 Linuxディストリビューションには、多数のソフトウェア・パッケージがあります。そのため、展開されているパッケージの確認、パッケージのインストールと削除、依存関係の確認が難しく、デプロイが大規模な場合はとりわけそう言えます。OS Managementサービスを使えば、どのOracle Linuxパッケージがインストールされているのかを簡単に確認できます。パッケージを検索、追加、削除したり、アップデートが公開された時点で、既存パッケージのアップデートをスケジュール設定したり、自動でアップデートしたりできます。   既知の脆弱性による影響 共通脆弱性識別子(CVE)は、一般に周知され、リスト化されているセキュリティの脆弱性と影響を識別するのに使われる、標準化された識別子です。OS ManagementサービスにはCVEの検索ツールがあり、脆弱性の影響を受けるパッケージを確認できます。影響を受ける各パッケージの詳細を見て、依存関係を確認し、パッケージをインストールできます。   使ってみよう Oracle Cloud Infrastructureの無料アカウントに登録してください。OS Managementサービスは現在、このドキュメントに記載されているOracle Cloud Infrastructureリージョンでは、デフォルトで使用可能です。  オラクルが提供しているOracle Linuxイメージのうち、2020年1月より前に導入されたものでは、既存のOracle Linux 6、7、8インスタンスでOS Managementサービス・エージェントをインストールする必要があります。Oracle Cloud Infrastructureにあるそれよりも新しいOracle Linuxイメージには、サービス・エージェントが含まれており、あらかじめインストールされています。構成および使用に関する情報については、OS Managementのドキュメントを参照してください。 パッチとパッケージの管理が、OS Managementによって自動で実行されるようにしましょう。時間とコストを節約し、運用環境のセキュリティと信頼性を今すぐに強化してください。   その他の情報 Oracle Linux Oracle Linux for Oracle Cloud Infrastructure:よくある質問 OS Managementのドキュメント

※本記事は、Julie Wong (Product Management Director)による"OS Management with Oracle Cloud Infrastructure"を翻訳したものです。 Julie Wong Product Management Director   多くの企業で行われている従来型のオペレーティング・システムの管理は、手作業で時間がかかり、複雑で、コストも高く...

Oracle Linux Yumサーバーのパッケー ジを使用したPHP 7とOracleデータベ ースとの接続

※本記事は、Sergio Leunissenによる"Connect PHP 7 to Oracle Database using packages from Oracle Linux Yum Server"を翻訳したものです。 Sergio Leunissen 注:この記事は更新されています。現在この記事には、PHPの最新リリースと、19cより導入されているOracle Instant Clientの簡易的なインストール・ガイドが含まれています。   当社は先般、Oracle Linux YumサーバーのリポジトリにPHP 7.4を追加しました。これらのリポジトリには、PHPアプリケーションをOracleデータベースと接続するためのPHP OCI8拡張も含まれています。 この記事では、PHP 7.4、PHP OCI8、およびOracle Linux上のOracle Instant Clientをインストールして、PHPとOracleデータベースを接続するための方法について説明いたします。なおこの記事では、Oracle Cloud Free Tierに含まれる無料のAutonomous Databaseを使用しています。 Oracle Instant Clientのインストール Oracle Instant Client RPMはOracle Linux Yumサーバーでも利用可能です。これを利用するには、以下のとおり、まずoracle-release-el7パッケージをインストールして、適切なリポジトリをセットアップする必要があります。 $ sudo yum -y install oracle-release-el7 $ sudo yum -y install oracle-instantclient19.5-basic SQL*Plus(健全性チェックに便利な場合があります)を使用できるようにしたい場合は、以下のとおりSQL*Plus RPMもインストールします。 $ sudo yum -y install oracle-instantclient19.5-sqlplus   スキーマの作成とHRサンプル・オブジェクトのインストール(任意) すでにデータベースに存在している任意のスキーマを使用することもできます。ここでは、github.comにあるOracle Databaseのサンプル・スキーマからHRスキーマを使用します。すでにスキーマとそれに対応するデータベース・オブジェクトがある場合は、このステップを省略してください。 $ yum -y install git $ git clone https://github.com/oracle/db-sample-schemas.git $ cd db-sample-schemas/human_resources SYSTEM(Autonomous Databaseを使用している場合はADMIN)として、ユーザー「PHPTEST」を作成します。 SQL> grant connect, resource, create view to phptest identified by <YOUR DATABASE PASSWORD>; SQL> alter user PHPTEST quota 5m on USERS; このサンプルのようにAutonomous Databaseを使用している場合は、上の表領域をDATAに変更します。 SQL> alter user phptest quota 5m on DATA; ユーザー「PHPTEST」としてスクリプト「hr_cre.sql」と「hr_popul.sql」を実行し、HRデータベース・オブジェクトの作成と移入を行います。 SQL> connect phptest/<YOUR DATABASE PASSWORD>@<YOUR CONNECT STRING> SQL> @hr_cre.sql SQL> @hr_popul.sql PHPおよびPHP OCI8のインストール PHP 7.4をインストールするには、まず最新のoracle-php-release-el7パッケージがインストールされていることを確認する必要があります。 $ sudo yum install -y oracle-php-release-el7 次に、インストール済みのOracle Instant Clientに対応したPHPおよびPHP OCI8拡張をインストールします。 $ sudo yum -y install php php-oci8-19c 以下のPHPコード・スニペットを実行すると、PHPをデータベースに接続できること、およびデータを戻せることを確認できます。必要であれば、スキーマと接続文字列を置き換えるようにします。 <!--?php error_reporting(E_ALL); ini_set('display_errors', '1'); $conn = oci_connect('phptest', '<YOUR DATABASE PASSWORD>', '<YOUR CONNECT STRING>'); $stid = oci_parse($conn, 'SELECT last_name FROM employees'); oci_execute($stid); echo "\n"; while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) { foreach ($row as $item) { echo $item ."\n"; } } ?--> 上のコードに基づいてファイル「emp.php」を作成します。 実行 $ php emp.php これにより以下が生成されるはずです。 King Kochhar De Haan Hunold Ernst Austin Pataballa Lorentz Greenberg Faviet Chen Sciarra ...

※本記事は、Sergio Leunissenによる"Connect PHP 7 to Oracle Database using packages from Oracle Linux Yum Server"を翻訳したものです。 Sergio Leunissen 注:この記事は更新されています。現在この記事には、PHPの最新リリースと、19cより導入されているOracle...

OCI

オラクルのMaximum Security Architectureで実現するデータベースのセキュリティ

※本記事は、Sean CahillによるOracle’s Maximum Security Architecture for Database Securityを翻訳したものです セキュリティ制御されるべき領域 御社の機密データを守り、世界中で急増している多数の新しいプライバシー規制に準拠するには、Oracle Databaseの保護が非常に大切です。御社のデータ――たとえば知的財産、財務データ、顧客やスタッフの個人情報、さらにそれらを組み合わせたデータ(この形を取ることが多い)――は、きわめて貴重です。データには価値があるので、盗難や誤用から守る必要があります。 データを本当に守ろうと思ったら、鍵をかけて埋めてしまえばいいのかもしれませんが、そうしたデータは業務での利用を目的としていますから、ユーザーやアプリケーションはそのデータを使用したり、データベースに接続したりしなければなりません。セキュリティ制御によってデータを保護し、御社のポリシーに従ってデータへのアクセスを制限することが必要です。 そのためには、次の3つの手順が求められます。 1.システムを評価して、現在の状態を見極め、修正プランを策定します。 システムは適切に構成されているか。  パッチは定期的に適用されているか。  ユーザー権限はどのように管理されているか(付与されている権限は必要最低限か)。 システムにはどのような種類の機密データがどれほど保存されているか。  Oracle Databaseのお客様であれば、データベースを評価し、改善とリスク軽減の対象となる箇所を特定するのに必要な機能やユーティリティは、ご購入済みのOracle Databaseにあらかじめ用意されています。 2.ポリシーに従っていないデータ・アクセスを検知し、データ・アクセスでの異常を特定します。データベースでは、ほぼすべての動作が繰り返されるため、異常の発見は多くの場合、データ盗難の疑いがあることを最前線で示すものとなります。 3.データベース制御メカニズムを経由していないデータ・アクセス、つまりネットワークを介したトラフィックの傍受、下方にあるデータ・ストレージ層の読取り、データベース・エクスポートやバックアップの誤用を防止します。制御メカニズムを経由したアクセスについては、その状況を見極めて、データにアクセスしたアカウントを特定するだけでなく、不適切なデータ・アクセスをブロックします。 オラクルでは、こうしたセキュリティ制御のそれぞれの目的に合致する、業界屈指の機能を用意しています。オラクルのチームは、制御の目的が実質的に何であれ、御社がふさわしい技術的対策を見つけられるように支援します。 データベースはどのようにして攻撃されるのか データベースは機密情報の宝庫であり、データを盗みだそうとする人はほぼ必ず、データベース内のデータを悪事の標的にします。この種の攻撃から身を守る方法について考える前に、攻撃がどのように発生するのかを見てみましょう。 データベースは単独で機能しているわけではありません。常に使用されている状態であるため、リスクにさらされることも概して多くなります。データベースには、ユーザーとアプリケーションがアクセスします。多くの場合、テスト用や開発用、さまざまな目的でのステージング用のデータベース・コピーがあります。データベースはストレージ・メディアにデータを保持しつつ、オペレーティング・システムや周辺機器を備えたサーバーで実行されます。管理者がすべてを管理し、大きな権限を与えられているため、ハッカーにとって管理者は狙いどころです。管理者アカウントへの不正アクセスに成功したハッカーは、昇格された権限を手に入れることになり、ほとんどの場合、制御不能でやりたい放題になります。 管理者アカウントを手に入れることはできなくても、エンドユーザー・アカウントを乗っ取ることができる場合が多々あります。与えられている権限は少ないですが、いくらかのデータにはアクセスできたり、SQLインジェクション攻撃によって、目的の管理者アクセスを手に入れるための足がかりとして利用できたりします。 アプリケーションも魅力あるターゲットです。データベースやデータベース・サーバーと比べて公開されている部分が多く、企業のファイアウォールの外から使用できることもあります。 ファイアウォールといえば、社内ネットワークへの侵入に成功した攻撃者が、ネットワークを行き来するデータを攻撃する場合があります。この種の攻撃は、データベースへの直接のアクセスと比べて、はるかに検知しにくくなります。 もう1つのよくある攻撃は、表には出てこないデータファイル、データベース・バックアップ、データベース・エクスポートへの攻撃です。この場合も、攻撃に成功したサイバー犯罪者は、データベースにログインしようとしなくても、データベース全体のデータを盗むことができる可能性があるのです。 上記のようなことが成功しなくても、データベースにパッチ未適用の脆弱性があるかもしれません。多くの場合、そのような脆弱性を悪用するための、自動化された攻撃ツールキットが作成されているものです。 そして、本番用以外のデータベース・コピーについても忘れるわけにはいきません。多くのシステムでは、テスト用および開発用のインスタンスは本番用のクローンですが、セキュリティ対策はほぼなされていません。 では、このような攻撃すべてに立ち向かうにはどうすればよいのか 効果的なデータベース・セキュリティ戦略があれば、複数の異なるセキュリティ制御を採り入れ、そのすべてを連携させることができます。オラクルのセキュリティ制御を、先述したカテゴリ、つまり評価、検知、防止、そしてデータ主導型セキュリティに分けて説明しましょう。データ主導型セキュリティとは、リレーショナル・データベース内のアプリケーション・データへのアクセスをきめ細かく制御することに主眼を置いた、セキュリティ制御の特別なカテゴリです。 まずは、Oracle Database Security Assessment Tool(DBSAT)を使用して、データベースの構成を評価し、ベスト・プラクティスからどれほど乖離しているかを調べます。データベースの構成に問題があると、他の制御も効果が薄れる可能性があります。 次に、ユーザーとアプリケーションについて思い出してください。皆がデータベースにログインすることを考えると、Centrally Managed Users(CMU)を使用するのがよいかもしれません。CMUはデータベース機能の1つで、データベースをMicrosoft Active Directoryに接続します。また、Enterprise User Security(EUS)の使用を検討してもよいかもしれません。EUSはデータベースとオラクル独自のディレクトリ・サービスとをつなげるデータベース機能です。いずれの場合も、データベースのユーザーとロールは、データベースから離れてLDAPディレクトリの下に置かれます。 パスワードよりも強力なものを使ってユーザーを認証したいという場合もあるかもしれません。Oracle DatabaseはRadius、Kerberos、PKI証明書による認証をサポートしています。DBSATに話を戻しましょう。ユーザーは誰なのか、どのような権限を持っているかについて知る必要がある場合、DBSATからその情報を得ることができます。 ユーザーが持っている権限に加えて、実際にどの権限を使用しているのかを知りたい場合は、権限分析(PA)が役立ちます。権限分析を使用すると、使用されていない権限とロールを特定して、ユーザー・アカウントから削除できるため、アカウントへの不正アクセスがあったときに攻撃対象となり得る領域を狭めることができます。 データベース・クライアントがデータベースにデータを送信し始め、データを取得してレポートを作成したり分析したりするようになると、送受信時のデータの保護についても心配しなければなりません。ここではネットワークの暗号化が有効です。 また、どのような種類のデータがデータベースに格納されているのか知りたい場合は、DBSATを使用して、機密データを探すことができます。 そのデータは最終的にはディスク、バックアップ、エクスポートに保存されるため、そこでデータを攻撃から保護する必要があります。ここは、Oracle Advanced Securityの一部である透過的データ暗号化(TDE)の出番です。TDEはオペレーティング・システム上、ストレージ・デバイス内、バックアップ・ファイルとエクスポート・ファイル内のデータを暗号化します。データを暗号化するとき、つまり1つまたは複数の暗号化鍵があり、鍵を保護し、安全に送信する必要があるときには、Oracle Key Vault(OKV)を使用します。OKVはエンタープライズ・アーキテクチャ内のOracle Databaseを認識して、TDE鍵をローテーションしやすくし、古いバックアップやファイルをリストアする必要があるときは、以前に使用された鍵を保護します。 管理者にはデータにアクセスする権限が与えられていることを思い出してください。そのような管理者も保護する必要があります。それには、Oracle Database Vault(DV)を使用します。Database Vaultは、データベース管理の役割を、データベース内のデータへのアクセスから切り離します。Database Vaultには不正にアクセスされたアプリケーション・サーバーからの保護機能もあり、アプリケーションのアカウントをロックして、アプリケーションの正常な状態のデータのみにアクセスできるようにします。 アプリケーション外からのデータ・アクセスがある場合、クレジット・カード番号や納税者番号などの機密度の高いデータ列には、保護を追加したいと思うかもしれません。この場合は、Oracle Advanced Securityの一部であるData Redactionを使用して、データベースを離れて送信される間であってもデータを隠すことができます。 さらに、データベースの本番用以外のクローンについては、Oracle Data Masking and Subsetting Packの一部であるData Maskingを使用して、単純に機密データを削除し、セキュリティ上のリスクにならない本物そっくりの“安全な”データに差し替え、アプリケーションの開発とテストはそのまま続けることができます。 データベースを保護するためにさまざまな対策を講じてきましたが、侵入やデータの盗難の疑いを確実に検知することも重要です。そのためには、データベース内に監査を構成し、一元管理された監査ボルトに監査イベントを送信して、分析、レポート作成、アラートの生成を行う必要があります。 ここでは、Oracle Audit Vault and Database Firewallの一部であるDatabase Firewallを使用して、異常やポリシー違反がないか、着信接続要求とSQL文を検証します。必要な場合はさらに一歩進んで、ポリシー違反の動作をファイアウォールによって実際にブロックします。当然ですが、Database FirewallのイベントはAudit Vaultサーバーに送信され、分析、レポート作成、アラート生成に使用されます。 最後になりますが、アプリケーション・ユーザー向けにデータを行や列レベルできめ細かく制御することができます。ここで利用できるオラクルの機能には、Real Application Security(RAS)やOracle Label Securityなど、さまざまなものがあります。このセル・レベルのアクセス制御によって、複数のアプリケーションを開発して、アプリケーション・ユーザーによるデータ・アクセスの認可モデルを統一し、アプリケーション開発に要する時間と労力を減らすことができます。 連携して動作するこれらすべての制御が、Maximum Security Architectureです。これまで紹介したもののほとんどはデータベースの機能であり、Enterprise Editionのお客様であればどなたでも使用できます。追加費用がかかるオプションや製品も一部あります。   クラウドでのMaximum Security Architecture クラウドのすばらしい点の1つは、用意されているそのオプションです。御社のデータベースがOracle Cloudで稼働している場合、Oracle Data Safeも利用できます。Data Safeには、DBSAT、Audit Vault、Data Maskingと同等の、Oracle Databaseを保護するための複数の機能があります。 Database Vaultや透過的データ暗号化のようなオプションもクラウドで提供されており、ほとんどのクラウド・サービス(Oracle Autonomous Databaseなど)は、追加料金不要でサブスクリプションに含まれています。多くの場合、自動で有効になり、構成されます。 クラウドでMaximum Security Architectureを実装するほうが簡単だということです。 忘れるべきでないのは、大半のサイバー攻撃の目的は、御社のもっとも価値ある資産、つまりデータを入手することです。その資産を保護するために、Maximum Security Architectureをガイドとして使用してください。 Maximum Security Architectureについての詳しい情報は、Oracle Database SecurityのWebページをご覧になるか、オラクルの電子書籍をダウンロードしてください。

※本記事は、Sean CahillによるOracle’s Maximum Security Architecture for Database Securityを翻訳したものです セキュリティ制御されるべき領域 御社の機密データを守り、世界中で急増している多数の新しいプライバシー規制に準拠するには、Oracle Databaseの保護が非常に大切です。御社のデータ――たとえば知的財産、財務データ、顧客や...

Autonomous Database

Autonomous Databaseの最新情報をお届けします

※本記事は、Keith Laker (Senior Principal Product Mamager)による"Autonomous Database Newsletter - February 18-2020"と"Autonomous Database Newsletter - February 26-2020"を翻訳したものです。Oracle Autonomous Database Cloudについての最新情報はこちら(Speaker Deckサイト)に日本語スライド資料も公開していますのであわせてご覧ください。   共有ExadataインフラストラクチャでのAutonomous Database ようこそ。共有ExadataインフラストラクチャでのAutonomous Databaseに関する最初のカスタマー・ニュースレターをお届けします。このニュースレターでは、以下の新しい機能およびテーマを取り上げます。 Oracle Database Vaultの提供開始 「Always Free」のADB(Autonomous Database)で新たに利用可能となったAPEX 19.2 複数DBのサポート ADB対応の新しいGraph Server より容易になった移行 新たなデータセンター プライベート・エンドポイントに対応 クイック・リンク oracle.comの自律型データベースに関する最新情報ページもどうぞご確認ください。   トップへ戻る   (共有インフラストラクチャのみ) NEW - Autonomous DatabaseでのOracle Database Vaultの使用 Oracle Database Vaultを使用すると、Autonomous Databaseのセキュリティを強化することができます。具体的には、データベースの特権ユーザーによるアプリケーション・データへのアクセスの制限、内部関係者および部外者による脅威の軽減、一般的なコンプライアンス要件への対応といった、他にはないセキュリティ制御機能を獲得することができます。 詳細情報 ドキュメント:Autonomous Data Warehouse(ADW)でのOracle Database Vaultの使用:こちらをクリック ドキュメント:Autonomous Transaction Processing(ATP)でのOracle Database Vaultの使用:こちらをクリック スライド:OOW2018 - オートノマス、そしてその先へ:自律型データベース時代のセキュリティ:こちらをクリック ドキュメント:Database Vaultに関するDatabase 19cドキュメント:こちらをクリック その他の情報 ビデオ:Database Vaultの概要 - こちらをクリック PDF:Database Vault FAQ - こちらをクリック PDF:Database Vaultのデータシート - こちらをクリック PDF:Oracle Database Vaultの概要 - こちらをクリック PDF:Oracle Database Vaultのベスト・プラクティス - こちらをクリック PDF:Oracle Database Vault:DBAのベスト・プラクティス - こちらをクリック こちらもご覧ください ビデオ:Database Vault - 職務分掌の適用 - こちらをクリック ビデオ:Database Vault - 信頼パス・アクセス制御の導入 - こちらをクリック ビデオ:Database Vaultの詳細なユースケース2 - Operations Control、Database Vaultシミュレーション・モード - こちらをクリック   トップへ戻る   (共有インフラストラクチャのみ) NEW - Always Free Autonomous DatabaseでAPEX 19.2が利用可能に Always Free Autonomous DatabaseにてOracle APEX 19.2を利用することで、ワールドクラスのデータ中心型アプリケーションの構築とデプロイの両方において、事前構成済みで、フル・マネージドの、安全な環境を獲得することができます。オンプレミスで開発されたOracle APEXアプリケーションを、無料版のAutonomous DatabaseにあるOracle APEXにデプロイすることも簡単です。   詳細情報 Application Expressとは:こちらをクリック ドキュメント:APEX 19cのGetting Started Guide:こちらをクリック ドキュメント:ADWでの、Oracle Application Expressを使用したアプリケーション構築:こちらをクリック ドキュメント:ATPでの、Oracle Application Expressを使用したアプリケーション構築:こちらをクリック こちらもご覧ください こちらで、架空の会社AnyCo Corpの簡単な人事アプリケーションを作成し、データベース・テーブルに保存された部門情報や従業員情報を管理してみましょう。 ビデオ:スプレッドシートを使用してわずか2分でAPEXアプリケーションを作成する方法をこちらでご紹介しています。 ハンズオン・ラボ:APEXハンズオン・ラボの一覧は、こちらをご覧ください。   トップへ戻る   (共有インフラストラクチャのみ) NEW - リージョンによっては、データベースの複数バージョンの利用が可能に データベースのプロビジョニングまたはクローニングを実施しているリージョンによっては、Autonomous DatabaseがOracle Databaseの複数のバージョンに対応できるようになりました。この対応はリージョンごとに異なります。また複数バージョンに対応していないリージョンもあります。 複数バージョンが利用可能な場合、データベースのプロビジョニングやクローニングの際には、Oracle Databaseのバージョンを選択します。 詳細情報 ドキュメント:ADWに対応するOracle Databaseのバージョンと可用性(リージョン別):こちらをクリック ドキュメント:ATPに対応するOracle Databaseのバージョンと可用性(リージョン別):こちらをクリック   Oracle Database 19cにてADBを使用している場合は、以下のとおり、そのデータベースで利用できる機能が他にもあります。 ドキュメント:Oracle Database 19cでのADWの機能:こちらをクリック ドキュメント:Oracle Database 19cでのATPの機能:こちらをクリック。   トップへ戻る   (共有インフラストラクチャのみ) NEW - Oracle Graph Server and Client 20.1が利用可能に Oracle Graph Server and Client 20.1は、Autonomous Databaseにて使用できるソフトウェア・パッケージです。このパッケージには、Autonomous Databaseのプロパティ・グラフ機能での作業に必要な、インメモリ・アナリスト(PGX)およびクライアント・ライブラリが含まれています。 グラフ分析を用いることで、ソーシャル・ネットワーク、IoT、ビッグ・データ、データウェアハウス、また銀行での不正の検出、全方位的な顧客情報、スマート製造などを扱うアプリケーションの複雑なトランザクション・データにおいて、関連性やパターンを見つけることができます。  詳細情報 edelivery.oracle.comからダウンロードが可能です。こちらをクリックしてください。 ドキュメント:インストール方法については、こちらをクリックし、「1.7 Using Oracle Graph with the Autonomous Database」のセクションをご覧ください。 Web:oracle.comのGraph Serverページ:こちらをクリック その他の情報 スライド:エキスパートでない方向けの優しいグラフ分析:こちらをクリック スライド:グラフ分析で新しいインサイトを獲得するには:こちらをクリック   トップへ戻る   (共有インフラストラクチャのみ) NEW - ADBへの移行を容易にする機能 1.データベース常駐接続プーリング(DRCP) DRCPを使用してADBの接続プールにアクセスすることで、多くのクライアント接続に必要な主要データベース・リソースの数を大幅に削減することができます。Autonomous Databaseでのデータベース常駐接続プーリングの使用に関する詳細は、以下を参照してください。 ドキュメント:ADW こちらをクリック ドキュメント:ATP こちらをクリック   2.MAX_STRING_SIZEの値の設定 ADWのデータベースでは、デフォルトでMAX_STRING_SIZEがEXTENDEDに設定されています。古いOracle Databaseまたはアプリケーションからの移行では、MAX_STRING_SIZEをSTANDARDに設定します。MAX_STRING_SIZEの設定に関する詳細は、以下を参照してください。 ドキュメント:ADW こちらをクリック ドキュメント:ATP こちらをクリック   3.同時文の数 同時文の最大数が増えました。詳しくは、Autonomous Data Warehouseの事前定義済みのデータベース・サービス名に関する以下の記事を参照してください。 ドキュメント:ADW こちらをクリック ドキュメント:ATP こちらをクリック   トップへ戻る   (共有インフラストラクチャのみ) NEW - より多くのデータセンターがオンライン上に 以下のデータセンターでも、Autonomous Databaseを利用できるようになりました。 サウジアラビア・ジェッダ 日本・大阪 オーストラリア・メルボルン オランダ・アムステルダム 各データセンターのサービスの状況については、こちらをクリックしてご確認ください。これらの新しいリージョンへの登録方法については、こちらを参照してください。リージョンを切り替えるには、コンソールにある「Region」メニューを使用します。詳しくは、こちらを参照してください。   トップへ戻る   (共有インフラストラクチャのみ) NEW - Autonomous Databaseがプライベート・エンドポイントに対応 仮想クラウド・ネットワーク(VCN)にあるプライベート・エンドポイントを指定して、Autonomous Databaseへのアクセスを制限することが可能になりました。プライベート・アクセスの構成は、Autonomous Databaseのプロビジョニングまたはクローニングの際に実施され、これによりAutonomous Databaseとのすべてのトラフィックをパブリック・インターネットから切り離すことができます。 プロビジョニングまたはクローニングの際に、Autonomous Databaseへのプライベート・アクセスを指定することも可能です。これには、以下のとおり、作成/クローニングのダイアログの「Choose network access」セクションにて、「Virtual cloud network」を選択します。 画像adb_private_vcn.pngの説明   プライベート・エンドポイントを有効にするには、以下の3つのステップを実行します。これらのステップは、Autonomous Databaseのプロビジョニングまたはクローニングの前に実施する必要があります。 Autonomous Databaseを置くリージョンにて、VCNを作成します。 VCN内にサブネットを設定します(デフォルトのDHCPオプションにて設定)。 VCN内にネットワーク・セキュリティ・グループ(NSG)を少なくとも1つ指定します(Autonomous Databaseの送信および受信ルールを指定するために使用)。 注:プライベート・エンドポイントは現在、オラクルのすべてのデータセンターに展開されつつあります。2月26日現在の利用可能なデータセンターは、以下のとおりです。 アムステルダム アッシュバーン フランクフルト ジェッダ ロンドン メルボルン 大阪 フェニックス ソウル 東京 トロント これ以外のデータセンターでも、まもなく利用可能になります。   詳細情報   ドキュメント:ADWにてプライベート・エンドポイントを設定:こちらをクリック ドキュメント:ATPにてプライベート・エンドポイントを設定:こちらをクリック ブログ:共有ExadataインフラストラクチャでのAutonomous Databaseでプライベート・エンドポイントを使用可能になりました:こちらをクリック       こちらもご覧ください ドキュメントには、ネットワーク・シナリオのサンプルが2つ用意されています。 サンプル1:Oracle Cloud Infrastructure VCNから接続 サンプル2:ご利用のデータセンターからAutonomous Databaseに接続   ドキュメント:ADWでのプライベート・エンドポイントの構成例:こちらをクリック ドキュメント:ATPでのプライベート・エンドポイントの構成例:こちらをクリック     トップへ戻る   oracle.com ADW ATP ドキュメント ADW ATP TCO計算ツール サーバーレス 専用サーバー クラウド・コスト試算ツール Autonomous Database ADBの新機能 Autonomous Database スキーマ・アドバイザー Autonomous Database       カスタマー・フォーラム ADW ATP      

※本記事は、Keith Laker (Senior Principal Product Mamager)による"Autonomous Database Newsletter - February 18-2020"と"Autonomous Database Newsletter - February 26-2020"を翻訳したものです。Oracle Autonomous Database...

Cloud Security

コンパートメントのベスト・プラクティス

※本記事はMartin Sleemanによる”Best Practices for Compartments”を翻訳したものです。 Oracle Cloud Infrastructureのコア・プラットフォームを担当する製品マネージャーである私は、コンパートメントやタグ付けなどの機能を使用した、効率的なコスト管理やオペレーションの簡略化、継続的なガバナンスのベスト・プラクティスについて、質問を受けることがよくあります。 そこで今回のブログでは、コンパートメントを使用した、リソースの整理、アクセス制御の設定、管理およびガバナンスのターゲティングに関するベスト・プラクティスについて、お話ししようと思います。 適切に設計されたコンパートメント戦略の効果 コンパートメント階層を使用してテナンシを適切に整理すると、以下のとおり多くのメリットを獲得することができます。 アクセス制御ポリシーを簡単に、かつ正しく記述できる。 管理者権限をコンパートメント管理者に委任できるため、コンパートメント管理者はコンパートメント・ツリー内の担当エリアのみを管理できる。 ある部門にコンパートメント・ツリーが付与されていれば、その部門に課金を割り当てることができる。コストセンターをコンパートメントごとに分けることで、どこで費用が発生しているかを一目で確認できるようになる。 コンパートメントの基本 コンパートメントを使用することでリソースが整理されるため、コストの管理責任や管理者権限を委任することも可能になります。コンパートメントは論理的なコンテナであり、リソースが配置され、どのデータセンターにも紐づけられず、すべてのデータセンターに対応させることができます。すべてのリソースは1つのコンパートメントに配置されます。 テナンシ管理者は、リソースのレイアウトを設計して、アクセス制御ポリシーにて適切なグループに適切なアクセス権が付与されるようにすることができます。またコンパートメント設計を行うことで、予算やリソースの定数を割り当てたり、リソースにタグを適用したりすることも可能になるため、リソース管理も容易になります。 コンパートメントは、Oracle Cloud Infrastructureのなかで唯一の体系的な階層リソースです。また、ポリシーをアタッチできる唯一のリソースでもあります。したがって、コンパートメントのレイアウトを考える際には、自社のアクセス管理目標を踏まえて、それらを達成できるようにレイアウトを策定されることをお勧めします。 効率的なコンパートメント階層を作成するためのベスト・プラクティス すべてのリソースに体系的な階層を適用するには、コンパートメントをネストする必要があります。 またコンパートメントの階層を設計する際は、自社のアクセス制御の境界が反映されるように設計します。特定のグループが特定のリソース・セットにのみアクセス権を必要としているのなら、それらのリソースをそのグループのコンパートメントに配置します。 コンパートメントをネストすると、コンパートメント管理者への管理者権限の委任も簡単になります。ある部門用にコンパートメントを作成すれば、その部門のメンバーは自部門の構成に合わせてサブコンパートメントを作成できるようになります。その部門に管理者タスクを委任すれば、プロジェクトの詳細に関与することなく、部門全体を1単位として、利用を統制することができます。そしてその委任作業を統括するのが、アクセス制御ポリシーになります。 コンパートメントによってユーザーのアクセス制御およびガバナンスの境界が作られますが、リソースにはこの境界を越えてアクセスすることができます。たとえば、すべての仮想クラウド・ネットワーク(VCN)を1つのコンパートメントに置いても、それらのVCNには、すべてのコンパートメントからアクセスすることも、接続することもできます。ただし、それらのVCNへの管理者アクセス権をネットワーク管理者のみに制限することで、より容易に管理を行うことができます。 以下の図は、架空の会社BlueCat Corpのテナンシを例にした、コンパートメント階層の一般的な設計パターンです。またここでは、コンパートメントの組織的原則も少し理解することができます。 BlueCatでは、SmartAlecコンパートメントにて、リソースをプロジェクト別に整理しています。プロジェクト内では、リソースをさらにリソース・コンパートメント(ADW、DBBackup)と環境(Dev、Test、Prod)に分類しています。 BlueCatでは、価値の高いネットワーキング・リソースを専用のコンパートメントに集め、アクセス・ポリシーを簡単かつ正確に記述できるようにしています。それでもリソースはコンパートメントの境界を越えて接続することができます(緑の点線で示されるように、各コンパートメントがネットワーキング・コンパートメントに接続されています)。 BlueCatでは、カスタマー・レンダリング用のリソースを専用のコンパートメント(Customer Rendering)に隔離しています。 こうした整理スキームは、ガバナンスの面でも、アクセス制御の面でも、効果的です。コンパートメントの整理スキームを策定する際は、アクセス制御とガバナンスのニーズが満たされるように策定することをお勧めします。 コンパートメントは必要な数だけ作成しましょう。コンパートメントは無料です。1つのテナンシに何百ものコンパートメントを置くことができます。設計における唯一の大きな制約は、コンパートメントのネストが6レベルまでに制限されていることです。しかし私が知る限り、この制約が問題になったお客様はいません。 ポリシーとコンパートメント コンパートメント階層の設計とレイアウトにおいて考慮しなければならないのは、テナンシにとって適切なアクセス制御を設定するポリシーを簡単に記述できるようにすることです。 ポリシーは以下のように記述します。 Allow group <group_name> to <verb> <resource> in compartment <compartment_name> このように、コンパートメント内のすべてのリソースまたは特定のリソースタイプからなるグループを対象に、特定のアクセス・レベルを付与するポリシーを、簡単に記述できます。 委任管理 コンパートメント階層を設定することで、コンパートメント・ツリー全体に対する管理タスクは委任しつつ、委任された管理者がテナンシ全体を変更することはできません。 委任するチームごとにコンパートメントを作成しましょう。そのうえで、そのコンパートメントの管理を任せたい管理者グループ(例:SmartAlec_Admins)を作成します。次に、以下のポリシーを記述して、それをルート・コンパートメントに置きます。 Allow group SmartAlec_Admins to manage all-resources in compartment Project_SmartAlec これで、SmartAlec_AdminsグループによるProject_SmartAlecコンパートメントのフル・コントロールが可能になりました。またこのSmartAlec_Adminsグループにて既存のグループ向けにポリシーを作成し、そのグループによるコンパートメント・ツリー内のリソースへのアクセスを許可することもできます。 たとえば、SmartAlec_Adminsグループはオペレーション・チームにProdコンパートメントのフル・コントロールを許可したいが、DevおよびTestコンパートメントの管理権限はDevTeamグループが持つべきである場合、SmartAlec_Adminsグループのメンバーは、ポリシーを以下のように記述します。 Allow group Operations to manage all-resources in compartment Prod Allow group DevTeam to manage all-resources in compartment Dev Allow group DevTeam to manage all-resources in compartment Test さらにSmartAlec_Adminsグループでは、Autonomous Data Warehouse(ADW)のバックアップへのアクセスをAutonomous Data Warehouseのサーバー・リソースから切り離したいと考えているとします。この場合、オペレーション・チームにてバックアップを管理し、DB_ADWグループがサーバーを管理するようにします。これには、以下のようにポリシーを記述します。 Allow group Operations to manage all-resources in compartment DBBackup Allow group DB_ADW to manage all-resources in compartment ADW コンパートメントの管理 完璧なコンパートメント・レイアウトを作成しようとすると、どこから手を付けてよいのかわからなくなるかもしれません。しかし心配は無用です。コンパートメントの構成は、後で変更できます。 また当社では、コンパートメント・エクスプローラという新しいツールもリリースしています。このツールは、リージョン内の特定のコンパートメントにあるすべてのリソースを確認しやすくするためのものです。これにより、すべてのリソースを可視化し、それらがコンパートメント全体でどのように整理されているのかを簡単に把握できます。 このツールでは、リソースの詳細を表示したり、コンパートメント間でリソースを移動させたりできます。またサブコンパートメント全体でリソースを再帰的に表示させることも可能です。当社ではこのツールを継続的に改良しており、新たな管理機能も続々と追加されていきます。 コンパートメント・ツリーはテナンシ内のどこにでも移動できるため、階層の再構築も可能です。そのため組織の変化に迅速に対応することができます。さらに、コンパートメントの名前の変更、削除、リストアも可能です。 コンパートメントをガバナンスに活用 コンパートメント・ツリーでは、大半のガバナンス機能をターゲティングできます。このターゲティングにより、コンパートメントおよびガバナンス・ツールにて柔軟にテナンシを管理できるようになります。 予算とリソース定数 テナンシのコンパートメント・ツリーに対し、予算やリソース定数を設定することができます。 予算を設定すると、コンパートメントでの支出額(または予想支出額)がしきい値を超えたときに、アラートを受け取ることができます。 またリソース定数により、許可するリソースの数を設定することができます。たとえば、1つのコンパートメントにデプロイできる特定のシェイプのコンピュート・インスタンスの数を制限することも可能です。 部門やプロジェクトの構成に合わせてコンパートメントを作成すると、それらの部門やプロジェクトに予算やリソース定数を設定することも簡単になります。 こうした予算やリソース定数はコンパートメント・ツリーにて集約されます。したがって、たとえば「データ分析」コンパートメントに入っている部門に予算を設定すれば、その部門自体がサブコンパートメントに予算を設定することができます。たとえば、2,000ドルの予算がある状態で、「データ分析」チームがデータ用と分析用の2つのサブコンパートメントを作成すれば、それぞれのコンパートメントに1,000ドルずつ予算を割り当てることができます。さらに、いずれかのコンパートメントの予想支出額が1,000ドルを超えたら、アラートも届きます。 このように予算およびリソース定数を階層的に作成することで、テナンシのメンテナンスおよびコスト管理をコンパートメントの管理者に任せることが可能になります。Oracle Cloud Infrastructureのコスト管理機能に関する詳細は、コスト管理に関するこちらのページにてご確認ください。 タグ・デフォルト タグ・デフォルトを使用すると、タグ・デフォルトが設定されているコンパートメントにて作成されたすべてのリソースに、タグを自動的に適用することができます。たとえば管理者は、「${iam.principal.name}」という値にて「Operations.CreatedBy」というタグを適用するタグ・デフォルトを作成できます。このタグはリソースの作成者のユーザー名にまで拡張されます。これにより、テナンシは2つの自動タグ・デフォルト(CreatedByと、そのリソースが作成された時刻をキャプチャしたもの)で作成されます。 このタグ・デフォルトをテナンシのルート・コンパートメントで使用すれば、テナンシ全体で作成されたすべてのリソースにそのタグ・デフォルトを使ってタグ付けできるため、管理者は時間と労力を節約することができます。 同様に、コンパートメントの管理者がそのコンパートメント・ツリー内のすべてのリソースにタグを適用したい場合も、そのコンパートメント・ツリーの最上位のコンパートメントにタグ・デフォルトを作成すれば、これを実現できます。タグ付け機能の詳細は、こちらのタグ付け製品のページにてご確認ください。また、私のブログ記事Best Practices for Using Tags To Manage Costs, Operations, and Governance(タグを利用したコスト、オペレーション、ガバナンス管理のベスト・プラクティス)でも、タグ付けのベスト・プラクティスを紹介しています。 まとめ 適切に設計されたコンパートメント階層には、多くのメリットがあります。皆さんがそれぞれのテナンシにて、コンパートメントを引き続きご利用いただければ幸いです。なぜならコンパートメントは、テナンシ管理のために当社が提供できる最良の整理ツールだからです。当社のすべての新しいガバナンス機能でも、コンパートメントを使用して、テナンシの具体的なリソースに機能を容易にターゲティングできるようにしています。 Oracle Cloud Infrastructureを初めてご利用になる場合は、無料アカウントの作成からスタートしてください。

※本記事はMartin Sleemanによる”Best Practices for Compartments”を翻訳したものです。 Oracle...

基本からわかる!高性能×高可用性データベースシステムの作り方 第15回 最低限度のData Guard環境を手作業で構成するには

基本からわかる!高性能×高可用性データベースシステムの作り方 indexページ Oracle Databaseのレプリケーション機能にはいくつかありますが、障害に備えるためレプリケーション機能としてはData Guardフィジカル・スタンバイおよびその拡張であるActive Data Guardが第一選択肢です。手作業でData Guardを構成する場合、最初にデータベースの複製を作成する工程が最も手間がかかります。Oracle Database 18c以降ではdbcaコマンドを使用するとこの複製工程を大幅に簡略化することができます。Data Guardを構成するには最低限何を設定すればよいかを解説します。 Data Guardフィジカル・スタンバイの動作原理 Data Guardフィジカル・スタンバイは物理バックアップ・リカバリの応用でデータベースのレプリカを作成します。 Data Guardの前にまず、物理バックアップ・リカバリの仕組みをおさらいしましょう。 データベースに更新が発生すると、その情報はREDOログの形でオンラインREDOログ・ファイルに記録されます。これはデータ・ブロックにどのような変更が行われたかの記録です。データベースのフル・バックアップ、つまり全データ・ブロックのバックアップが取得してあれば、それを書き戻して(リストア)、REDOログを適用(リカバリ)すれば、REDOログが存在する時点までのデータベースの状態を復元することができます。 Data Guardフィジカル・スタンバイは、レプリケーション元のデータベース(プライマリ・データベース)のフル・バックアップを別のサーバーにリストアし、そこでプライマリ・データベースで生成されたREDOログを適用し続けることでプライマリ・データベースとデータ・ブロックの中身の構造まで全く同じになるデータベースを複製することができます。Data Guard用語では複製したレプリケーション先のデータベースをスタンバイ・データベースと呼びます。   Data Guardでは、プライマリ・データベースでREDOログ情報がSystem Global AreaのREDOログ・バッファに書き込まれるとスタンバイ・データベースに転送しようとします。そのため、いくつかあるレプリケーション機能の中でも、Data Guardが最も転送遅延が小さくなります。転送遅延が小さいため、レプリケーション先に更新が伝搬したことが確認できるまでCOMMITが確定しないという同期転送モードを持っているのもData Guardだけです。 Data Guardの仕組みをもう少し詳細に見ていきましょう。Data Guardは大別すると2つの機能に分解できます。1つはREDOログの送受信(REDO転送サービス)で、もう1つは受信したREDOログの適用(適用サービス)です。Data Guardの構成とは、これら2つの機能を設定することです。   1つ目の設定はREDOログの送受信に関する設定です。プライマリ・データベースではREDOログ送信に関する設定を行います。スタンバイ・データベースではREDOログ受信に関する設定を行います。受信したREDOログ情報はスタンバイREDOログ・ファイルに格納されます。CREATE DATABASEで作成したプライマリ・データベースではREDOログ情報はオンラインREDOログ・ファイルに格納されますが、スタンバイREDOログ・ファイルはデータベース作成時点では存在していないのでこれを作成する必要があります。スタンバイREDOログ・ファイルもオンラインREDOログ・ファイルと同様に固定長固定数のファイル群で、循環して上書きされます。スタンバイREDOログ・ファイルの内容も上書きされる前にアーカイブREDOログ・ファイルに書き出されていきます。 2つ目の設定はリカバリです。スタンバイREDOログ・ファイルおよびアーカイブREDOログ・ファイルに記録されたREDOログ情報をデータファイルに適用します。REDOログの送受信とリカバリは独立して動作します。つまり、リカバリ・プロセスを停止している場合でもREDOログの送受信は継続しています。 Active Data GuardというのはData Guardフィジカル・スタンバイの発展形で、スタンバイ・データベースをリカバリしながらもREAD ONLYでオープンすることができる機能です。 Data Guardフィジカル・スタンバイを手作業で構成 Oracle Enterprise Manager Cloud Controlが構成されているオンプレミス環境や、Oracle Cloud InfrastructureのDatabase Cloud ServiceではWebのGUIからData Guardが簡単に構成できるようになっていますが、ここでは手作業でコマンド操作して構成する方法を解説します。Data Guard環境の構築で最も手間がかかる工程は、最初にプライマリ・データベースの複製を作る(違う名前でデータベースをリストアする)ところです。Oracle Database 18c以降のDatabase Configuration Assistant(dbcaコマンド)を使用すると、Data Guardスタンバイ・データベース用の複製を簡単に作成できるようになっています。 Data Guardの構築は大別すると3つの工程になります。 1.    プライマリ・データベースでの設定 プライマリ・データベースからスタンバイ・データベースを複製するにあたり、スタンバイ・データベースに設定が引き継がれる項目をプライマリ・データベースで設定しておきます。 2.    スタンバイ・データベースでの設定 プライマリ・データベースからスタンバイ・データベースの複製を作成します。その後、プライマリ・データベースの設定を引き継がない項目をスタンバイ・データベースで設定します。 3.    REDO転送サービスの設定とリカバリ開始 Data GuardのREDOログ送受信の設定を双方に行います(REDO転送サービスの開始)。そして、スタンバイ・データベースでリカバリを開始します(リカバリ・サービスの開始)。 それでは各工程を見ていきましょう。 1.    プライマリ・データベースでの設定 1.1.     アーカイブ・ログ・モード Data Guardはアーカイブ・ログ・モードである必要があります。アーカイブ・ログ・モードへの変更はData Guard構築でプライマリ・データベースの停止が発生する唯一の工程です。しかし本番運用されているデータベースならばすでにアーカイブ・ログ・モードになっていることが想定されるので、Data Guardは実質的に無停止で構成することが可能です。 $ srvctl stop database -db ... $ sqlplus / as sysdba SQL> startup mount SQL> alter database archivelog; SQL> select LOG_MODE from v$database; LOG_MODE ------------------------------------ ARCHIVELOG SQL> exit $ srvctl start database -db ...   1.2.     FORCE LOGGING Data GuardはREDOログ情報に乗らない変更は伝搬しません。そのため、NO LOGGINGが指定されている操作でもREDOログを生成するFORCE LOGGINGモードにします。 SQL> alter database force logging; SQL> select force_logging from v$database; FORCE_LOGGING -------------------- YES 1.3.     STANDBY_FILE_MANAGEMENT プライマリ・データベースで新しいデータファイルが生成されたとき、それをスタンバイ・データベースでも自動生成する設定を行います。 SQL> alter system set STANDBY_FILE_MANAGEMENT='AUTO' scope=both sid='*'; 1.4.     スタンバイREDOログ・ファイル スタンバイ・データベースで受信されたREDOログ情報はスタンバイREDOログ・ファイルに格納されます。スタンバイREDOログ・ファイルはスタンバイ・データベースにあればよいのですが、Data Guardを構成する場合はいずれプライマリとスタンバイの役割を入れ替える(スイッチオーバー)ことになるので、あらかじめプライマリ・データベースにも作成します。プライマリ・データベースで作成したスタンバイREDOログ・ファイルは、dbcaで複製するとスタンバイ・データベースにもその構成が引き継がれます。 Oracleインスタンスは自分が書き込む専用のREDOログのストリームを持っており、これをREDOスレッドと呼びます。RAC構成では複数のOracleインスタンスがあるので、REDOスレッドはOracleインスタンスの数だけあります(V$LOG.THREAD#)。   スタンバイREDOログ・ファイルの構成はオンラインREDOログ・ファイルの構成から導出しますので、まずオンラインREDOログ・ファイルの構成を調べます。CDB$ROOTにログインしてV$LOGビューからREDOスレッドとメンバーの構成、そしてファイルのサイズを調べます。 SQL> select thread#,group#,bytes from v$log;    THREAD#     GROUP#      BYTES ---------- ---------- ----------          1          1 1073741824          1          2 1073741824          2          3 1073741824          2          4 1073741824   作成するスタンバイREDOログ・ファイルは、各REDOスレッドにオンラインREDOログのメンバー数+1個のメンバーを作成します。各ファイル・サイズはオンラインREDOログ・ファイルと同じにします。GROUP番号はオンラインREDOログおよびスタンバイREDOログあわせて一意な番号を指定します。 SQL> alter database add standby logfile thread 1 group 5 size 1073741824, group 6 size 1073741824, group 7 size 1073741824; SQL> alter database add standby logfile thread 2 group 8 size 1073741824, group 9 size 1073741824, group 10 size 1073741824; 2.    スタンバイ・データベースでの設定 2.1.     dbcaでプライマリ・データベースをオンライン複製 Data Guardの構築で最も手間がかかる工程は、データベースを違う名前(DB_UNIQUE_NAME)で複製を作るところです。 Oracle Databaseはデータベースの名前が3階層あります。CDBおよびnon-CDBの名前で2階層(DB_NAMEとDB_UNIQUE_NAME)、そしてPluggable Databaseの名前で1階層(PDB_NAME)です。Data Guardを構成するデータベース群は、それぞれDB_UNIQUE_NAMEが一意になっています。DB_NAMEとPDB_NAMEは共通です。DB_UNIQUE_NAMEが管理操作上のデータベースを区別する識別子です。データベースのファイル・パスに含まれる「データベースの名前」や、srvctlコマンドの-dbオプションで指定する「データベースの名前」というのはDB_UNIQUE_NAMEを指しています。そのため、複製を作る前にDB_UNIQUE_NAMEを決めておく必要があります。   手作業でデータベースを複製するには、DB_UNIQUE_NAMEを変えてOracleインスタンスを起動するための初期化パラメータファイルの用意や各種ログ・ファイルが生成されるディレクトリの作成などが必要です。これらをdbcaで大幅に簡略化します。 dbcaにプライマリ・データベースへの接続情報を指定し、内部的にはRMANを使用してオンライン・フル・バックアップを取得し直接スタンバイ・データベース用のストレージにリストアします。スタンバイ・データベース用のOracleインスタンスの初期化パラメータは基本的にはプライマリ・データベースのものを継承します。変更のあるパラメータはdbcaのオプションで指定します。 $ export LANG=C $ export NLS_LANG=American_America.US7ASCII $ dbca -silent -createDuplicateDB -createAsStandby      -gdbName DB_NAME.DB_DOMAIN      -sysPassword SYSユーザーのパスワード      -primaryDBConnectionString scan-host-name:port/SERVICE_NAMES      -dbUniqueName DB_UNIQUE_NAME      -sid ORACLE_SID接頭辞      -initParams db_create_file_dest=+DATA, db_recovery_file_dest=+RECO      -databaseConfigType RAC      -adminManaged      -nodelist node3,node4   分類 dbcaのオプション 意味 プライマリ・データベースへの接続情報 -gdbName グローバル・データベース名 DB_NAME.DB_DOMAIN -sysPassword SYSユーザーのパスワード -primaryDBConnectionString Oracle Netの接続情報 CDB$ROOTに接続するのでサービス名はDB_UNIQUE_NAME.DB_DOMAIN スタンバイ・データベース側の識別子の指定 -dbUniqueName DB_UNIQUE_NAME -sid ORACLE_SID接頭辞 RACの場合はこれに1,2,3...と数値が付加される -initParams プライマリ・データベースの初期化パラメータから変更するもの RAC構成 -databaseConfigType RAC/RAC One Node構成の指定 -adminManaged RACのクラスタ・タイプの指定 -nodelist RACインスタンスを起動するノードの指定 dbcaで複製が完了するとスタンバイ・データベースになれる状態(V$DATABASE.DATABASE_ROLE=PHYSICAL_STANDBY)になっておりOPEN READ ONLYでアクセスできる状態になっています。また、Oracle Grid Infrastructure上に構成されている環境ではdbcaの完了でクラスタのリソース登録まで行われるので、srvctlコマンドでOracleインスタンスの操作ができるようになっています。 SQL> select OPEN_MODE,DATABASE_ROLE from v$database; OPEN_MODE       DATABASE_ROLE --------------- -------------------- READ ONLY       PHYSICAL STANDBY 2.2.     BCTファイル(Optional) Data Guardフィジカル・スタンバイはスタンバイ・データベースでもデータファイルのバックアップを取得することができます。Block Change Trackingファイルを設定しておけば高速増分バックアップも可能です。これはプライマリ・データベースで設定されていてもスタンバイ・データベースには引き継がれないので、必要であればスタンバイ・データベースであらためて設定が必要です。 SQL> alter database enable block change tracking; SQL> select status from V$BLOCK_CHANGE_TRACKING; STATUS ---------- ENABLED 2.3.     FLASHBACK ON(Optionalただし実質的に必須) Data GuardのREDO転送およびリカバリ自体はFlashback Databaseが設定されていなくても動作します。ただし、スタンバイ・データベースへのフェイルオーバーのテストや、スタンバイ・データベースを切り離してアプリケーションのテストなどに使用すると、プライマリ・データベースとはREDOログの内容がずれているのでそのままではREDOログ適用の再同期ができなくなります。これをFlashback Databaseを使用せずに再同期させるには、REDOログがずれる前に取得したフル・バックアップをリストアするか、現在のプライマリ・データベースから複製しなければなりません。つまりスタンバイ・データベースの再構築が必要になります。これを簡単に再同期できるようにするためには、Flashback Databaseの機能を使用しREDOログの内容がずれる前の時刻まで戻します。そのため、Data Guardをまともに運用しようとするならばFlashback Databaseの設定は実質的に必須です。 SQL> alter database flashback on; SQL> select flashback_on from v$database; FLASHBACK_ON -------------------- YES 3.    REDO転送サービスの設定とリカバリ開始 3.1.     tnsnames.ora REDOログの転送先の情報はtnsnames.oraファイルのエントリを使用します。書式はアプリケーションが接続するときの書き方と同じで、Oracleリスナーのアドレスとサービス名を指定します。REDO転送の宛先のサービス名はCDB$ROOTのサービス、つまりデータベースのデフォルト・サービス名であるDB_UNIQUE_NAME.DB_DOMAINになります。 スタンバイ・データベース側にもプライマリ・データベースへの接続情報を作成しておきます。つまり、Data Guardを構成する全データベースへの接続情報をあらかじめ全ノードのtnsnames.oraに記述しておきます。 primary_database_net_alias =   (DESCRIPTION =     (ADDRESS = (PROTOCOL = TCP)(HOST = scan-host-name-primary)(PORT = scan-port))     (CONNECT_DATA =       (SERVER = DEDICATED)       (SERVICE_NAME = primary_SERVICE_NAMES)     )   ) standby_database_net_alias =   (DESCRIPTION =     (ADDRESS = (PROTOCOL = TCP)(HOST = scan-host-name-standby)(PORT = scan-port))     (CONNECT_DATA =       (SERVER = DEDICATED)       (SERVICE_NAME = standby_SERVICE_NAMES)     )   ) 3.2.     REDOログ転送サービスの初期化パラメータ REDOログ送受信の設定はOracleインスタンスの初期化パラメータに設定します。プライマリとスタンバイの役割で設定するパラメータが異なりますが、プライマリとスタンバイの役割はいつか入れ替わるので、あらかじめすべての初期化パラメータをプライマリとスタンバイの両方に設定しておきます。Data Guardに関連する初期化パラメータはすべてALTER SYSTEM SET ... SCOPE=BOTHで動的に設定できます。   3.2.1.    アーカイブREDOログ・ファイルの出力先 プライマリとスタンバイ両方でLOG_ARCHIVE_DEST_1を設定しておきます。ここに指定するDB_UNIQUE_NAMEは自データベースの名前です。出力先はDB_RECOVERY_FILE_DESTを使用します。 SQL> alter system set DB_RECOVERY_FILE_DEST_SIZE=195G scope=spfile sid='*'; SQL> alter system set DB_RECOVERY_FILE_DEST='+RECO' scope=spfile sid='*'; SQL> alter system set LOG_ARCHIVE_DEST_1='LOCATION=USE_DB_RECOVERY_FILE_DEST VALID_FOR=(ALL_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=db_unique_name' scope=spfile sid='*'; SQL> alter system set LOG_ARCHIVE_DEST_STATE_1='ENABLE' scope=spfile sid='*'; 3.2.2.    スタンバイ・データベース REDOログ送受信の許可と、アーカイブREDOログ・ファイルをリモートから取得する場合の接続先を指定します。LOG_ARCHIVE_CONFIGのDG_CONFIGにはData Guardを構成するすべてのデータベースのDB_UNIQUE_NAMEを列挙します。FAL_SERVERはリモートのデータベース、つまりプライマリ・データベースへ接続するtnsnames.oraのエントリを指定します。 SQL> ALTER SYSTEM SET  LOG_ARCHIVE_CONFIG='DG_CONFIG=(primary_DB_UNIQUE_NAME,standby_DB_UNIQUE_NAME)' scope=spfile sid='*'; SQL> ALTER SYSTEM SET FAL_SERVER='primary_alias' scope=spfile sid='*'; プライマリとスタンバイの役割はいつか入れ替わるので、あらかじめプライマリ・データベースにもFAL_SERVERは設定しておきます。 3.2.3.    プライマリ・データベース REDOログ送受信の許可と、REDOログ送信先を指定します。REDOログ送信先の指定にはアーカイブREDOログ・ファイルの出力先の指定と同様にLOG_ARCHIVE_DEST_nを使用します。 SQL> ALTER SYSTEM SET  LOG_ARCHIVE_CONFIG='DG_CONFIG=(primary_DB_UNIQUE_NAME,standby_DB_UNIQUE_NAME)' scope=spfile sid='*'; SQL> ALTER SYSTEM SET  LOG_ARCHIVE_DEST_2='SERVICE=standby_database_net_alias                        ASYNC                        VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE)                        DB_UNIQUE_NAME=standby_DB_UNIQUE_NAME' scope=spfile sid='*'; SQL> ALTER SYSTEM SET LOG_ARCHIVE_DEST_STATE_2='ENABLE' scope=spfile sid='*'; LOG_ARCHIVE_DEST_nの指定でREDOログ転送サービスの動作モードが変わります。これは設定の一例です。詳しくはOracle Databaseのマニュアル「Data Guard概要および管理」を参照してください。 スタンバイとプライマリの役割はいつか入れ替わるので、スタンバイ・データベースにもあらかじめLOG_ARCHIVE_DEST_nを設定しておきます。 ALTER SYSTEM SETで設定が完了した瞬間に、REDOログの送受信が開始されます。 3.3.     リカバリ開始 スタンバイ・データベースでリカバリを開始します。dbcaで複製が完了するとREAD ONLYでOPENした状態になっていました。これでリカバリを開始するとActive Data Guardになります。OPEN_MODEがREAD ONLY WITH APPLYになります。 SQL> select OPEN_MODE,DATABASE_ROLE from v$database; OPEN_MODE            DATABASE_ROLE -------------------- -------------------- READ ONLY            PHYSICAL STANDBY SQL> RECOVER MANAGED STANDBY DATABASE DISCONNECT; SQL> select OPEN_MODE,DATABASE_ROLE from v$database; OPEN_MODE            DATABASE_ROLE -------------------- -------------------- READ ONLY WITH APPLY PHYSICAL STANDBY 3.4.     スタンバイ・データベースの状態遷移 スタンバイ・データベースをREAD ONLY WITH APPLY状態にするActive Data GuardはEnterprise Editionの有償ライセンス・オプションです。READ ONLYでOPENせずにMOUNT状態にするData Guardフィジカル・スタンバイとの状態遷移はコマンドで操作します。 通常はData Guard(MOUNT)かActive Data Guard(OPEN READ ONLY)のどちらか片方に決め打ちして運用するので、起動手順としてはどちらもOracleインスタンス起動 → リカバリ開始になります。Oracleインスタンスの起動オプションが異なります。   まとめ 今回はData Guardフィジカル・スタンバイとその拡張であるActive Data Guardの動作原理と、手作業の場合に最低限なにを設定すればData Guardが構築できるかまでを解説しました。REDO転送モードの設定は要件に応じてマニュアルを参照してください。 Data GuardはREDO転送サービスと適用サービスの設定を行えば終わりというわけにはいきません。プライマリとスタンバイの役割の切り替え(ロール変換)などの運用を検討する必要があります。ですが今回は構築まで! 基本からわかる!高性能×高可用性データベースシステムの作り方 indexページ  

基本からわかる!高性能×高可用性データベースシステムの作り方 indexページ Oracle Databaseのレプリケーション機能にはいくつかありますが、障害に備えるためレプリケーション機能としてはData Guardフィジカル・スタンバイおよびその拡張であるActive Data...

もしもみなみんがDBをクラウドで動かしてみたら 第17回 課金と停止について - Autonomous Database/DBCS/ExaCS

window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-159254751-1'); もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/02/25時点のものになります みなさん、こんにちは。 今回のテーマは、Oracle Cloud上でOracle DatabaseのPaaSサービスを利用する際に気になる、課金周りや環境の停止についてです。関連するよく聞かれることとして、「利用していない間は課金を抑えたいのですが、停止すると課金は止まりますか?」「スケーリングでOCPUを0にするのと、環境の『停止』は何が違うのですか」などがあります。実は、サービスによってこのあたりの動作や意味合いが変わってきます。そこを深堀すべく、今回はOracle Database のPaaS全サービス、Autonomous Database – Shared Infrastructure/Dedicated Infrastructure、Database Cloud Service- Virtual Machine/Bare Metal、Exadata Cloud Serviceを対象に解説していきます。 ※本記事の内容は記事執筆時の情報となります。課金周りの最新情報はこちらのドキュメントをご確認ください。 ・Oracle PaaS and IaaS Universal Credits Service Descriptions 英語版 ・Oracle Cloud Infrastructure ドキュメント 英語版 / 日本語版   各サービスの課金単位 各サービスを利用する上で、サービスごとに課金の対象や考え方が異なってきます。まずは、各サービスごとの課金に関わるコンポーネントについてまとめます。 Database Cloud Service - Virtual Machine (DBCS-VM) VMのコンピュート部分は、エディションごとにOCPU単価が異なり、利用するシェイプでOCPU数が決まります。メモリやネットワーク帯域幅などもシェイプ毎に決まります。コンピュート部分とは別に、ローカル・ストレージとしてBlock Volumeが利用されます。このBlock Volumeには、S/W用の領域(/u01=約200GB)+データベース利用領域(ASMのDATAディスク・グループとRECOディスク・グループ)が含まれます。課金対象のサイズは、データベース利用領域のサイズ(利用可能なストレージ)として選択するサイズではなく、Total Storage(総ストレージ)のサイズになるのでご注意ください。また、データベースの自動バックアップ用の領域としてObject Storageが利用されるので、ここも含めて考えましょう。 ・Oracle Cloud Infrastructure ドキュメント 英語 / 日本語 ・価格ページ  Database Cloud Service- Bare Metal (DBCS-BM) DBCS-BMは、VM同様エディションごとにOCPU単価が決まっていますが、シェイプではなく追加キャパシティとして指定する値で課金対象のOCPU数が決まります。インフラ部分はシェイプで固定になります。なお、DBCS-BMのインフラ部分にはデフォルトで2 OCPU分が含まれます。データベースの自動バックアップ用の領域には、Object Storageが利用されます。 ・Oracle Cloud Infrastructure ドキュメント 英語 / 日本語 ・価格ページ  Exadata Cloud Service (ExaCS)     ExaCSは、有効なOCPUとして指定している数がOCPU課金対象となります。このOCPUを有効にする対象は、ノード毎ではなくクラスタ全体に対して有効にするOCPUになります。例えば、コンピュートが2台作られるQuarterやBaseシェイプで"24"とした場合には、1コンピュートあたり12が割り当てられます。ストレージなどInfrastructure部分は、ExaCSのシェイプの選択で決まります。データベースの自動バックアップ用の領域には、Object Storageが利用されるので、ここも含めて考えましょう。 ・Oracle Cloud Infrastructure ドキュメント 英語 / 日本語 ・価格ページ    Autonomous Database – Shared Infrastructure (ADB-S) Autonomous Data Warehouse(ADW) /Autonomous Transaction Processing(ATP)でおなじみのShared InfrastructureのAutonomous Database(ADB-S)は、有効にするOCPUとして指定している数がOCPU課金対象となります。ストレージ部分は、実利用しているサイズではなくExadata Storageとして利用可能とするサイズが対象となります。データベースの自動バックアップ用の領域はサービス利用料金に含まれるので別途計上はされませんが、手動でバックアップを取得する場合に利用する領域はObject Storageの課金対象となります。 Autonomous Database – Dedicated Infrastructure (ADB-D) Autonomous Databaseをインフラ占有型で利用可能なDedicated Infrastructure(ADB-D)は、OCPU部分は有効にするOCPUとして指定している数の合計数がOCPU課金対象となります。ストレージなどInfrastructure部分は、利用するシェイプの選択で決まります。データベースの自動バックアップ用の領域はサービス利用料金に含まれるので別途計上はされませんが、手動でバックアップを取得する場合に利用する領域はObject Storageの課金対象となります。 ・Oracle Cloud Infrastructure ドキュメント 英語 / 日本語 ・Autonomous Data Warehouse 価格ページ / Autonomous Transactional Processing 価格ページ   課金を止めるには ・OCPUの課金 OCPUの課金に関しては、その環境に割り当てているOCPU数=利用可能なOCPU数が課金対象となります。課金を止めたいときには、DBCS-VMやAutonomous Databaseは停止することでOCPU課金も停止します。DBCS-BMやExaCSは、OCPUを0にスケールダウンすることでOCPU課金が停止します。 ・Database Cloud Service (DBCS)– VMでOCPU課金を停止する場合 ・Autonomous DatabaseでOCPU課金を停止する場合 ・Exadata Cloud Service(ExaCS)でOCPU課金を停止する場合   OCPUの課金を停止している間もストレージ上にはデータが存在=ストレージを利用していることになるので、ストレージ部分の課金は継続します。データをローカル・ストレージに保持していることのメリットは、利用するときにはデータロード不要で起動してすぐにデータを扱える状態にできることだと思います。なおAutonomous Databaseの自動スケーリングの課金の考え方は、第15回 Autonomous Databaseの自動スケーリングを設定してみようをご参照ください。 ・ローカルのストレージ/Infrastructureの課金 ストレージ部分は全サービスで共通で、システム(サービス・インスタンス)を作成してから終了するまでは課金が継続されます。そのため、ストレージの課金を止めたいときには終了=環境の削除が必要になります。その際にデータ保持が必要であれば、Object Storageなどのリモート・ストレージにバックアップを取得したり複製環境を残しておくなどして、データの退避を検討する形になります。課金を止めるのではなく抑えるという観点だと、Autonomous Database - Shared Infrastructureの場合は利用状況に応じて必要なストレージ容量を割り当てる=スケールアップ・ダウンが可能なので、ストレージをあまり使っていない状況であれば、スケール・ダウンで減額も可能です。専有型のExaCSDBCS-BMの場合は、利用シェイプによってストレージ含むInfrastructureサイズが決まり、利用可能なリソースが最初から割り当てられる=有効になるので、増減なしの固定で終了時まで課金が継続されます。注意点としては、ExaCSやAutonomous Database - Dedicated Infrastructureは48時間という最低利用期間が設けられているため、48時間未満の利用時間で削除したとしても48時間分は課金が発生します。49時間以降は、利用した分だけ=終了するまでの時間単位での課金になります。   停止とOCPUを0にスケールダウンすることとの違いは? 前述したようにOCPUの課金を止める=課金対象外としたい際に、環境の停止で出来るサービスとできないサービスがあります。この違いを一言でいうと、OCPUの割り当て対象がなにかで違います。   コンピュートやDB単位でOCPUを割り当てるサービスは、システムを停止することでOCPU課金が止まります。DBシステム全体にOCPUを割り当てるサービスは、割り当てているOCPU数を減らす(0にする)ことで課金が止まります。 たとえばExaCSはOCPUの割り当て対象はDBシステム全体に対してなので、コンソールから停止を実行(ExaCSの場合は1コンピュートの停止)しても課金は継続されるので、課金を停止したい場合には割り当てているOCPU数を減らします。DBCS-VMは、コンピュートに対してOCPUが割り当てられているので、RAC環境で片ノード(コンピュート)を停止すると、そのコンピュートの課金は停止するようになっています。 まとめ 複数サービスをご利用いただいている方だと、今回の内容のようなサービスごとの考え方の違いになれるのは、なかなか難しいかと思います。今回まとめさせていただいたように、Oracle Cloud上のOracle DatabaseのPaaSサービスは提供形態や管理体系などがサービスによって異なるため、課金周りや停止の考え方にもサービス毎に考え方が異なっています。今回の記事が、課金まわりだけでなく、サービス形態の違いを理解するうえでもご参考にしていただければ幸いです。   関連リンク ・Oracle PaaS and IaaS Universal Credits Service Descriptions 英語版 ・Oracle Cloud Infrastructure ドキュメント 英語版 / 日本語版 もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ

もしもみなみんがDBをクラウドで動かしてみたら 連載Indexページ ※本記事は2020/02/25時点のものになります みなさん、こんにちは。 今回のテーマは、Oracle Cloud上でOracle DatabaseのPaaSサービスを利用する際に気になる、課金周りや環境の停止についてです。関連するよく聞かれることとして、「利用していない間は課金を抑えたいのですが、停止すると課金は止まりますか?」「ス...

Cloud Security

DDoS保護をクラウド・プロバイダ頼みにしてはいけない

Paul Toal Distinguished Solution Engineer(Cyber Security) 分散型サービス拒否(DDoS)攻撃は昔からある攻撃で、少なくとも2000年にはその存在が確認されています。しかしDDoS攻撃とはいったいどのようなものなのでしょうか。ネット上にもその定義はさまざま掲載されていますが、端的に説明するなら、これは複数のソースから開始される攻撃で、その意図は、オンライン・サービスの処理能力を大量のデータで溢れさせ、通常のトラフィックを不可能にさせることにあります。詳しくは、こちらを参照してください。 DDoS攻撃は、企業や組織に非常に大きな混乱をもたらし得るものです。攻撃の時間は数秒から数時間までさまざまで、その対象がオンライン店舗であれ、物流企業であれ、金融機関であれ、その攻撃の間は、オンラインでのサービス提供が疎外される可能性があります。有名なオンライン・サービスに影響を与え、人々の注目を集めるような大々的なDDoS攻撃も発生しています。ここ数年はモノのインターネット(IOT)を背景として、DDoS攻撃が再び活性化しており、オンライン接続された何百万もの小型コンピュータが複数攻撃ソースの形成に利用されています。 多くの企業や組織がクラウド・サービスへ移行している現代では、そのクラウド・プロバイダでカバーされている対策範囲を理解しておくことが重要です。一部のプロバイダは、有料サービスとして、またはそのクラウド・サービスの一部として、DDoS保護を提供しています。Oracle Cloud Infrastructure(OCI)が提供する保護については、こちらでも説明されていますが、以下にその抜粋を掲載いたします。   分散型サービス拒否(DDoS)保護:次世代の「Oracle Cloud Infrastructure」の一部として、すべてのオラクル・データセンターでは、大規模およびレイヤー3/4のDDoS攻撃の検出と緩和が自動化されます。これは、攻撃が長く続く場合でもオラクル・ネットワーク・リソースの可用性を確保する上で役立ちます。   ということです。では、オラクルがDDoS保護を提供しているとすれば、OCIを利用するお客様は保護されており、それで心配はないということでしょうか。残念ながら、それで万事解決とはなりません。上記で何がカバーされているかを正確に理解するためには、DDoS攻撃への理解をもう少し深め、さまざまなタイプのDDoS攻撃を考察する必要があります。 私はDDoS攻撃について考えるとき、3つのタイプの攻撃を念頭に置きます。そしてそのすべてがサービスをオフラインにし得るものです。   DNSベースのDDoS攻撃 第1のタイプはDNSベースの攻撃です。この場合、攻撃者はDNSサーバーを溢れさせ、IPアドレスへの正規のリクエストに応答できない状態にします。たとえば私がwww.oracle.comにアクセスしようとしても、このドメインに関連付けられているDNSサーバーが私のマシンにwww.oracle.comのIPアドレスを伝えることができないため、私はこのサイトにアクセスできなくなります。この段階では、WebサイトをホストしているWebサーバーはまったく攻撃されていません。単にインターネットの電話帳を攻撃しているのみです。 アプリケーションベースの攻撃 第2のタイプは、オンラインのコンテンツやサービスをホストしているサーバーを直接攻撃するものです。どのサーバーにも、処理できるリクエストの量に限界があります。ここでの攻撃者の目的は、大量のリクエストと接続でアプリケーションを溢れさせ、正規のトラフィックに応答できないようにすることにあります。 ネットワークベースの攻撃 第3のタイプはネットワークベースの攻撃です。「DDoS攻撃」と聞いてたいていの人が思い浮かべるのもこの攻撃です。これは、ネットワーク・パイプを溢れさせるものです。攻撃者が悪意あるトラフィックを大量に送り、パイプが詰まってしまえば、適切なリクエストがそこを通れなくなってしまいます。 では、OCIはこれらの攻撃パターンのどこに対応しており、利用者とOracle Cloud Infrastructure、それぞれの責任範囲はどこまでなのでしょう。 オラクルがOCIに関連してDDoS保護について述べるとき、これはネットワークベースの攻撃、つまり多くの人がボリュームベースの攻撃と考えるものを対象としています。OCIでは、全顧客のアドレス空間全体を監視しており、大規模なボリュームベースの攻撃が確認されると、それらをブロックして悪意あるトラフィックを排除するためのツールおよびプロセスを実行します。これは、(OSI参照モデルの)レイヤー3/4で実施されます。このようにオラクルでは、OCIのすべての顧客を対象に、3つの攻撃タイプのうちの1つに標準で対応しています。しかし他の2つの攻撃タイプについては、どうでしょう。これらに対応するには、どうすればよいでしょうか。 DNSベースの攻撃に対しては、DNSプロバイダ(つまり、オラクルか否かに関係なく、プライマリDNSサービスの提供に利用しているプロバイダ)のDDoS保護能力を確認する必要があります。オラクルでは、OCIのサービスの一環として、顧客のプライマリDNSプロバイダまたはセカンダリDNSプロバイダとして利用可能なDNSサービスを用意しています。OCI DNSは、複数の大陸に戦略的に配置されている複数のデータセンターのグローバル・エニーキャスト・ネットワークで、さまざまな冗長インターネット・トランジット・プロバイダを活用して究極の回復性とDDoS攻撃からの保護を実現します。 アプリケーションベースの攻撃に対しては、アプリケーション自体を保護する対策を講じる必要があります。しかしここでも、利用できるテクノロジーやサービスがあります。OCIでは、クラウド・セキュリティ・ポートフォリオの一環として、Web Application Firewall(WAF)を提供しています。WAFは(OSI参照モデルの)レイヤー7で動作し、ボットネット、アプリケーション攻撃、およびDDoS攻撃からアプリケーションレイヤーを保護します。これは、OCIにホストされているアプリケーションだけでなく、インターネットに接続するすべてのWebアプリケーションの保護に使用できます。レートの制限やアクセス制御などの機能を使用して、DDoS攻撃からWebアプリケーションを保護することができます。実際のOCI WAFをご覧になりたい場合は、こちらにて、おもなユースケースのデモンストレーションをご覧ください。 以下は、攻撃タイプとその対策をまとめた表です。     ご覧のとおりDDoS攻撃は多面的であるため、この脅威を低減させるには、大半のセキュリティ対策と同様、多層防御戦略を講じる必要があります。すでに適切なセキュリティ対策を実施している場合も、必要な施策を現在検討中の場合も、この記事がDDoS攻撃と対策について、またもちろん、そうした対策にオラクルがお役に立てることについて、ご理解いただくためのヒントとなれば幸いです。 ※本記事は、 Paul Toal : Distinguished Solution Engineer(Cyber Security) による"Don't just rely on your Cloud Provider for DDoS protection"を翻訳したものです。

Paul Toal Distinguished Solution Engineer(Cyber Security) 分散型サービス拒否(DDoS)攻撃は昔からある攻撃で、少なくとも2000年にはその存在が確認されています。しかしDDoS攻撃とはいったいどのようなものなのでしょうか。ネット上にもその定義はさまざま掲載されていますが、端的に説明するなら、これは複数のソースから開始される攻撃で、その意図は、...

Kubernetes

GitLab CI/CDパイプラインとContainer Engine for Kubernetesを統合する

Gilson Melo Senior Principal Product Manager   オラクルのパブリック・クラウドは、オープン・ソース・テクノロジーとそうしたテクノロジーをサポートするコミュニティに対応しています。クラウド・ネイティブ・テクノロジーおよびDevOps手法へのシフトが加速するなか、当社は多くの企業や組織から、クラウド・プロバイダーによる構築か否かに関係なく、クラウドのロックインを回避し、必要な処理を実行できる、パフォーマンスと信頼性の高いクラウドを求める声に接してきました。Oracle Cloud InfrastructureでのGitLabの運用は、DevOpsプラットフォームにおいて、1つのアプリケーションでDevOpsの全ライフサイクルをカバーできる選択肢となります。 GitLab CI/CDとOracle Cloud GitLabは、単一のアプリケーションとして提供される、完全にオープン・ソースのDevOpsプラットフォームです。これにより、開発、セキュリティ、およびオペレーションの各チームによる協調的なソフトウエア構築に根本的な変革をもたらすことができます。またGitLab CI/CDパイプライン自動DevOps機能とOracle Cloud InfrastructureのContainer Engine for Kubernetesとを統合することで、クラウドにてコードを構築、テスト、デプロイ、モニターできる、信頼性と拡張性に優れた総合的なワークフロー・プラットフォームを持つことができます。 Container Engine for Kubernetesは、クラウドでのKubernetesクラスタのデプロイ、管理、および拡張を支援するサービスです。このサービスを利用することで、企業や組織はOracle Cloud Infrastructureで運用しているサービスとKubernetesとを統合して、動的にコンテナ化されたアプリケーションを構築することができます。 CI/CDワークフロー 共有リポジトリにあるチームのコードは、継続的インテグレーション(CI)により統合できます。開発者がプル・リクエストを使って新しいコードを共有すると、そのリクエストをトリガーとして、パイプラインにてコードの構築、テスト、検証が実施され、その後リポジトリにマージされます。CIは、開発サイクルの早い段階でのエラーの発見、統合上の課題の削減、構成上の問題の回避に効果的です。 継続的デリバリ(CD)は、CIで検証済みのコードが、構造化されたデプロイメント・パイプラインを通じて間違いなくアプリケーションにデリバリされるようにするものです。またCDは、各変更がリリース可能であるようにし、各リリースのリスクを低減し、より多くの価値を高い頻度で提供し、顧客から迅速かつ頻繁にフィードバックを得られるようにするものです。 こうしたCIとCDを組み合わせることで、迅速かつ効果的な構築が可能になります。この組み合わせは、開発プロセスの最適化にとって非常に重要です。 始めるには まずは、Container Engine for KubernetesにてGitLab CI/CDサービスを構築およびデプロイするために必要なコンポーネントを確認しましょう。Container Engine for KubernetesにGitLabをインストールするには、Oracle Cloud Infrastructure(OCI)とkubectlコマンド・ラインをインストールおよび設定している、デスクトップ(クラウドVMまたはオンプレミス)が必要です。 さらに、インストールを進めるには、以下のコンポーネントも必要です。 バージョン1.14.8以降のKubernetes バージョン2.14以降のHelm/Tiller ローカルの仮想クラウド・ネットワーク(VCN)セキュリティ・リストで公開されている、Kubernetesのポッドとワーカー・ノードに対応するサブネットCIDRおよびポート デプロイメント・プロセス Oracle Container Engine for KubernetesにGitLabをデプロイするには、デプロイメント・ガイドを確認し、以下のステップを実施します。 次のコマンドを実行します。その際、「example.com」の部分を実際のDNSに変更します。Helmにて別のネームスペースにGitLabのリソースをインストールしたい場合は、「--install」の後に「--namespace NEW_NAME_SPACE」を追加します。 helm repo add gitlab https://charts.gitlab.io/ helm repo update helm upgrade --install gitlab gitlab/gitlab --set externalUrl=http://gitlab.example.com \ --timeout 600s \ --set global.hosts.domain=example.com \ --set certmanager-issuer.email=me@example.com プロビジョニング・プロセスが完了したら、次のコマンドを実行してGitLabに割り当てる外部IPアドレスを指定します。 kubectl get services gitlab-nginx-ingress-controller ステップ1のDNSエントリ(例:gitlab.example.com)でGitLabを指定します。お使いのDNSゾーン管理サービスを使用して、DNSレコード・エントリを更新します。これを実施しないと、ステップ5にて「default backend - 404」というエラー・メッセージが表示されます。404エラー・メッセージが表示された場合は、DNSエントリを修正して、GitLab Runnerポッドを再作成します。 DNSゾーン・レコードが作成されたら、次のコマンドを使用して、ダッシュボードでの接続に必要なbase64ルート・パスワードを取得します。 kubectl get secret <name>-gitlab-initial-root-password -ojsonpath='{.data.password}' | base64 --decode ; echo " すべてのポッドが稼働するまで待ち、その後次のステップに進みます。以下のスクリーンショットは、Helmを通じてインストールされるポッドの一覧になります。 ブラウザを開き、DNSアドレスを入力し、その後にrootのユーザー名とbase64パスワードを使用します。 既存のContainer Engine for Kubernetesクラスタ情報をGitLabに追加します。Admin Areaメニューにて「Kubernetes」を選択し、「Add Kubernetes cluster」に続いて「Add existing cluster」タブをクリックし、次の情報を入力します。 Name:~/.kube/configファイルにあるクラスタ名を入力します。 Environment scope:「*」を入力します。 API URL:次のコマンドを実行してAPI URLを取得します。 kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}' CA Certificate:kubectl get secretsを実行します。リスト化されたsecretの1つに「default-token-xxxxx」に類似した名前があるはずです。そのトークン名をコピーし、次のコマンドに使用します。次のコマンドを実行して証明書を取得します。 kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode Token:GitLabでは、特定のネームスペースに対してスコープされるサービス・トークンを使用して、Kubernetesの認証を実施します。使用されるトークンは、クラスタ管理者権限を持つサービス・アカウントに属している必要があります。このサービス・アカウントを作成するには、次の手順を実行します。 次の内容にて、gitlab-admin-service-account.yamlという名前で、ファイルを作成します。 apiVersion: v1 kind: ServiceAccount metadata: name: gitlab-admin namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: gitlab-admin roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: gitlab-admin namespace: kube-system 次のコマンドを実行して、このサービス・アカウントとクラスタのロール・バインディングをクラスタに適用します。 kubectl apply -f gitlab-admin-service-account.yaml 結果は次のとおりです。 serviceaccount "gitlab-admin" created clusterrolebinding "gitlab-admin" created Gitlab管理者サービス・アカウント用のトークンを取り出します。 kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}') 「Add Kubernetes Cluster」をクリックします。インスタンス・クラスタが追加されます。 インスタンス・クラスタにて、次のコンポーネントをインストールします。 Helm Tiller Ingress Cert-Manager Prometheus GitLab Runner Ingressがインストールされると、新しいパブリックのOracle Cloud Infrastructureロードバランサがプロビジョニングされます。 UIに提示されているとおりにベース・ドメインをロードバランサのパブリックIPアドレスに変更し(例:OCI_Public_IP.nip.io)、「Save Changes」をクリックします。詳しくは、GitLabのドキュメントを参照してください。 以上です。これで、GitLab CI/CDパイプラインのインストールとContainer Engine for Kubernetesとの統合が完了しました。ここからは、GitLab CI/CDのワークフローのテストに移ります。 Oracle Cloud InfrastructureレジストリにてGitLab CI/CDパイプラインを使用 ここでは例として、GitLab CI/CDワークフローを使用して、プライベートOracle Cloud Infrastructureのレジストリ・リポジトリからイメージをプルし、これを再構築し、新しいビルド名にてレジストリにプッシュ・バックする方法を紹介します。 レジストリ・リポジトリにプライベート・イメージがある場合は、次のステップまでスキップしてください。プライベート・イメージがない場合は、Dockerイメージを作成してプライベート・レジストリ・リポジトリにこれをアップロードし、KubernetesのSecretを作成します(Secret情報には、レジストリのユーザー名とパスワードを使用します)。これらのステップについては、次のチュートリアルを参照してください。 Push an Image to Oracle Cloud Infrastructure Registry Pull an Image from Oracle Cloud Infrastructure Registry when Deploying a Load-Balanced Application to a Cluster 次の変数をGitLabに追加します。「Settings」 > 「CI/CD」の順に進み、「Variables」を開いて、次の変数のキーと値を追加します。 OKE_REGISTRY:レジストリ・リージョンのエンドポイント(例:iad.ocir.io) OKE_REGISTRY_IMAGE:レジストリ・イメージ(例:iad.ocir.io/tenancy_name/imagename:build) OKE_REGISTRY_NEW_IMAGE:ビルド・プロセスの後にレジストリにプッシュされる、レジストリ・イメージの新しいビルド・バージョン(例:iad.ocir.io/tenancy_name/imagename:new_image) OKE_REGISTRY_PASSWORD:Oracle Cloud Infrastructureの認証トークン詳しくは、こちらのドキュメントを参照してください。 OKE_REGISTRY_USER:Oracle_Cloud_Infrastructure_tenancy_name/usernam 内部のブランク・プロジェクトを作成します(例:gitlab-docker-ocir-oke)。 新しいファイルを2つ(「Dockerfile」と「.gitla-ci.yml」)作成します。これらは、GitLabをContainer Engine for Kubernetesおよびレジストリで運用するための基本的な例にすぎません。実際に作成する際は、必要に応じて修正してください。 .gitlab-ci.yml docker-build-master:   # Official docker image.   image: docker:latest   stage: build   services:     - name: docker:19.03.0-dind       entrypoint: ["env", "-u", "DOCKER_HOST"]       command: ["dockerd-entrypoint.sh"]   variables:     #CI_DEBUG_TRACE: "true"     DOCKER_HOST: tcp://localhost:2375/     DOCKER_DRIVER: overlay2     DOCKER_TLS_CERTDIR: ""   before_script:     - docker login -u "$OKE_REGISTRY_USER" -p "$OKE_REGISTRY_PASSWORD" $OKE_REGISTRY   script:     - docker info      - docker build  --pull -t "$OKE_REGISTRY_IMAGE" .     - docker tag "$OKE_REGISTRY_IMAGE" "$OKE_REGISTRY_NEW_IMAGE"     - docker push "$OKE_REGISTRY_NEW_IMAGE"   only:     - master   docker-build:   # Official docker image.   image: docker:latest   stage: build   services:     - docker:19.03.0-dind   before_script:     - docker login -u "$OKE_REGISTRY_USER" -p "$OKE_REGISTRY_PASSWORD" $OKE_REGISTRY   script:     - docker info     - docker build  --pull -t "$OKE_REGISTRY_IMAGE" .     - docker tag "$OKE_REGISTRY_IMAGE" "$OKE_REGISTRY_NEW_IMAGE"     - docker push "$OKE_REGISTRY_NEW_IMAGE"   except:     - master Dockerfile FROM nginx VOLUME /usr/share/nginx/html これらのファイルを作成し、保存すると、GitLab Auto DevOpsによりパイプラインがトリガーされます。これにより、レジストリ・イメージがプルされ、新しいイメージが作成され、名前「$OKE_REGISTRY_NEW_IMAGE」を使用してそのイメージがタグ付けされて、レジストリにプッシュされます。 レジストリ・ダッシュボードを更新すると、新しいイメージが表示されます。   セットアップとテストは以上です。これで、GitLab CI/CDパイプラインのデプロイとContainer Engine for Kubernetesおよびレジストリとの統合が完了しました。 まとめ Container Engine for KubernetesとGitLab CI/CDの統合ワークフローとを組み合わせることで、本番対応の堅牢かつ拡張性の高いDevOpsプラットフォームを獲得できます。詳細については、「Container Engine for Kubernetesの概要」およびGitlabのWebサイトを参照してください。Container Engine for KubernetesでのGitlabの運用を体験したい場合は、Oracle Cloud Infrastructureのアカウントを取得していただければ、すぐにテストを開始できます。 ※本記事は、Max Verun (Principal Product Manager)による"Using the GitLab CI/CD Pipelines Integrated Workflow To Build, Test, Deploy, and Monitor Your Code with Container Engine for Kubernetes and Registry"を翻訳したものです。

Gilson Melo Senior Principal Product Manager   オラクルのパブリック・クラウドは、オープン・ソース・テクノロジーとそうしたテクノロジーをサポートするコミュニティに対応しています。クラウド・ネイティブ・テクノロジーおよびDevOps手法へのシフトが加速するなか、当社は多くの企業や組織から、クラウド・プロバイダーによる構築か否かに関係なく、クラウドのロックインを...

Always Free

Oracle Databaseを始める5つの方法

Gerald Venzl Master Product Manager Oracle Databaseを触ってみたいけれど、どう始めたらいいか分からない?この記事で5つの方法をご紹介します。 LiveSQL ひとまず便利なSQLをいくつか作成して、データベースにそのとおり実行させたい、と思っていますか。そうであれば、LiveSQL.oracle.comがお役に立ちます。LiveSQLはブラウザベースのSQL作成ツールで、SQLのすばらしさをいくつか体験できるだけではなく、作成したスクリプトを保存して、他の人と共有することもできます。チュートリアルとサンプルの総合的なライブラリもあります。Oracle Databaseの使用経験がまったくなく、これから始めてみようとする人なら誰でも、LiveSQLを気に入るでしょう。 Dockerイメージ Oracle Databaseを自分のマシンに入れたいが、セットアップや構成に頭を悩ませるのは遠慮したいという場合は、オラクルが提供しているDockerイメージが便利です。自分のマシン(MacかWindows)にDockerをインストールし、オラクルのDocker GitHubリポジトリからイメージを1度作成すれば、残りの作業はDockerが引き受けてくれます。その後覚えておく必要があるのは、これだけです。 docker run -name oracle -p 1521:1521 oracle/database:19.3.0-ee と、 docker start oracle Dockerは、ご使用のマシン上でOracle Databaseの1つまたは多数のインスタンスおよびバージョンを実行するのに適しています。運用(開始/停止/セットアップ)方法に関する知識がなくても問題ありません。その場合でも、Oracle Databaseの全機能を使用できます。 Vagrant Box VM Oracle Databaseを自分のマシンに入れたいが、仮想マシンの内部で実行したいという場合は、オラクルが提供するVagrantスクリプトが非常に役立ちます。HashiCorpのVagrantは、VirtualBox VMなどのVM環境を繰り返しプロビジョニングする場合に便利です。まず、ご使用のマシンにオラクルのVirtualBoxとHashiCorpのVagrantをインストールします。インストールが完了したら、Oracle Vagrant Boxes GitHubリポジトリからスクリプトを使用してVMをプロビジョニングすれば、あとはVagrantがすべてやってくれます。その後覚えておく必要があるのは、これだけです。 vagrant up と、 vagrant ssh Oracle Databaseを含むVirtualBox VMをスクリプトで作成し、繰り返し使用したい場合は、Vagrant Boxが適しています。Oracle Databaseのさまざまなバージョンがインストールされた、複数のVMをプロビジョニングすることもできます。VMではポート転送がデフォルトで有効になっています。つまり、ご使用のホストから直接、どのツールにでも接続できます。たとえば、Oracle SQL DeveloperからVM内部のデータベースに接続して、VMを小さな組込みサーバーのように扱うことができます。 Database Application Development VM VMを使用したいが、Vagrantの複製機能は使いたくない、または使う必要がない場合は、Oracle Database Application Development VMがぴったりです。.ovaファイルをダウンロードし、VirtualBoxにインポートして、VMを起動するだけです。VMが起動して、Linuxのグラフィカル・デスクトップが表示されます。 Oracle Database Application Development VMには、SQL DeveloperやOracle REST Data Servicesなどのツールがあらかじめインストールされているため、VMにとって必要なものが1か所ですべてそろう、優れたツールであると言えます。こちらもデフォルトでポート転送が有効になっており、ホストから直接ツールに接続できます。Application Development VMのもう1つの利点は、ハンズオン・ラボがいくつか用意されていることです。これを利用して、ひととおり試してみることができます。 Always Free Oracle Autonomous Database Oracle Databaseを使いたいが、自分のラップトップ上に置きたくはないという場合は、Always Free Oracle Autonomous Databaseが含まれるOracle Cloud Free Tierを検討してみてください。Free Tierにサインアップし、Always Free Autonomous Databaseをプロビジョニングすれば、Oracle SQL Developer Webを使用できるようになります。 オラクルによる最新かつ最高のクラウド・データベースを体験したいならば、Always Free Oracle Autonomous Databaseがベストな選択肢です。SQL Developer WebとOracle Application Express(APEX)がすぐに使えるようになっているため、インターネット・アクセスさえあれば、世界中のどこからでも、他のアプリやIDEに接続できます。そして最高にすばらしいのは、ユーザーがデータベースを必要とする限り、無期限で利用できることです。 さあ、手をこまねいている時間はありません。Oracle Databaseをさっそく使ってみてください! ※本記事は、Gerald Venzl (Master Product Manager)による"5 ways to get an Oracle Database"を翻訳したものです。

Gerald Venzl Master Product Manager Oracle Databaseを触ってみたいけれど、どう始めたらいいか分からない?この記事で5つの方法をご紹介します。 LiveSQL ひとまず便利なSQLをいくつか作成して、データベースにそのとおり実行させたい、と思っていますか。そうであれば、LiveSQL.oracle.comがお役に立ちます。LiveSQLはブラウザベースのSQ...

Java Magazine February 2020

  2020年2月 Java 13のswitch式と再実装されたSocket APIの内側 著者:Raoul-Gabriel Urma、Richard Warburton 段階的変更により、将来のメリットが今回のリリースに含まれるように   Javaにテキスト・ブロックが登場 著者:Mala Gupta 長く待ち望まれてきた複数行文字列がJava 13で実現   言語の内側:シールド型 著者:Ben Evans パターン・マッチング、列挙型およびswitch文の改善に向けて進化するJava TeaVMを使ってブラウザでJavaを動かす 著者:Andrew Oliver Javaをフロントエンドとバックエンドの両方に使ってWebアプリを構築する   ツールをよく知る 著者:Andrew Binstock 偉大なプログラマーは皆、主要ツールについての深い知識を持っている。ツールのエキスパートでないなら、必要な時間をかけること。その手はじめはここから   クイズに挑戦:1次元配列(中級者向け) 著者:Simon Roberts、Mikalai Zaikin コンストラクタを使って配列を作成する際の微妙な違い   クイズに挑戦:カスタム例外(上級者向け) 著者:Simon Roberts、Mikalai Zaikin 例外の宣言が必要になる正確な条件   クイズに挑戦:ロケールの読取りと設定(上級者向け) 著者:Simon Roberts、Mikalai Zaikin ユーザーの満足のために、正確なロケール設定を細   クイズに挑戦:関数型インタフェース(上級者向け) 著者:Simon Roberts、Mikalai Zaikin ストリームにおけるボクシングおよびアンボクシングの難解さ

  2020年2月 Java 13のswitch式と再実装されたSocket APIの内側 著者:Raoul-Gabriel Urma、Richard Warburton 段階的変更により、将来のメリットが今回のリリースに含まれるように   Javaにテキスト・ブロックが登場 著者:Mala Gupta 長く待ち望まれてきた複数行文字列がJava 13で実現   言語の内側:シールド型 著者:Ben...

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

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 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 この設問では、プリミティブ版の関数型インタフェースを使うコードを作成したいと思います。 次のコードについて: DoubleStream ds = DoubleStream.of(1.0, 2.0, 3.0); … // line n1 System.out.print(ds.map(fun.apply(1.0)).sum()); line n1に追加することで、ストリームがもっとも効率よく処理され、コンソールに 9.0 が出力されるコードはどれですか。1つ選んでください。 Function<Double, DoubleUnaryOperator> fun = a -> d -> d + a; DoubleFunction<DoubleUnaryOperator> fun = a -> d -> d + a; DoubleFunction<DoubleFunction<Double>> fun = a -> d -> d + a; Function<Double, DoubleFunction<Double>> fun = a -> d -> d + a; 解答:この設問では、複数のトピックを扱っています。1つはプリミティブ・データタイプとJavaのジェネリクス・メカニズムとの相互作用、もう1つは複雑な関数型どうしのマッチングです。  まずは、最初のトピックの概要を見ていくところから始めます。Javaのジェネリクス・メカニズムは、オブジェクト型にのみ対応しています。すなわち、任意のSomethingについてList<Something>を定義できるのは、Somethingが何らかのObject型である場合に限られます。List<int>や、他のプリミティブタイプのListを定義することはできません。 この不便さを緩和するために、Javaのラッパー型を使うことができます。そのため、たとえばList<Integer>というような宣言を行うことができます。さらに、このようにジェネリックを扱う場合のほとんどで(ただし、常にというわけではありません)コンパイラのオートボクシングとアンボクシングの機能が働くため、オブジェクトとプリミティブとの相互変換を行うコードを明示的に書かずにプリミティブタイプを処理することができます。したがって、次のコードは問題なく動作します。 List<Integer> li = new ArrayList<>(); li.add(99); int ninetyNine = li.get(0); しかし、実際にコンパイラが生成するのは、次の形式と同等のコードです。 List<Integer> li = new ArrayList<>(); li.add(Integer.valueOf(99)); int ninetyNine = ((Integer)li.get(0)).intValue(); この2番目の形式には、見えないCPU負荷がかなりかかっている点に注意してください。 ここでは、Integerオブジェクトの生成と初期化(または、これまでに生成されたオブジェクトのプールから既存のIntegerを探す作業)が行われています。さらに、キャストも行われています。このキャストは、実際にはget(0)の戻り値に対して行われ、intValueメソッドを呼び出してプリミティブの結果を抽出しています。 通常、このCPUオーバーヘッド全体は、ソースコードの可読性(およびその結果としての保守性)改善との引き換えとなる妥当なトレードオフです。ただし、このようなトレードオフが発生すると知っておくことは重要です。パフォーマンスの低下が度重なり、このトレードオフを許容できないこともあるからです。いずれにせよ、パフォーマンスの低下が許容範囲である場合でも、この処理は常に、設問の言葉を使うなら、「あまり効率的でない」と言えます。 次に、Listなどのデータ構造を定義する場合、このボクシングおよびアンボクシングの処理を避けるのは結構難しく、通常はトレードオフが生じるだけの価値はあります。ただし、大量の計算を行っている場合、ジェネリクスではプリミティブを扱えないという制限に直面する可能性もあります。1つの入力を受け取り、1つの結果を生成する処理を定義したい場合を考えてみます。これは一般的なjava.util.function.Function<E,F>であり、操作の入力がE型、結果がF型です。ここで、この操作を使ってdouble値に対する計算を行いたいものとします。次のようなコードを試されるかもしれません。 Function<Double, Double> fdd = in -> in + 2.0; 残念ながら、これで作成されたのはDoubleオブジェクトを受け取る関数です。この関数からコンパイラは、2.0を加算する前にラッパー・オブジェクトからdoubleプリミティブを抽出するコードを生成し、その結果に対してDoubleオブジェクト・ラッパーを適用するコードを生成します。このような変換によるオーバーヘッドは、単純にdoubleプリミティブに2.0を加算する処理に比べれば膨大です。この変換機能が繰り返し使われた場合、トレードオフを許容できなくなる可能性ははるかに高くなります。 以上の制限を踏まえ、こういったボクシングおよびアンボクシングを一切行わないことで効率向上を実現するという明確な目標のもと、関数型インタフェースとStream API(およびJava 8における関数型機能のその他の要素)では、直接プリミティブを扱う特別なバージョンを提供しています。 先ほどの例に関連する機能を以下に示します。 DoubleStream:doubleプリミティブのデータを扱うことを目的としたストリーム概念 java.util.function.DoubleFunction<E>:1つのdoubleプリミティブ引数を受け取り、E型のオブジェクトを返すメソッドを定義する関数型インタフェース java.util.function.DoubleUnaryOperator:1つのdoubleプリミティブ引数を受け取り、doubleプリミティブの結果を返すメソッドを定義する関数型インタフェース これに類する機能は他にも多く提供されていますが、この設問に直接関係するのは、上に挙げた3つです。 設問の最初の重要な要素を満たすためには、ボクシングとアンボクシングを回避するか、または少なくとも最低限にとどめる答えを見つける必要があります。ボクシングとアンボクシングの処理が行われることで、設問で要求されている、効率が低下するからです。与えられたコードでは、DoubleStreamを直接作成しています。DoubleStreamはプリミティブ版のストリームであり、double値を直接扱うことから、効率に関する条件は満たされています。したがって、以降の処理で、値がプリミティブのまま扱われることを確認しなければなりません。そのためには、プリミティブを扱う関数型インタフェースが使われていることを確認する必要があります。 その他の背景についても考えてみます。すべての選択肢で、変数funを定義しようとしていることがわかります。このfunは、ds.map(fun.apply(1.0)).sum()という式のストリーム処理で使われています。 map操作では、上流ストリームの型の各項目を受け取り、指定された処理操作(これは1つの結果を生成する必要があります)を実行して、その処理操作が返す型のストリームを生成します。ここから、操作に必要ないくつかの要件を特定できます。さらにそこから、funの型と動作に必要な要件を特定できます。 funは、マップに適用される操作ではない点に注意してください。そうではなく、式fun.apply(1.0)を評価すると、map操作によって実行される操作が作成されます。言い換えるなら、fun自体は操作ではなく、動作のファクトリのようなものです。 ここではわかりやすさを優先し、mapが使うこの動作を単に「操作」と呼ぶことにします。この操作では、入力としてdoubleプリミティブを受け取り、結果としてdoubleプリミティブを生成しなければなりません。どうすればこれを実現できるでしょうか。純粋に論理的に推論すれば、map操作の入力と、上流ストリームの型との間に代入互換性がなければならないことがわかります。今回の場合、上流ストリームの型はdoubleです。そのため、doubleは動作し、Doubleもおそらく動作しますが、Doubleにはオートボクシングが必要となり非効率であるため、使うべきではありません。 それでは、下流ストリームの型はどうでしょうか。map操作では常に新しいストリームを生成します。また、sum()操作はプリミティブ・ストリームのみに存在します。いずれにせよ、Automobileオブジェクトのリストを足し合わせて1つのAutomobileを表す結果を生成することはできません。したがって、結果の値はプリミティブタイプでなければならず、doubleが自明の選択であることがわかります。実際には、APIを使ってdoubleプリミティブのストリームをintプリミティブやlongプリミティブのストリームにマップすることはできますが、この変更を行うためには、mapToIntメソッドやmapToLongメソッドが必要です。そのため、明らかにこの型であるという十分な確信を持てず、「設問に他の選択肢がないことからdoubleである」という推測に満足しない場合でも、前述の事実から確実にわかります。 つまり、操作の型は、1つのdouble引数を受け取り、doubleの結果を返すものでなければなりません。これは、DoubleUnaryOperatorインタフェースで定義されている動作です。 ここで、APIを詳細まで理解している方なら、DoubleStreamに対するmapの引数の型はDoubleUnaryOperator以外にないことはご存じでしょう(この設問では、このことを知っていなくても、論理的に考えればわかります)。しかし、重要なことは、これがオートボクシングが対処しない状況の1つであることです。Function<Double, Double>、DoubleFunction<Double>、ToDoubleFunction<Double>はいずれもDoubleUnaryOperatorとの互換性があるはずだと思うかもしれませんが、そうではありません。ここでは、DoubleUnaryOperatorを指定しなければなりません。  それでは、funの型はどうでしょうか。その型が何であれ、double引数(またはオートボクシングが適用されるDoubleも考えられますが、このバージョンは可能であれば避けたいということはわかっています)を受け取り、map操作で使われるDoubleUnaryOperatorを返す動作が実現されていなければなりません。 入力としてdoubleプリミティブを受け取り、オブジェクト型を返す関数はDoubleFunction<E>と呼ばれています。Eは戻りタイプを表します(逆に、引数としてEを受け取り、doubleプリミティブを返す関数はToDoubleFunction<E>です)。これを考えれば、funを実現する効率的な型はDoubleFunction<DoubleUnaryOperator>であることがわかります。この型は、選択肢Bで宣言されているものと一致しています。そこで、この選択肢の動作と、適切な出力が行われるかどうかを考えてみます。 選択肢Bにあるfunの定義のもとでfun.apply(1.0)を呼び出すと、double引数を受け取ってその引数に1を加算したものを返す関数が作成されます。このコードを実行すると、map操作の結果はストリーム・データ2.0、3.0、4.0(対応する入力のそれぞれに1を加算した数字)になり、その合計が要件どおりの9.0になることがわかります。この宣言が最大限に効率化されていることはわかっているため、選択肢Bが正解だと判断して差し支えありません。しかし念のため、他の選択肢はうまく動作しないか、または効率が低いことを確認してみます。 選択肢Aのfunでは、同じ論理計算が宣言されていますが、最初の入力にdoubleプリミティブではなくDoubleオブジェクトを受け取ると宣言されています。結果の関数が、引数に1.0を加算するDoubleUnaryOperatorである点は変わりません。そのため、コードはコンパイルでき、結果も9.0になります。ただし、ボクシング操作によって選択肢Bよりも効率が低いことは明らかであるため、選択肢Aは誤りです。 選択肢Cと選択肢Dではいずれも、DoubleFunction<Double>に関して定義されている操作のファクトリを定義しています。DoubleFunction<Double>は、引数としてdoubleプリミティブを受け取り、結果としてDoubleオブジェクトを返す関数を宣言します。そのため、これはコンパイルできると考える方もいらっしゃるかもしれません。しかし、たとえコンパイルできたとしても、選択肢Bよりも効率が低いことは明らかです。そのため、この段階でいずれの選択肢も誤りであることがわかります。ただし前述のように、オートボクシングのメカニズムでは、doubleとDoubleを相互に変換することはできますが、引数としてDoubleを受け取る関数を、引数としてdoubleを受け取る関数に変換することはできません。これは、単なるボクシングおよびアンボクシングの操作ではなく、関数型の変換です。その結果、選択肢CおよびDはコンパイルできないため、いずれも誤りです。 正解は選択肢Bです。   Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) 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 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験...

クイズに挑戦:ロケールの読取りと設定(上級者向け)

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 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 この設問では、Localeオブジェクトを使用してロケールの読取りと設定を行いたいと思います。  次のうち、正しい選択肢はどれですか。1つ選んでください。 Localeは国と言語の両方を表すことができる Localeを作成するとき、国は必ず指定しなければならない 言語は必ず2文字で表される Localeクラスでは、Localeの新しいインスタンスを作成するのではなく、事前定義されたstatic final定数を常に使うことができる 解答:java.util.Localeクラスでは、地理的、言語的、政治的、および文化的な好みの設定を表しますが、こういった側面のすべてが必要となるわけではありません。ユーザーのニーズと好みの設定に応じて、ソフトウェアでは最適なものを提供できるようにするという考え方です。 たとえば、大半のユーザーは、ユーザー・インタフェースが第一言語または少なくとも自分が理解する言語で提示されることは絶対要件だと考えるでしょう。しかし、日付の年月日の要素が、ユーザーの国で一般的に使われる形式とは違う順序で表示されたとしても、そのソフトウェアを使おうと思ってくれるかもしれません。 その点を考えれば、JavaのLocaleオブジェクトの要素には優先順位が付けられており、言語の優先順位がもっとも高く、地域の優先順位は言語よりも低くなっている点に驚くことはないでしょう。 Localeクラスには、3つのコンストラクタがあります。1つ目は言語のみ、2つ目は言語および国、そして3つ目は、言語および国に加えてvariant要素が必須になっています。コンストラクタを以下に示します。 Locale(String language) Locale(String language, String country) Locale(String language, String country, String variant) ここまでの説明で、2つの重要なポイントに注意してください。 まず、Localeでは言語および国の両方(とさらに別の情報)を表すことができるという点です。そのため、選択肢Aは正解です。 次に、言語は優先順位がもっとも高い要素で、どのコンストラクタでも省略できないものの、地域は二次的要素であり省略できるという点です。Localeの性質を理解すれば(API機能についての知識も参考になるでしょう)、選択肢Bは誤りであることがわかります。 通常、言語は小文字2文字を使って表されます。しかし、標準(厳密に言えば、IETF BCP 47およびISO 639)とAPIドキュメントには、3文字も可能であることが記されています。3文字タグは珍しいですが、APIドキュメントではkok(コンカニ語)が例として挙げられています。さらに、サブタグでは8文字まで拡張することもできます。以上より、選択肢Cは誤りであることがわかります。 コンカニ語は、インドで約700万人が話している言語です。 設問に含まれるこの要素が、知識の丸暗記という、かなり疑わしいカテゴリに入るものであることには注目すべきです。私たちも、試験の作成者も、このような設問は好みません。率直に言えば、このトピックについての設問を、単なる事実に基づかずに作成するのはかなり難しいことです。しかし、Localeの性質と使用方法について十分実務的に理解していれば、即座に完全な自信を持って選択肢Aを選ぶことができ、他の選択肢について悩む必要はないはずです。事実そのものについての知識が必要と思われるさまざまな状況で、このようなことはよくあります。 Java 8のリリースによって、コンストラクタをパブリックAPIの一部として公開することは、多くの場合、あまりよい方法ではないという認識が広がりました。コンストラクタはオブジェクトを作成する際に必要であり、クラスの継承を使いたい場合は、privateでないコンストラクタが必要になります。しかし、パブリックAPIの一部として考えた場合、一般的に言って、コンストラクタをお勧めできないいくつかの理由があります。 その理由の1つに、newの呼出しによって、新しいオブジェクトまたは例外のいずれかしか得られないことが挙げられます。つまり、プールの中にあるオブジェクトを渡すことはできません。現在、Java(または参照ベースの任意の言語)で可変オブジェクトをプールすることは、ほとんどすべての場合において適切ではありません。しかし、可能な値がほどよい範囲である不変オブジェクトをプールしておくことは合理的で、ますます一般的なAPI機能になっています(「実行時に拡張可能な列挙型」を考えてみてください)。 不変オブジェクトの不必要な複製を最低限に抑える方法の1つとして、事前定義された定数を提供するというものがあります。Localeクラスでは、およそ8つの国およびそのバリエーションのいくつかについて、この方法がとられています。しかし、対象となっているすべての国からはほど遠く、すべての組合せにはとても及びません。そのため、選択肢Dは誤りです。 正解は選択肢Aです。   Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) 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: Read and Set the Locale (Advanced)“を翻訳したものです。

ユーザーの満足のために、正確なロケール設定を 著者:Simon Roberts、Mikalai Zaikin 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類で...

クイズに挑戦:カスタム例外(上級者向け)

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 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 この設問では、カスタム例外を作成したいと思います。 次のコードについて: class UnknownException extends Error {} class BusinessException1 extends Exception {} class BusinessException2 extends Exception {} class Application { public Object doId() /* point n1 */ { // line n2 } } 次に示す一連の追加を行った場合(それぞれの追加は独立して行うものとします)、正常にコンパイルできるのはどれですか。3つ選んでください。 point n1に:throws BusinessException2 line n2に:return "Hi"; point n1に:何も追加しない line n2に:throw new UnknownException() point n1に:throws Exception line n2に:throw new BusinessException1() point n1に:throws BusinessException1 line n2に:throw new BusinessException2() point n1に:throws BusinessException1 line n2に:throw new Exception() 解答:Javaでは、例外をチェック例外および非チェック例外という2つの大きなカテゴリに分類しています。ただし、JVMでは異なります(後で補足として説明します)。非チェックのカテゴリはさらに大きく2つに分けることができますが、設問とは無関係であることから、その点についてここでは触れません。 説明に補足を入れるのは少し早いですが、これは厄介な問題であるため、早く片付けてしまいます。「JVMでは異なります」というただし書きはどういう意味でしょうか。最初に言っておきますが、気にしないのであれば知る必要はありません。ただし、背景知識としては興味深いものかもしれません。Javaプログラムでは、チェック例外をスローする可能性があるコードを含むメソッドは、その事実を宣言しなければなりません。また、Java言語のルールのほとんど(アクセス可能性や代入の互換性など)は、クラス・ローダーがバイトコードに強制しています。しかし、チェック例外のルールは違います。バイトコードから見れば、すべての例外は等しいもので、どの例外でもいつでも宣言なしにスローすることができます。 java.lang.Throwableクラスは、スロー可能なもののベース・クラスで、java.lang.Errorおよびjava.lang.Exceptionという2つのサブクラスがあります。Exceptionには、java.lang.RuntimeExceptionというサブクラスがあります。Javaでは、ErrorでもRuntimeExceptionでもないすべてのThrowableはチェック例外です。 つまり、この2つのクラスとそのサブクラスは非チェック例外で、それ以外のすべてはチェック例外です。以上を踏まえると、UnknownExceptionはErrorのサブクラスであることから非チェック例外であり、BusinessException1とBusinessException2はいずれもチェック例外であることがわかります。 ところで、Javaでは、非チェック例外をスローすることについて、何の制約も課していません。つまり、非チェック例外は、メソッドがその非チェック例外をスローする可能性があるかないかにかかわらず、また、非チェック例外のスローが可能であるかないかにかかわらず、宣言しても宣言しなくても構いません。 対照的に、メソッドがコール元にチェック例外をスローできるのは、throws句でその可能性を宣言している場合に限られます。メソッドのthrows句では、スローされる可能性がある実際の例外の親を宣言するだけで十分です。子の例外は親の例外型のインスタンスであるからです。  さらに、特定の例外を列挙するthrows句は、単にそのメソッドがその例外をスローできるようにしているだけで、必ずそのメソッドがその例外をスローしなければならないということではありません。ほとんどの例外は特別な状況でスローされることから、例外が発生しない可能性もあることは明らかです。しかし、メソッドでその例外がスローされる可能性がまったくなくても構いません。例外の宣言は、将来的な変更のためや、オーバーライドするメソッドがその例外をスローできるようにするために行うことも可能です。 それでは、選択肢を確認していきます。 選択肢Aでは、チェック例外を指定したthrows句を宣言していますが、メソッドの本体でその例外がスローされる可能性はありません。先ほど触れたように、これは問題ないため、選択肢Aは正解です。 選択肢Bでは、UnknownExceptionをスローしています。これは実際にはErrorのサブクラスです(あまりよい名前ではありません)。しかし、ここでさらに重要なのは、この例外が非チェック例外であることです。 非チェック例外であるため、throws句で宣言されていてもいなくても問題ありません。つまり、選択肢Bも正解です。 選択肢Cでは、特定のチェック例外BusinessException1をスローしていますが、Exceptionをスローすると宣言しています。前述のように、BusinessException1はExceptionである(is-a関係)ため、これは問題ありません。なお、選択肢AおよびBとは異なり、この場合はthrows句を省略することはできません。したがって、選択肢Cも正解です。 選択肢Dではチェック例外BusinessException2をスローしていますが、BusinessException1をスローすると宣言しています。このthrows句は問題ありませんが、兄弟関係にあたる例外をスローできるようにするためには、これでは不十分です。十分簡単なことですが、ここで問題になるのは、2つのBusinessException型に、似たような名前と共通の親を持つということ以外には何の関係もないことです。そのため、この場合、メソッドはthrows句で宣言されていないチェック例外をスローすることになり、選択肢Dは誤りです。 選択肢Eも、選択肢Dと本質的に同じ理由で失敗します。すべてのBusinessException1はExceptionです(is-a関係)が、継承関係は一方向であるため、ExceptionはBusinessException1ではありません。したがって、スローされる可能性がある例外に対してthrows句が不十分であるため、選択肢Eは誤りです。 正解は選択肢A、B、Cです。   Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) 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: Custom Exceptions (Advanced)“を翻訳したものです。

例外の宣言が必要になる正確な条件 著者:Simon Roberts、Mikalai Zaikin 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、...

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

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 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 この設問では、1次元配列を宣言してインスタンス化し、初期化して使用したいと思います。 次のコード・ブロックについて: int[] ia = new int[2]; ia[1] = 1; for (int v : ia) System.out.print(v + " "); String[] sa = new String[2]; sa[0] = "Hi"; for (String v : sa) System.out.print(v + " "); // line n1 どのような出力になりますか。1つ選んでください。 0 1 Hi null null 1 Hi null 0 1 0 Hi null null null 1 null Hi null null 0 1 Hiの後、line n1で例外がスローされる 解答:この設問は、配列の作成と初期化について問うものです。この例では1次元配列が使われていますが、多次元配列(同様に試験対象です)も、本質的には配列の配列にすぎません。そのため、細かいことはいくつか加わりますが、ここでの議論は多次元配列にも基本的には当てはまります。 配列は、2つの方法で作成できます。具体的には、newキーワードを使ったコンストラクタ形式か、または配列リテラルを使うことができます。この設問では、コンストラクタ形式が使われています。この形式では、型の後の角括弧内に、配列の次元を指定する必要があります。 この方法を使う場合、配列に入れる値を同時に指定することはできません。 リテラル形式は波括弧を使うものであり、配列のサイズが小さい場合は特にですが、簡潔な記述となります。この方法では、同時に要素を明示的に初期化することが可能であり、実際には必要になります。 配列リテラルには、2つの構文形式が存在します。もっとも一般的なのは、次のようなものです。 new int[] {1, 1} ここで示されている一般的な形式は、「intの配列」型の式であり、その型の式が必要な、任意の状況で使用できます。そのため、たとえばメソッド呼出しの実パラメータ・リストで使用できます。次に例を示します。 doStuffWithIntArray(new int[] {1, 1}); 次のような、単純な代入にも使用できます。 int[] ia; ia = new int[] {1, 1}; 上記以外の状況で使用することもできます。 この形式では、(コンストラクタ形式とは違って)角括弧内に配列サイズを含めないことに注意してください。配列のサイズは、初期化する値のリストから推論されます。 2番目の形式の方がおなじみかもしれません。実のところ、この形式は、初期化済みの変数を宣言する際の、値の部分にのみ許可されている省略表記です。この形式を使用できるのは、次のような場合に限られます。 int[] ia = { 1, 1 }; もう1つ注意事項を挙げておきます。変数宣言では、変数名の後に角括弧を記述することも許可されています。しかし、この順序での構文はあまり一般的ではありません。宣言の「型」部分が左側にあり、「名前」の部分が右側にある方が大抵好まれているからです。しかしながら、CやC++というJavaの先駆者で使われてきたものであるため、この形式が思い浮かぶという方もいるかもしれません。次にこの形式の例を示します。 int ia[]; 設問に戻ります。今回の設問では、リテラル形式は使われていません。どちらの配列も、コンストラクタ形式を使って作成されています。そのため、生成処理の一部として要素の値を指定することはできず、表現したい特定の値は後で割り当てる必要があります。 もちろん、この設問で行われている明示的な代入では、配列のすべての値が設定されるわけではありません。設問の核心は、配列のうちで明示的な代入が行われていない要素には何の値が入っているかという点です。 実は、この「デフォルトの値は何ですか」という問いは、配列にとどまらず、一般的な状況にも当てはまります。ヒープに割り当てられるすべてのものは、デフォルトの初期化の対象となります。配列であれ、(もっとはっきり言えば)オブジェクトであれ、すべてのオブジェクトがこの対象に含まれます(ここでのポイントは、Javaの配列はオブジェクトであるということです)。このデフォルトの初期化は、メモリ割当てと切り離すことができないものです。コードからヒープに新しいオブジェクトを作成する場合、JVMはデフォルトの初期化が行われたメモリか、例外のいずれかを返します。このデフォルトの初期化が行われていない値を取得することはできません。 それでは、デフォルトの初期化とは何でしょうか。答えは簡単で、ゼロまたはゼロに似た値です。数値型とcharではゼロ、boolean値ではfalse、その他のオブジェクト参照値では、すべてNULLになります。 したがって、new int[2]では、2つのintプリミティブ値の配列の領域が確保されます。そのインデックス(添字と呼ばれることもあります)は0と1(Javaの配列インデックスは、常にゼロベースです)になり、両方の要素に値0が設定されます。そのため、この時点で、ia[0] == 0かつia[1] == 0となります。 次の行では、2つ目の要素(当然、添字は1になります)に値1が代入され、1つ目の要素はデフォルト値0のままになります。 次に、単純なループを使ってint配列の値を出力しています。2つの値、0と1があることはすでに確認しました。そのため、コードのこの部分からの出力は0 1となります(メソッド呼出しがprintlnではなくprintであることに注意してください)。 2つ目の配列も、int配列と同じように宣言され、インスタンス化されます。ここでも、1つの要素が明示的に初期化され、もう1つにはデフォルト値が含まれます。重要な違いは、この配列がオブジェクト型(具体的にはString)であるため、デフォルト値がNULLになることです。そのため、最初の状態でsa[0] == nullかつsa[1] == nullとなります。次に、添字が0の要素が文字列Hiを参照するように代入が行われます。したがって、このコードのループ部分では、Hi nullと出力されます。 以上より、選択肢Aが正解であることがわかります。 int型の配列ではなく、Integerの配列が作成されていた場合、選択肢Bが正解だったでしょう。Integerはオブジェクトで、その配列のデフォルト値はやはりNULLポインタになるからです。しかし、そうではなかったため、選択肢Bは誤りです。 選択肢Cか選択肢Dを選びたくなったのは、new Blah[x]で作成された配列には、添字が0からx-1までのx個の要素ではなく、添字が0からxまでのx+1個の要素があると考えた方かもしれません。 選択肢Eを選びたくなったのは、初期化されていない文字列にアクセスすると例外が発生するだろうと考えた方かもしれません。そう考えるのは、必ずしもおかしなことではありません。興味深いかもしれないのは、式"String is " + sa[1]を評価するとString is nullという出力が得られる一方で、"String is " + sa[1].toString()を評価すると、実はNullPointerExceptionが発生することです。いずれにしても、sa[1]の値はNULLであり、NULLを使ってオブジェクトを参照しようとすると、必ず失敗します。ただし、文字列の連結ではこの問題が回避され、例外がスローされるのではなく、出力nullが得られます。 正解は選択肢Aです。   Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) 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: One-Dimensional Arrays (Intermediate)“を翻訳したものです。

コンストラクタを使って配列を作成する際の微妙な違い 著者:Simon Roberts、Mikalai Zaikin 2019年10月4日 その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による...

ツールをよく知る

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; } 偉大なプログラマーは皆、主要ツールについての深い知識を持っている。ツールのエキスパートでないなら、必要な時間をかけること。その手はじめはここから 著者:Andrew Binstock 2019年10月16日 私の仕事でよいところの1つは、偉大な開発者や真のドメイン・エキスパート、テクニカル・イノベーター、そして「魔法使い」に出会えることです。そういった人々と話す中で、偉大なプログラマーを偉大たらしめているものは何かについて、たびたび議論することができました。偉大なプログラマーを観察してわかる特性は、「コードがきれいでたくさんのテストを書く」という安直な答えで表せるものではなく、高く評価されることがほとんどないものです。つまり、真に大成するためには、疑問点を表面だけでなく、深いところまで徹底的に調査することが必要だということがわかります。 私が個人的にエキスパートを観察してわかったのは、偉大な開発者はいくつかの特性を共有しているということです。その特性を(順不同で)挙げてみます。解決しようとしている問題を完全に理解する辛抱強さ。頭の中に多くの情報を同時に蓄えておく能力。小規模なものから大規模なものにすばやく移り、再び戻る能力。そして最後に、使っている主なツールについての深い知識です。こういった特性のうち、最初と最後は自分で簡単に身につけることができるものです。その他の2つは、どちらかといえば生来の能力に近いものです。しかしもちろん、訓練や懸命な努力によって獲得することもできます。 ツールについての深い知識を得るのは、20年前よりも現在の方が難しくなっています。現在のフルスタック開発者は、数十種類のツールを恒常的に使っているかもしれません。そのため、すべてを細部までマスターするというのは現実的でなくなっています。しかし、私が熟慮したうえで指摘しておきたいのは、主なツールは完全に使いこなせるようになっている必要があるということです。 基本的なことから考えてみましょう。皆さんは完全なブラインド・タッチができるでしょうか。自分の手を見ずにタイピングができないなら、徐々に自分を大変不利な立場に追いやっていることになります。仕事が遅くなるだけではありません。非常に認識しづらい形で、タイピングがさらに必要なちょっとしたことを避けたくなるはずです。たとえば、コードが数行では収まらないことを試そうとは思わず、繰り返し行っているタスクについて、ちょっとしたスクリプトを書いて自動化しようとも思わないでしょう。たとえタイピングが不完全であっても(皆さんは手元を見ずにすべての数字をタイプできるでしょうか。!=などはどうでしょう)、筋肉の記憶を鍛えるために使った時間は、スピードと、キーボードではなく画面だけに集中できる能力という形で報われるはずです。 次に、ソフトウェア・ツールについて考えてみましょう。皆さんは、エディタやIDEのことをどのくらい知っているでしょうか。多くの開発者は、頻繁に使ういくつかのコマンドは使いこなせますが、その他のコマンドはそうではありません。コードを入力してコンパイルし、テストを実行するという枠を越える場合、メニューやマウスを使わざるを得なくなります。こういった限定的な知識には、不完全なタイピングと同じような欠点があります。すなわち、仕事が遅くなり、新しいことを試すのを目には見えない形で避けたくなります。また、簡単なタスクを完了しようとする場合も、絶えず小さな中断に見舞われます。 次号には、『The Pragmatic Programmer』の書評を掲載する予定です。   エディタやIDEを十分使いこなせるとは、どういうことでしょうか。『The Pragmatic Programmer』の20周年記念版で、David Thomas氏とAndrew Hunt氏は、真の意味で開発環境を使いこなすために必要と感じているものについて述べています。皆さんは、以下のことをすべてキーボードから行うことができるでしょうか。 カーソルを動かし、文字や単語、行、段落単位で選択する 対応するデリミタ、関数、モジュールなど、構文単位でカーソルを動かす 変更を行った後にコードのインデントを調整する コード・ブロックを1回のコマンドでコメント化およびコメント解除する 変更を元に戻す、やり直す エディタ・ウィンドウを複数のパネルに分割し、その間を移動する 特定の行番号に移動する 選択した行を並べ替える 文字列と正規表現の両方を検索し、以前の検索を繰り返す 一時的に複数のカーソルを作成し、それぞれのカーソルでテキストを編集する 現在のプロジェクトのコンパイル・エラーを表示する 現在のプロジェクトのテストを実行する 私がこのリストに付け加えたいと思っているのは、コードをコミットしてセントラル・リポジトリにプッシュすることができる、コードをコンパイルすることができる、デバッガで問題のあるコードをステップ実行することができるという点です。 いずれかで「No」と答えた方も、大丈夫です(私も確実に含まれますが、そのような人はたくさんいます)。しかし、偉大なプログラマーの大多数は、こういったことのすべて(またはそのほとんど)を簡単に行う方法を知っているものです。Thomas氏とHunt氏は、必要なスキルを獲得できる方法を示しています。その中でもっとも重要なのは、「マウスに触れない」ということです。こうすることで、調べざるを得なくなり、コマンドも身につきます。 また、両氏はさらにその先の提案も行っています。エディタやIDEのすぐ外側で行っているタスクを見つけ、そのタスクを自動化するか、または容易にするプラグインを探すことです。こうすることで、主なツールに関する専門知識が増え、さらに多くの作業に対応できるようになります。 偉大なプログラマーとペアプログラミングを行ったことがある方なら、そういった人のスピードや器用さを実際に目の当たりにしているはずです。コードやテストを書いてすぐに、ウィンドウを開いてメモをとったりユーティリティを実行したりしてリファクタリングを行います。これを見るのは楽しいことです(そして実際、さまざまなソーシャル・メディアで見ることができます)。そのような人を目指しましょう。重要なのはツールを知ることです。   Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) Andrew Binstock Andrew Binstock(javamag_us@oracle.com、@platypusguy):Java Magazine編集長。Dr. Dobb's Journalの元編集長。オープンソースのiText PDFライブラリを扱う会社(2015年に他社によって買収)の共同創業者。16刷を経て、現在もロング・テールとして扱われている、Cでのアルゴリズム実装についての書籍を執筆。以前はUNIX Reviewで編集長を務め、その前にはC Gazetteを創刊し編集長を務めた。 妻とともにシリコン・バレーに住んでおり、コーディングや編集をしていないときは、ピアノを学んでいる。 ※本記事は、Andrew Binstockによる”Really Know Your Tools“を翻訳したものです。

偉大なプログラマーは皆、主要ツールについての深い知識を持っている。ツールのエキスパートでないなら、必要な時間をかけること。その手はじめはここから 著者:Andrew Binstock 2019年10月16日 私の仕事でよいところの1つは、偉大な開発者や真のドメイン・エキスパート、テクニカル・イノベーター、そして「魔法使い」に出会えることです。そういった人々と話す中で、偉大なプログラマーを偉大たらしめてい...

TeaVMを使ってブラウザでJavaを動かす

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; } Javaをフロントエンドとバックエンドの両方に使ってWebアプリを構築する 著者:Andrew Oliver 2019年10月8日 Javaがブラウザの中で実行されていた頃を覚えている方もいるかもしれません。その頃は、Webページから「Pure Java」のUIを簡単に起動できました。ユーザー・インタフェースとバックエンドを強く型付けされた同じ言語で開発することや、データ・クラスや検証ロジックを共有することもできました。また、コード品質ツールをフロントエンドとバックエンドの両方のコードに対して適用することもできました。いい時代でした。 しかし最近、ブラウザ・ベンダーはJavaScriptに重きを置くようになり、Javaのサポートをだんだん減らしてきました。クライアント側のWebアプリに支配された世の中では、Javaはサーバー側のみのテクノロジーに格下げされてしまっているようです。フルスタックJavaエクスペリエンスのメリットは、失われています。開発者は複数の言語やツールセットを行き来しなければならないため、開発の生産性は急降下します。サーバー側のリファクタリングがクライアント側で見逃されることや、その逆のことが起きた場合、エラーが紛れ込むことになります。コードの品質を保つための優れたJavaツールセットも、クライアント側で再発明せざるを得なくなります。これは大きな後退であるように感じられます。   解決策 うれしいことに、解決策があります。オープンソース・プロジェクトTeaVMは、受け取ったJavaコードをビルド時にコンパクトで高速なJavaScriptに変換するものです。多くのWeb APIのラッパーが提供されているため、DOMの操作からユーザーの位置情報の取得まで、コードでブラウザを完全に操作できます。また、Flavourという、Webページ・テンプレート用の軽量フレームワークも含まれています。Flavourには、JSONバインディングを含め、JAX-RS Webサービスを簡単に呼び出す仕組みが組み込まれています。 TeaVMの考え方はGoogle Web Toolkit(GWT)に似ています。いずれのプロダクトでもJavaでコードを書くことができ、ブラウザと親和性があるJavaScriptが生成されます。しかし、柔軟性、パフォーマンス、Webネイティブ性という領域では、TeaVMがGWTを上回っています。TeaVMではKotlinやScalaなどのすべてのJVM言語をサポートしていますが、GWTでサポートしているのはJavaのみです。TeaVMでは、GWTよりコードのビルドが高速であるだけでなく、GWTよりコンパクトでブラウザでのパフォーマンスもよいJavaScriptが生成されます。さらに、おそらくもっとも重要なポイントは、TeaVMのFlavourライブラリにより、アプリのコンテンツやコンポーネントをHTMLとCSSで構築できるため、開発者とデザイナーが連携して作業できることです。 フロントエンドのTeaVMとバックエンドの従来のJavaサービスとを組み合わせれば、フルスタックのJavaを取り戻すことになります。すべてをJavaでコーディングすることができます。UIとサーバーでクラスを共有することができ、サーバーで名前をリファクタリングすればUIもリファクタリングされます。すべてのビジネス・ロジックの単体テストをJUnitで書くこともできます。PITミューテーション・テストでテスト品質を計測することや、PMDやCheckstyleなどのユーティリティでコード品質を確認することも可能です。   使ってみる TeaVM Flavourアプリは、標準Maven Webプロジェクトにいくつかの追加を行ったものです。アプリケーションのメイン・ページは、おなじみのwebapp/index.htmlです(慣例的に、CSSはwebapp/css/app.cssにあります)。ただし、メイン・ページは簡単なものであることが多く、通常はresources/templatesフォルダからHTMLテンプレート(ページ断片)を読み込みます。このHTMLテンプレートは、Javaのビュー・クラスと1対1になるようにリンクされています。ページのビジネス・ロジックを提供するのは、このビュー・クラス(src/main/javaにあります)です。ビュー・クラスには、ビジネス・ロジック、テンプレートで使われるプロパティ、バインディングが含まれています。さらに具体的に言えば、ビュー・クラスでは、イベントへの応答、RESTサービスとの通信、表示されるテンプレートの変更など、何でも行うことができます。 このセクションでは、単純なTeaVMアプリケーションをゼロから立ち上げる方法について説明します。その中で、Javaベースのビジネス・ロジックにリンクしたHTMLテンプレートを作成します。このビジネス・ロジックが、ブラウザの中だけでユーザーのアクションに応答します。 使用を開始するもっとも早い方法は、Mavenアーキタイプを使ってTeaVM Flavourプロジェクトを作成することです。 mvn archetype:generate \     -DarchetypeGroupId=org.teavm.flavour \     -DarchetypeArtifactId=teavm-flavour-application \     -DarchetypeVersion=0.2.0 グループとパッケージにcom.mycompanyを、アーティファクトIDにflavourを使った場合、主なファイルは次のようになります。 src/main/java/com/mycompany/Client.java:このファイルには、アプリのJavaロジック(ビューと呼ばれます)が含まれます。また、テンプレート(次の項目で説明します)で使われるプロパティ(名前)も含まれます。 src/main/resources/templates/client.html:アプリのHTMLテンプレートです。nameフィールドにバインドされる、入力フィールドとHTMLテキストが含まれます。入力を変更すると、HTMLが変わります。後ほどルーティングとコンポーネントのセクションで説明しますが、HTMLテンプレートとビューの2つは、Flavourのあらゆる場面で使用される基本要素です。 src/main/webapp/css/app.css:アプリのCSSです。 src/main/webapp/index.html:アプリケーションのラッパーHTMLページです。ここでは大したことはしておらず、実際のアクションはテンプレートで行われます。 packageゴールを使ってプロジェクトをビルドします。 mvn clean package Webアプリのファイルで、パッケージが指定されていないものは、target/flavour-1.0-SNAPSHOT/に格納されることになります。ブラウザからindex.htmlファイルを開いて、アプリが動作していることをすぐに確認できます。 プラグインやダウンロードは必要なく、Javaアプリがブラウザで直接実行されます。 アプリをTomcatにデプロイする場合は、target/flavour-1.0-SNAPSHOT.warを使います。または、${TOMCAT_HOME}/webapps/flavourをflavour-1.0-SNAPSHOTフォルダへのシンボリック・リンクにすることもできます。シンボリック・リンクの方法を使った場合は、http://localhost:8080/flavourからアプリにアクセスすることができます(Tomcatがデフォルト・ポートの8080で動作するように構成している場合)。   Flavourを使ったリスト表示 UIに項目のリストを表示したいことはよくあります。Flavour要素<std:foreach>を使うことで、短いリストや中規模のリストを表示できます。このFlavour要素には、Javaでの通常のforeachループと同じように、反復処理するコレクションと、ループ本体をスコープとするコレクション要素の変数名を渡します。 コレクションはビュー・クラスから読み取られます。public List<Song> getSongs()というgetterで歌のリストを定義している場合、次のようにして単純なリストを作成することができます(本記事で紹介しているすべてのコードは、ダウンロード・サイトで公開しています)。次に示すのは、listプロジェクトのclient.htmlのコードです。 <ol>   <std:foreach var="song" in="songs">     <li>       <html:text value="song.name"> by       <strong><html:text value="song.artist"></strong>     </li>   </std:foreach> </ol> Flavourで生成される順序付きリスト(ol)には、getSongs()が返すすべてのSongに対応するリスト項目(li)が存在しています。リストの各項目には、歌の名前とアーティストが含まれます。<html:text>要素では、value式が評価され、その結果がページに挿入されます。Flavourの式言語を使うことで、明示的にgetterやsetterを呼び出さずに、JavaBeansスタイルのプロパティ(getX/setX)に名前を使ってアクセスすることができます。そのため、song.getName()と書く代わりに、song.nameと簡略化して書くことができます。 次は、それぞれの機能について詳しく見ていきます。   その他の標準コンポーネント Flavourでは、std:foreachの他にも、ページのコンテンツを制御する多くの標準コンポーネントをサポートしています。 std:ifを使うことで、ブール条件に基づいてページにコンテンツを追加できます。 attr:xyzを使うことで、式を使って属性xyzを設定できます。たとえば、attr:classにより、要素のクラスを動的に設定できます。 標準コンポーネントは、次に紹介するFlavourの式言語と密接に連携しています。   式言語 Flavourコンポーネントの属性は、式言語(EL)を使って指定します。ELはJavaに似ていますが、いくつかのシンタックス・シュガーが追加されており、HTMLページでうまく動作させるための若干の調整も行われています。 以下を含め、すべての標準プリミティブがサポートされています。 文字列(HTML内で簡単に使えるように、一重引用符を使用) 数値(整数と浮動小数点数) ブール値(trueとfalse) ビュー・クラスのメソッドとプロパティは、名前を使って呼び出すことができます。Javaと同様に、メソッド呼出しにはパラメータが必要です。ただし、プロパティは名前で直接参照できます。先ほど紹介したように、titleと指定するだけで、FlavourがgetTitle()を呼び出してくれます。 次に示すのは、ELの使い方を説明したいくつかの標準コンポーネントの例です(standard-components-elのclient.html)。 <!-- ブール値プロパティshowHeadingを使用 --> <std:if condition="showHeading">   <h1>Flavour Messenger</h1> </std:if>   <!-- messageが入力されたときにボタンを有効化 --> <!-- message.emptyはELの省略記法で、message.isEmpty()を表す --> <button html:enabled="!message.empty">Check Spelling</button>   イベントへの応答 UIは、ユーザーの操作に応答できなければ完成とは言えません。Flavourでevent属性を使うことにより、DOMイベントが発生したときにJavaメソッドを呼び出すことができます。一般的に使われるオプションには、以下のものがあります。 event:click:クリック・イベントのハンドリング event:change:コンポーネントの値変更のハンドリング テンプレートで、ボタンをクリックしたときにsendメソッドを呼び出したい場合を考えてみます。次のようにして、単純にevent:click属性をボタンに追加します。 <button event:click="send()">Send</button> バインディング バインディングを行うことで、ビュー・クラスのプロパティを表示コンポーネントにリンクすることができます。バインディングには、いくつかの種類があります。ビュー・クラスのプロパティが変更されたときにUIが更新されるようにするものもあれば、ユーザーがUIコンポーネントを操作したときにビュー・クラスのプロパティが更新されるようにするものもあります。双方向バインディングは、この2つを組み合わせて、ビュー・クラスのプロパティ、またはUIのいずれが変更された場合でも両方を同期させるものです。特によく使われるものを紹介します。 html:textでは、ビュー・クラスのプロパティに基づいてテンプレートにHTMLが出力されます。 html:valueでは、ビュー・クラスのプロパティの変更に基づいて入力コンポーネントが更新されます。 html:changeでは、入力値が変更されたときにビュー・クラスのメソッドが呼び出されます。 html:bidir-valueはもっとも強力です。html:valueとhtml:changeを組み合わせて、UIの入力フィールド、またはビュー・クラスのプロパティのいずれが変更されても両者を同期させます。 先ほどのファイルには、次の内容が含まれています。 <form> <!-- 値が変更されると、messageプロパティからの読取りまたはmessageプロパティへの書込みを行う -->   <input type="text" html:bidir-value="message"> </form>   ルーティング 単一ページ・アプリケーション(SPA)には複数の画面があり、サーバーにリクエストせずにブラウザ内で切り替わるようになっています。Flavourでは、ルーティング機能によってSPAを完全にサポートしています。ルーティングには、いくつかの要素が関連します。 ルート・インタフェース:画面とそのURLを定義する ルート実装:オンデマンドで画面のインスタンスを生成する 画面のHTMLテンプレート:1画面につき1つ存在し、レイアウトとコンポーネントを含む ビュー・クラス:1つのテンプレートにつき1つ存在し、テンプレートのデータとイベント・ハンドラを提供する レストランの一覧を表示する画面と、それぞれのレストランの詳細画面があるものとします。その場合、レストラン一覧とレストラン詳細のテンプレートを作成できます。それぞれのテンプレートにビュー・ページが存在することになります。ルート・インタフェース(サンプル・コードではApplicationRoute.java)は次のようになります。 @PathSet interface ApplicationRoute extends Route {   @Path("/restaurants")   public void restaurantList();     @Path("/restaurant/{id}")   public void restaurantDetails(@PathParameter("id") int id); } レストランの詳細を表示するrestaurantDetailsページに、レストランID用のパラメータがどのように含まれているかに注意してください。 Routeの実装には、ページを切り替えるロジックが含まれています。慣例的に、RouteはClient.javaのメイン・ページで実装しています。 @BindTemplate("templates/client.html") public class Client extends ApplicationTemplate   implements ApplicationRoute {     RestaurantSource source = new RestaurantSource();     public static void main(String[] args) {         Client client = new Client();         new RouteBinder()             .withDefault(ApplicationRoute.class,                          route -> route.restaurantList())             .add(client)             .update();         client.bind("application-content");     }     @Override     public void restaurantList() {         setView(new RestaurantListView(source));     }     @Override     public void restaurantDetails(int id) {         setView(new RestaurantDetailsView(source, id));     } } レストラン一覧ページ(routingのrestaurant-list.html)では、std:foreachを使ってレストランの一覧を表示します。各レストランには、対応するレストラン詳細ページへのリンクが含まれます。 <div>   <h1>Restaurants</h1>   <ol>     <std:foreach var="restaurant" in="restaurants">       <li>         <button type="button"                 event:click="showRestaurant(restaurant.id)">           <html:text value="restaurant.name"/>         </button>       </li>     </std:foreach>   </ol> </div> レストラン詳細ページ(restaurant-detail.html)では、1つのレストランについての情報を表示します。ここでは、<html:text>を使ってレストラン・オブジェクトのフィールドを表示しています。 <div>   <h1><html:text value="restaurant.name"/></h1>   <h3>In business for     <html:text value="restaurant.yearsInBusiness"/> years!</h3>   <p></p>   <p><button type="button" event:click="showList()">   Return to the restaurant list</button></p> </div>   RESTful API ほとんどのWebアプリケーションではサーバーと通信します。よく使われるのは、リモート・サービスの起動、データの保存、アップデートの取得などです。 Flavourでは、簡単にWebサービスにアクセスする方法を提供しています。RESTClientクラスにより、JAX-RSサービスとして宣言したJSONベースのWebサービス用のクライアントを構築できます。JSONとJAX-RSの人気を考えれば、皆さんのWebサービスがこのクラスに対応している可能性も高いはずです。UIモジュールのPOMファイルにJAX-RSインタフェースを含めるだけで、1行のコードを書くことによりFlavourからインスタンスを生成できます。 YourService service =     RESTClient.factory(YourService.class)               .createResource("api"); レストランの例の続きとして、サービスにレストランの一覧を取得するメソッドがあるものとします。 List getRestaurants(); このメソッドは、次のようにして呼び出すことができます。 List restaurants = service.getRestaurants(); 以上です。クライアント側でレストランの一覧を入手できたことから、先ほどのセクションのようにしてビューで使用できます。 近日中に予定されているTeaVMバージョン0.6リリースでは、REST関係の小さな変更が行われることが発表されています。コードを0.6で動作させるために必要なのは、2箇所にマーカー・アノテーションを付加することだけです。 RESTサービス・インタフェースに@Resource(org.teavm.flavour.rest.Resource)アノテーション サービス・メソッドに渡す、またはサービス・メソッドから受け取るカスタム・クラスに@JsonPersistable (org.teavm.flavour.json.JsonPersistable)アノテーション(StringやLongなどの標準クラスには不要)   カスタム・コンポーネント ページやビューのクラスを設計する際には、再利用したいHTMLやコードが数多くあることに気づくでしょう。筆者がよく見るケースは2つあります。 ページの一部を2つのページで繰り返して使う必要がある リストやテーブルなど、ページ内に繰り返される部分がある コンポーネントを定義する最初の手順は、Flavourアプリで通常のページを定義する操作によく似ています。ビュー・クラスにバインドされたHTMLテンプレートを作成します。このテンプレート(慣例的に、src/main/componentsに配置します)には、コンポーネントのHTMLを格納します。ビュー・クラスにはテンプレートで使用するビジネス・ロジックとプロパティを含めます。この点は先ほどと同じですが、バインディングを追加します。バインディングはコンポーネントに対して一意で、属性やその他の値がどのようにテンプレートにバインドされるかを指定します。属性は、コンポーネントをカスタマイズするために使われます。 それでは、リスト内部や複数のページで再利用できるレストラン・コンポーネントの作り方を見てみます。 まず、components/restaurant.htmlでテンプレートを定義します。 <div style="background-color: #99e">   <div>     <h1><html:text value="restaurant.name"/></h1>   </div>   <div>     <h3><html:text value="restaurant.yearsInBusiness"/>      years in business!</h3>   </div> </div> 次に、src/main/java/com/app/component/Restaurant.javaでビュー・クラスを定義します。コンポーネントに表示するレストランをrestaurant属性にバインドしている点に注意してください。 @BindTemplate("components/restaurant.html") @BindElement(name = "restaurant") public class RestaurantComponent extends AbstractWidget {     private Supplier<Restaurant> restaurantSupplier;       public RestaurantComponent(Slot slot) {       super(slot);     }       @BindAttribute(name = "restaurant")     public void setNameSupplier(                     Supplier<Restaurant> supplier) {         this.restaurantSupplier = supplier;     }       public Restaurant getRestaurant() {         return restaurantSupplier.get();     } } さらに、コンポーネントを使う前に、ビュー・クラスの名前を次のファイルに追加して登録を行う必要があります。 META-INF/flavour/component-packages/com.mycompany コンポーネントが別のパッケージにある場合は、com.mycompanyの部分をパッケージと一致するように変更します。 以上の手順が完了すれば、他のテンプレートでレストラン・コンポーネントを使えるようになります。先ほどのレストラン一覧ページを書き換えて、各レストランの一覧が表示されるページを作成してみます。std:foreachループの中にコンポーネントを配置し、各レストランに対応する、コンポーネントのインスタンスを1つずつ作成します(restaurant-list.html)。 <?use comp:com.mycompany?> <div>   <h1>Restaurants</h1>   <ol>     <std:foreach var="restaurant" in="restaurants">       <li>         <comp:restaurant restaurant="restaurant"/>       </li>     </std:foreach>   </ol> </div> テンプレートの最初に、処理命令useがある点に注意してください。この命令では、コンポーネントを使用することをFlavourに対して伝えるとともに、後ほどファイル内で使用する接頭辞を指定しています。ここでは、接頭辞をcompとして宣言しています。後ほどコンポーネントを使う際に、comp:restaurant-detailという形で接頭辞が再登場しています。   位置情報APIの使用 位置情報APIを使ってユーザーの位置を照会することができます。サンプル・アプリのビュー・クラス(ここでは、geolocationのClient.java)に次のコードを追加します。 public void locate() {     if (Navigator.isGeolocationAvailable()) {        Navigator.getGeolocation().getCurrentPosition(            (Position pos) -> {                final Coordinates coords = pos.getCoords();                location = "Lat/Lon: " + coords.getLatitude()                         + "/" + coords.getLongitude();                Templates.update();               },            (PositionError positionError) -> {               switch (positionError.getCode()) {                   case PositionError.PERMISSION_DENIED:                     location = "The user blocked location access.";                     break;                   case PositionError.POSITION_UNAVAILABLE:                     location = "Location could not be determined.";                     break;                  case PositionError.TIMEOUT:                     location = "A timeout occurred while attempting"                             + " to determine your location."; break;               }               Templates.update();            });     } else {           location = "This browser doesn't support geolocation";           Templates.update();     } } public String getLocation() {     return location; } 位置情報の検索を呼び出すボタンと、結果を表示する別のテキスト・フィールドを追加します。 <div>   <div>     <button event:click="locate()">Locate</button>   </div>   <div>     Your location is: <html:text value="location"/>   </div> </div> 位置情報は非同期的に発生することから、Templates.update()を使って表示を更新しています。Templates.update()は軽量であるため、必要なときにはためらわずに呼び出すことができます。   まとめ 以上で、TeaVMで独自のJava Webアプリを構築する準備が整いました。レストラン一覧のWebサイトでも、新しいソーシャル・ネットワークでも、画期的なオンライン・ゲームでも作ることができます。 他の考え方や情報を探したい方、本記事の範囲を超える質問がある方は、TeaVMのWebサイトにアクセスしてみることをお勧めします。フォーラムが設置されているほか、詳しいドキュメントや例、問題リストなどが掲載されています。 Alexey Andreev氏は、TeaVMとFlavourの作者です。本記事の原稿を確認していただいたことに感謝いたします。   Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 言語の内側:シールド型 ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) Andrew Oliver 20年以上にわたり、Javaのコーディング、講演、執筆に携わる。O'Reilly Conferenceでは、エンタープライズJavaについて講演を行った。AWT、Swing、JavaFX、Codename OneなどのさまざまなJavaツールキットでUIを構築した経験を持ち、現在はいくつかのTeaVM/Flavourベースのプロジェクトに従事している。 ※本記事は、Andrew Oliverによる”Java in the Browser with TeaVM“を翻訳したものです。

Javaをフロントエンドとバックエンドの両方に使ってWebアプリを構築する 著者:Andrew Oliver 2019年10月8日 Javaがブラウザの中で実行されていた頃を覚えている方もいるかもしれません。その頃は、Webページから「Pure Java」のUIを簡単に起動できました。ユーザー・インタフェースとバックエンドを強く型付けされた同じ言語で開発することや、データ・クラスや検証ロジックを共有するこ...

言語の内側:シールド型

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; } パターン・マッチング、列挙型およびswitch文の改善に向けて進化するJava 著者:Ben Evans 2019年10月16日 本記事では、Javaにとって新しいプログラミング言語概念である、シールド型について説明します。この機能は現在活発に開発が行われており、将来のバージョンのJavaに導入される見込みです。シールド型の実現に必要な、プラットフォームレベルの重要な仕組みが、Java 11のネストメイトと呼ばれる機能で導入されました。Java 12でも、プレビュー機能としてswitch式が導入されました。そこから、将来のバージョンのJavaでシールド型をどのように使えるかが垣間見えます。 本記事の内容を完全に理解するためには、プログラミング言語の設計や実装について、かなりの知識が必要です(少なくとも、興味を持っている必要があります)。まず、シールド型とは何であるかを説明するにあたり、Javaの列挙型とその内部の仕組みについて詳しく見てみます。 列挙型は、とてもよく知られている、Javaの言語機能です。列挙型を使うことにより、ある型が取り得るすべての値を表現する有限集合をモデリングすることができます。そのため、列挙型は実質的に型安全な定数として扱われます。 たとえば、列挙型Petについて考えてみます。 enum Pet { CAT, DOG } プラットフォームでは、特殊な形態のクラス型をJavaコンパイラで自動生成させることによって、この列挙型を実現しています(厳密に言えば、ランタイムは実際にはライブラリ型java.lang.Enumを他のクラスとは少しばかり異なる特殊な方法で扱っていますが、この処理の詳細はここではあまり関係ありません。すべての列挙型クラスはjava.lang.Enumを直接拡張しています)。 この列挙型を逆コンパイルして、コンパイラが何を生成したのかを確認してみます。 eris:src ben$ javap –c Pet.class Compiled from "Pet.java" final class Pet extends java.lang.Enum { public static final Pet CAT; public static final Pet DOG; … // Private constructor } クラス・ファイルの中では、列挙型が取り得るすべての値がpublic static final変数として定義されています。コンストラクタはprivateであるため、インスタンスを追加生成することはできません。 実質的に、列挙型はシングルトン・パターンを一般化したものに近くなっていますが、クラスのインスタンスが1つだけ存在するのではなく、有限個存在する点が異なります。このパターンにより、網羅性という考え方を導入できるため、非常に便利です。つまり、NULLでないPetオブジェクトがある場合、CATインスタンスまたはDOGインスタンスのいずれかであることが確実にわかります。 ここで、さまざまな種類の犬と猫を現在のJavaでモデリングすることを考えてみます。その場合、1つのインスタンスではそれぞれの種類のペットを表すのに十分ではないため、2つの好ましくない方法のいずれかを使わざるを得なくなります。 まず、1つの実装クラスPetと、実際の種類を保持する状態フィールドを併用する方法が可能です。状態フィールドは列挙型のような性質を持ち、特定のオブジェクトが実際にどの種類に属するかを表すビットを提供するため、このパターンはうまく動作します。しかし、自らビットを追跡する必要があり、それはまさに型システムの範疇であることから、これは明らかに次善の方法です。 もう1つの方法として、抽象型のベースタイプPetを宣言し、そのサブクラスとして具象型のCatとDogを作成することができます。ここでの問題は、Javaが、デフォルトで拡張可能であるオープンな言語として常に設計されてきたことです。クラスが一度コンパイルされれば、何年が経過しても(または何十年後であっても)サブクラスをコンパイルすることができます。 Java 11の時点で、Java言語で許可されているクラス継承構造は、オープン継承(デフォルト)と継承なし(final)のみです。抽象型をベースとして具象型のサブタイプを使うパターンの重大な弱点がここに露呈しています。 ここでの問題は、クラスではパッケージ・プライベートなコンストラクタ(これは実質的に「同じパッケージのクラスのみが拡張できる」ことを意味します)を宣言できるものの、ランタイム・システムにはユーザーがそのパッケージ内に新しいクラスを作成することを防ぐ手段はない点です。したがって、この保護は、どう好意的に見ても不完全です。 つまり、Petクラスを定義した場合、Petを継承したSkunkクラスを第三者が作ることを防ぐのは不可能です。さらに悪いことに、この望まれない拡張は、Pet型がコンパイルされてから何年が経過しても(または何十年後であっても)行われる可能性があります。これは非常に好ましくないことです。 結論として、現在のバージョンのJavaでは、抽象型のベースを使ったアプローチは安全ではありません。つまり、開発者は、Petの実際の種類を保持するためにフィールドを使うしかないということになります。 OpenJDKプロジェクト(標準バージョンのJavaが開発されている場所)全体の中の、Project Amberというプロジェクトに関する最近の取り組みでは、継承性を細かく制御する新しい方法を導入することで、この状態を改善することを目指しています。その新しい方法がシールド型です。 この機能は、さまざまな形態で他のいくつかのプログラミング言語に存在しており、ここ最近の流行になっています(ただし実のところ、この考え方はかなり古くからあります)。 Javaでは、この機能をシールド(封印された)という概念によって実現しています。この概念が表しているのは、クラスは拡張可能であるものの、既知のリストにあるサブタイプにのみ拡張でき、他のクラスには拡張できないということです。 他の言語ではこの機能を違う形で見ているかもしれませんが、Javaでは「ほとんどfinal」なクラスを表す機能と考えます。 簡単な例を使って、現時点での新しい構文を見てみます。 public abstract sealed class SealedPet permits Cat, Dog { protected final String name; public abstract void speak(); public SealedPet(String name) { this.name = name; } } public final class Cat extends SealedPet { public Cat(String name) { super(name); } public void speak() { System.out.println(name +" says Meow"); } public void huntMouse() { System.out.println(name +" caught a mouse"); } } public final class Dog extends SealedPet { public Dog(String name) { super(name); } public void speak() { System.out.println(name +" says Woof"); } public void pullSled() { System.out.println(name +" pulled the sled"); } } ここでいくつか気づくことがあります。まず、SealedPetはabstract sealedクラスになっています。sealedは、今までJavaで許可されていたキーワードではありません。次に、2つ目の新しいキーワードであるpermitsが使われています。開発者はpermitsを使って、そのシールド型に許可されているサブクラスを列挙することができます(サブタイプのリストを記述していない場合、許可されているサブタイプは同じコンパイル・ユニットのサブタイプから推論されます)。 さらに、CatとDogは正規のクラスであるため、ネズミを捕まえる、ソリを引くなど、その型に固有な動作を行うことができるというメリットも得られます。オブジェクトの「実際の型」を示すフィールドを使う場合、すべてのサブタイプにおけるすべてのメソッドがベースタイプに存在することが必要になるか、不格好なダウンキャストを行わざるを得なくなるため、ここまで簡単にはいかないでしょう(なお、この例では、サブクラスをfinalにしていますが、staticでfinalなインナー・クラスにするなど、修飾子の組合せは他のものも可能です)。 ここでこれらの型を使ってプログラムを作る場合、登場するすべてのPetインスタンスは、CatまたはDogのいずれかであるとわかっていることになります。さらに、コンパイラもこの情報を使うことができます。つまり、ライブラリのコードでは、列挙されたものしかサブクラスが存在しないことを問題なく前提にできるようになり、クライアントのコードもこの前提を破ることはできません。 オブジェクト指向プログラミングの理論で言えば、これは新しい種類の正式な関係を表します。オブジェクトoには、CatまたはDogとis-a関係があると言うことができます。すなわち、oが取り得る型は、CatとDogの和集合(union)です。 そのため、こういった型は非交和(disjoint union)型と呼ばれます。さまざまな言語では、タグ付き共用型(tagged union)や直和型(sum type)とも呼ばれています。ただし、これらはCの共用体(union)とは少しばかり異なることに注意してください。 たとえば、Scalaプログラマーは、ケース・クラスやScala版のsealedキーワードを使って、非交和型と非常によく似た考え方を実現できます。 JVM以外では、Rust言語にも非交和型の考え方が導入されています。ただし、Rustでは非交和型をenumキーワードで表しているため、Javaプログラマーはかなりややこしく感じるかもしれません。 一見したところ、この型はJavaにとってまったく新しい考え方であるように感じるかもしれません。しかし、列挙型にとてもよく似ているため、Javaプログラマーにとっては、その類似点がよい出発点になるはずです。実は、直和型に似たものがすでに存在する箇所があります。マルチキャッチ句の例外パラメータの型です。 Java 11の言語仕様には、「例外パラメータを宣言する型は、例外パラメータの型を和集合(union)D1 | D2 | ... | Dnで記述すると、lub(D1, D2, ..., Dn)となる」(JLS 11セクション14.20)と書かれています。引用部にあるlubとは「最小上界」で、D1、D2、...、 Dnにもっとも近い共通のスーパータイプという意味です。これはjava.lang.Objectまたはそれよりも具体的な何らかの型になります。マルチキャッチの例外の場合、lubはThrowableまたはそれよりも具体的な何らかの型になります。 シールド型と新しいswitch しかし、シールド型の実用性はモデリング能力の向上にとどまりません。実際、JDK 12で導入された(そしてJDK 13で強化された)興味深い機能に、switch式があります。そこから、今後Java開発者がシールド型をどのように活用できるかが垣間見えます。これについてもう少し詳しく見ていきます。 switch式は、Javaに古くからある(Cによく似た)switchキーワード(これは文です)を大きくアップグレードするものです。この新機能を使うことで、switchを式として動作させることができます。この設計では、switch(または同じような言語構造)を、値を返す式として扱う、より関数指向型の言語(Haskell、Scala、Kotlinなど)との間の言語的なギャップを埋めることを目指しています。 最終形がJava 12に導入されたバージョンと大幅に異なるものになることは考えにくいですが、本記事はJava 13の形式が確定する前に執筆しているため、ここではJava 12の形式を使って説明します。次に例を示します。 import java.time.DayOfWeek; public static boolean isWorkDay(DayOfWeek day){ var today = switch(day) { case SATURDAY, SUNDAY -> false; case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> true; }; // 祝日の考慮など、その他の処理を行う return today; } switch式は、入力値に基づいて出力値を生成する関数のように振る舞うことができるようになりました。switch式を使うことで、switchが非常によく使われるパターンを簡単に記述できるため、この機能単独でとても便利です。実のところ、switch式のルールは、すべての入力値が出力値を生成することが保証されている必要があるというものです。 たとえば、intを受け取るswitch式では、取り得るすべての場合を列挙することはできないため、default句を含める必要があります。ただし、鋭い開発者の方なら気づくと思いますが、列挙型の場合、コンパイラでは定数の網羅性を使用できます。ご想像どおり、取り得るすべての列挙型定数がswitch式に存在する場合は、先ほどの例のようにdefault句を含める必要はありません。 また、switch式は、将来のバージョンのJavaで実現される大きな機能へ向けての足がかりでもあります。 その目的は、パターン・マッチングと呼ばれる、より高度な構造を今後のリリースで導入することです。提案されている、言語レベルのこの機能を、正規表現のパターン・マッチング(すなわちregex)と混同しないでください。ここでのパターン・マッチングは、マッチ式(switchのcaseラベルを一般化したもの)をJavaでおなじみの単純な定数以外のものに適用できるように拡張することを指します。 たとえば、対象となるオブジェクトの値ではなく型と照合することもできます(これは型パターンと呼ばれています)。プログラマーにとって型パターンは、コンパイル時に型がわからないオブジェクトがあり、種類の異なる有効な選択肢が存在する場合に役立ちます。たとえば、JSONを解析している場合なら、出現する値は文字列、数値、ブール値、その他のJSONオブジェクトのいずれかになります。解析後に型パターンを使えば、値を表すオブジェクトの型と照合を行うことで、値がString、Double、Booleanの場合に特化した処理を作成できるでしょう。 Javaフレームワークは、この種の動的な柔軟性をJava言語にたびたびもたらしています。この柔軟性が言語レベルで直接サポートされれば、大きな一歩になるでしょう。 シールド型の導入により、このオブジェクトの型はX、Y、Zのいずれかであり、その他の型ではないと断言できるようになります。このオブジェクト指向的な考え方は非常に強力です。switch式における列挙型で紹介した網羅性が、型レベルでも実現できるようになります。シールド型では、Javaの既存の列挙型言語機能に似た機能を提供しますが、列挙型は「存在し得るインスタンスが有限個」であるのに対し、シールド型は「存在し得る型が有限個」となります。 マッチ式、シールド型、そしてその他の開発中のJVM技術を組み合わせれば、関数型スタイルとオブジェクト指向スタイルの両方のプログラミングに対するJavaのサポートがさらに拡張されることになります。 早期アクセス・バイナリはまだ公開されていないため、早速シールド型を試してみたい開発者の方は、ソースから自分用のOpenJDKバイナリをビルドする必要があります。なお、特定の言語機能を含む確定版のJavaが提供されるまでは、そのバイナリを信頼すべきではない点に注意してください。本記事のように、将来のことや、今後導入される見込みの機能について取り上げている場合、純粋に探求目的のみで将来の見込みを説明していることを常に理解しておいてください。本記事の説明は、ここで取り上げた機能をサポートした確定版のJavaが実際に提供されるということを、誰か(オラクルを含む)に代わって確約するものではありません。 Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 Javaにテキスト・ブロックが登場 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) Ben Evans Ben Evans(@kittylyst):Java ChampionおよびjClarityの創業者/技術フェローであり、London Java Community(LJC)主催者。Java SE/EE Executive Committeeのメンバーでもある。先日発刊された『Optimizing Java』(O'Reilly)を含め、プログラミングに関する4冊の書籍を執筆している。 ※本記事は、Ben Evans”Inside the Language: Sealed Types“を翻訳したものです。

パターン・マッチング、列挙型およびswitch文の改善に向けて進化するJava 著者:Ben Evans 2019年10月16日 本記事では、Javaにとって新しいプログラミング言語概念である、シールド型について説明します。この機能は現在活発に開発が行われており、将来のバージョンのJavaに導入される見込みです。シールド型の実現に必要な、プラットフォームレベルの重要な仕組みが、Java 11のネストメイ...

Javaにテキスト・ブロックが登場

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; } 長く待ち望まれてきた複数行文字列がJava 13で実現 著者:Mala Gupta 2019年10月16日 Java 13のテキスト・ブロックを使うことで、複数行文字列リテラルを簡単に使えるようになります。文字列リテラル内の特殊文字をエスケープすることや、複数行にまたがる値に連結演算子を使うことは不要になります。さらに、文字列を書式設定する方法も制御できるようになります。テキスト・ブロックとは、複数行文字列を表すJavaの用語です。テキスト・ブロックにより、コードの可読性は大幅に向上します。 本記事では、テキスト・ブロックとは何か、テキスト・ブロックによって対処できる問題、そしてテキスト・ブロックの使い方について説明します。それでは始めます。 テキスト・ブロックとは Stringデータタイプはおそらく、Java開発者がもっともよく使う型の1つです。任意の言語で数文字の文字列から複数行文字列まで、何でも格納できます。しかし、この柔軟性のため、一部のString値を読み取ることや変更することが難しくなる場合があります。例を挙げるなら、引用符が埋め込まれている文字列、エスケープ文字、複数行にわたる文字列などです。 それでは、Java 13の新しいプレビュー機能であるテキスト・ブロックが、どのように役立つかについて見ていきます。 テキスト・ブロックを使用して、複数行Stringリテラルを簡単に定義することができます。通常のStringリテラルを使って実現した場合に見にくさの原因となる、連結演算子やエスケープ・シーケンスの追加が不要です。さらに、String値をどのように書式設定するかも制御できます。例として、次のHTMLスニペットについて考えてみます。 String html = """ <HTML> <BODY> <H1>"Java 13 is here!"</H1> </BODY> </HTML>"""; ブロックの開始と終了を表す3つの二重引用符に注目してください。以前のJavaでは代わりにどのように記述していたかについて考えてみます。 String html1 = "<HTML>\n\t<BODY>\n\t\t<H1>\"Java 13 is here!\"</H1> \n\t</BODY>\n</HTML>\n"; 次の方が一般的かもしれません。 String html = "<HTML>" + "\n\t" + "<BODY>" + "\n\t\t" + "<H1>\"Java 13 is here!\"</H1>" + "\n\t" + "</BODY>" + "\n" + "</HTML>"; いずれも、テキスト・ブロックほどわかりやすくはありません。 構文 前述のように、テキスト・ブロックは、3つの二重引用符(""")を、開始および終了を表すデリミタとして使って定義します。開始デリミタの後には、0個以上の空白と行終端文字(改行)を置くことができます。テキスト・ブロックの値は、この行終端文字の後から始まります。終了デリミタの場合、このようなルールはありません。 したがって、次の例は無効なテキスト・ブロックとなります。開始デリミタの後に行終端文字が存在しないからです。 String multilineValue1 = """ """; String multilineValue2 = """"""; プレビュー言語機能 テキスト・ブロックは、Java 13でプレビュー言語機能としてリリースされました。しかし、プレビュー言語機能は未完成または開発途中の機能ではありません。実際のところ、開発者に使ってもらう準備は整っているものの、細かい部分は今後のJavaリリースで変更される可能性もある機能という意味です。このような扱いになっていることには、理由があります。 開発者は、6か月周期の新しいリリース・サイクルにより、言語の新機能を使えるようになっています。しかし、Javaチームは、Javaに言語機能を恒久的に追加する前に、その機能に対する開発者の意見について評価します。フィードバック次第で、プレビュー機能が微調整されてからJava SEに追加されることも、完全に削除されることもあります。そこで、テキスト・ブロックについてフィードバックがある方は、JDKメーリング・リスト(メンバー登録が必要です)で共有してください。 プレビュー言語機能を使うためには、コンパイル時と実行時に明確に有効化する必要があります。これにより、意図せずにプレビュー機能を使ってしまうことがないようにしています。テキスト・ブロックを含むソース・ファイルをコンパイルするためには、--enable-previewオプションと-release 13オプションを使用します。コマンドラインを使ってソース・ファイルJava13.javaをコンパイルする例を次に示します。 javac --enable-preview --release 13 Java13.java プレビュー機能は変更される可能性もあることを強調するため、先ほどのコマンドを実行した際に、図1のようなコンパイラ警告が表示されます。 図1:>プレビュー機能が使われているコードに対して表示されるコンパイラ警告 クラスJava13を実行するときも、--enable-previewオプションを使う必要があります。 java --enable-preview Java13 次は、テキスト・ブロックの実装について見てみます。 同じStringデータタイプ 従来のString値もテキスト・ブロックも、コンパイルされると同じ型、すなわちStringになります。バイトコードのクラス・ファイルでは、String値が従来のStringによるものか、テキスト・ブロックによるものかは区別されません。このことは、テキスト・ブロックの値が文字列プールに格納されていることを示しています。 次のコードをご覧ください。皆さんは、変数traditonalStringとtextBlockStringが同じStringインスタンスを指すと思うでしょうか。 String traditionalString = "Java"; String textBlockString = """ Java"""; System.out.println(traditionalString == textBlockString); この2つは内容が同じであるため、同じインスタンスを指します。先ほどのコードでは、trueが出力されます。 本記事の冒頭で、従来のStringでは複数行のString値が扱いづらくなることについて説明しました。続くいくつかのセクションでは、テキスト・ブロックがどのように役立つかについて取り上げます。 複数行の値の扱いを改善 開発者は、JSON、HTML、XMLや正規表現(regex)データなどの複数行文字列値を頻繁に扱います。テキスト・ブロックを使うことで、複数行JSON値の扱いが次のようにシンプルになります。 String json = """ { "name": "web", "version": "1.0.0", "dependencies": "AppA" } """; エスケープ・シーケンスや連結演算子によって見にくくなることがないため、JSON値を容易に編集できます。念のため、メリットを感じないと思う方のために、従来のStringで同じJSON値を定義した場合の例も挙げておきます。 String json = "{" + "\"name\": \"web\"," + "\"version\": \"1.0.0\"," + "\"dependencies\": \"AppA\" + "}"; この例は、読者のSven Bloesl氏の提案によって改善されています。 SQL問合せをString値として格納するためには、SQL問合せをコピーして貼り付けるか、または自分で書くかのいずれかを行います。String変数を使って(Java 12またはそれ以前のバージョンで)複数行SQL問合せを次のようにして格納したとします。 String query = "SELECT name, age" + "FROM EMP" + "WHERE name = \'John\'" + "AND age > 20"; このコードは、無効な問合せを表しています。各行の末尾に空白がないため、この問合せは次のように解釈されます。 SELECT name, ageFROM EMPWHERE name = 'John'AND age > 20 Karim Ourrai氏とBrian Goetz氏の報告により、誤った例がこのセクションから削除されました。 テキスト・ブロックを使うことにより、同じような問題を避けることができます。 String query = """ SELECT name, age FROM EMP WHERE name = 'John' AND age > 20 """; テキスト・ブロック内のエスケープ・シーケンス Stringリテラルと同様に、テキスト・ブロックにもさまざまなエスケープ・シーケンスを追加できます。たとえば、テキスト・ブロックに新しい行を含める場合、値を複数行にわたって配置することも、\nのようなエスケープ・シーケンスを使うこともできます。次のコードでは、I'mとhappyは別々の行になります。 String html = """ <HTML> <BODY> <H1>I'm \nhappy</H1> </BODY> </HTML>"""; ご想像のとおり、無効なエスケープ・シーケンスやエスケープしていないバックスラッシュは許可されません。 意味のない空白とインデント 大きな疑問として、不要な空白がどのように扱われるかが挙げられます。実は、この点はコンパイラがエレガントに処理してくれます。テキスト・ブロックでは、すべての行で一番左側にある非空白文字、または一番左側にある終了デリミタによって、意味のある空白が始まる場所が定義されます。 図2の場合、getHTML()で返されるString値の中で、一番左側にある非空白文字は<(<HTML>の最初の部分)です。この位置は、終了デリミタの位置とも一致しています。 図2:>不要な空白(青)と意味のある空白(緑)を表したコード このコードでは、図3に示す文字列が返されます(最初と最後の行では、先頭に空白が含まれません)。 図3:>先ほどのコードから返される文字列(緑色の四角は文字列に含まれる空白を示します) 空白が削除されないように、必須としてマークするためには、終了デリミタ、またはいずれかの非空白文字を左に動かします。図4に示すように、終了デリミタ"""を8文字分左に動かしてみます。 図4:>終了デリミタを左に動かした、図2のコード 変更したコードでは、図5に示すString値が返されます。各行の先頭に8文字分の空白(緑色の四角)が追加されています。その他の空白も緑色の四角で表されています。 図5:>図4のコードから出力される文字列(先頭に空白(緑色の四角)が追加されています) デフォルトでは、それぞれの行の末尾にある空白はテキスト・ブロックから削除されます。その空白を保持する必要がある場合は、8進エスケープ・シーケンス\040(ASCIIでは、空白は文字32となります)を使って強制的に空白を含めることができます。次に例を示します。この例では、テキスト・ブロックの2行目の末尾に空白を追加しています。 String campaign = """ Don't leave home without - money &\040 carry bag. Reduce | Reuse """; なお、必須の空白にタブ(\t)が含まれている場合、タブは展開されず、1つの空白としてカウントされることに注意してください。 テキスト・ブロックの連結 テキスト・ブロックは、従来のString値と連結できます。その逆も可能です。次に例を示します。 String concatenate() { return """ Items to avoid - Single Use Plastics """ + "Let's pledge to find alternatives"; } String値を連結する理由の1つに、変数の値の挿入があります。 String concatenate(Object obj) { return """ Items to avoid - Single Use """ + obj + """ Let's pledge to find alternatives"""; } テキスト・ブロックは、文字列が想定される任意の場所で使うことができます。そのため、たとえばString.replaceメソッドでも、特別な処理をせずに使うことができます。 String concatenateReplace(Object obj) { return """ Items to avoid - Single Use $type Let's pledge to find alternatives""".replace("$type", obj.toString()); } 同じように、format()をはじめとする、Stringの任意のメソッドも使用できます。 まとめ テキスト・ブロックにより、開発者は複数行文字列値をより簡単に扱えるようになります。現時点でテキスト・ブロックはプレビュー機能であり、変更される可能性もあることに留意してください。 ただし、そのような状況ではあっても、コーディング作業を大いに軽減してくれるはずです。 Java Magazine 日本版Vol.47の他の記事 Java 13のswitch式と再実装されたSocket APIの内側 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) Mala Gupta Mala Gupta(@eMalaGupta):Java Champion。JetBrainsのデベロッパー・アドボケート。eJavaGuru.comの創設者で、認定試験に関する何冊かの人気書籍を執筆。Delhi Java User Groupの共同リーダーであり、Women Who Codeデリー支部のディレクターも務める。 ※本記事は、Mala Guptaによる”Text Blocks Come to Java“を翻訳したものです。

長く待ち望まれてきた複数行文字列がJava 13で実現 著者:Mala Gupta 2019年10月16日 Java 13のテキスト・ブロックを使うことで、複数行文字列リテラルを簡単に使えるようになります。文字列リテラル内の特殊文字をエスケープすることや、複数行にまたがる値に連結演算子を使うことは不要になります。さらに、文字列を書式設定する方法も制御できるようになります。テキスト・ブロックとは、複数行文字...

Java 13のswitch式と再実装されたSocket APIの内側

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; } 段階的変更により、将来のメリットが今回のリリースに含まれるように 著者:Raoul-Gabriel Urma、Richard Warburton 2019年10月16日 時がたつのは早いものです。予定どおり、2019年9月にJDK 13がリリースされました。Java 13では、開発者の興味を引くアップデートが主に3つ行われています。 switch式(プレビュー機能)を新しいyield文により改善 言語プレビュー機能として、複数行文字列リテラル(テキスト・ブロック)を導入 Socket APIの実装を最新化 本記事では、switch式とSocket APIに注目します。テキスト・ブロックについては、本号の別の記事で特集しています。 switch式(プレビュー機能) 以前の記事で、JDK 12のプレビュー機能として導入されたswitch式を紹介しました。switch式はJDK 13でもプレビュー機能のままですが、これが意味するのは、プレビュー・モードを卒業するまでは今後のリリースで変更される可能性があるということです。さらに、機能を開放する必要があることも表しています。switch式を有効にするためには、次のコマンドを実行する必要があります(Example.javaというファイルがあるものとします)。 javac --enable-preview --release 13 Example.java java --enable-preview Example switchの新機能に入る前に、簡単にJDK 12をおさらいしておきます。JDK 12では、式形式のswitchが導入されています。この形式のswitchを使うことで、以前の記事で紹介したいくつかのメリットを享受できます。たとえば、次のようなものです。 フォールスルー:switchの新しい構文ではフォールスルーが発生しません。これにより、バグが発生する可能性を減らすことができます。 複合形式:新しいswitch式では、1つの分岐条件で複数のcaseラベルを扱うことができます。これにより、コードの冗長性を減らすことができます。 網羅性:新しいswitch式を使った場合、可能な列挙値すべてに対して、対応するswitchラベルが存在することをコンパイラが確認してくれます。これにより、バグが発生する可能性がまた少なくなります。 式形式:次の例で示すように、switchを式形式で使えることから、コードが短くなるとともに、コードの意図をより明確に表現できるようになります。次の例では、列挙型をswitchで条件分岐して適切に文字列を返しています。 var log = switch (event) { case PLAY -> "User has triggered the play button"; case STOP -> "User needs a break"; default -> "No event to log"; }; この例からわかるように、アロー構文を使ったワンライナーで簡単な式を返すことができます。しかし、分岐のコードが複数行にまたがる複数の文(ブロック式)の場合、どのように値を返すかはわかりやすくはありませんでした。return文は、呼び出されたメソッド自体から戻ることを示すため、使うことはできません。JDK 12では、switch式自体から値を返す場合、break文を使いました。これは、次に示す例のコードの最終行のように、値付きのキーワードをオーバーロードしているため、少々不格好です。  var log = switch (event) { case PLAY -> "User has triggered the play button"; case STOP -> "User needs a break"; default -> { String message = event.toString(); LocalDateTime now = LocalDateTime.now(); break "Unknown event " + message + " logged on " + now; } }; 上記のコードはJDK 12のプレビュー・モードではコンパイルできますが、JDK 13では動作しなくなります。 JDK 13では、この動作を実現するためにyield文が導入されています(コードの最終行をご覧ください)。  var log = switch (event) { case PLAY -> "User has triggered the play button"; case STOP -> "User needs a break"; default -> { String message = event.toString(); LocalDateTime now = LocalDateTime.now(); yield "Unknown event " + message + " logged on " + now; } }; それでは、break、return、yieldはどう違うのでしょうか。return文は呼出し元に制御を戻し、yieldはもっとも内側にあるswitch式に制御を戻すと考えることができます。breakキーワードは、switch文から抜け出すためのものです。なお、yieldの導入により、Javaのキーワード管理と予約済み識別子に関連して、拡大した議論が始まっていることに注目してください。returnとbreakはキーワードですが、yieldは現在、JDK 10で導入されたvarのような限定識別子として提案されています。この2つの違いは小さなものですが、この違いが重要になる場合もあります。breakを変数名として使うことはできません。その理由は、breakがキーワードであるからです。しかし、varは限定識別子であるため、変数名として使うことができます。 switch式へのyield文の導入は小さな変更ですが、言語にyield文が導入されることによって、将来的な可能性が開かれることになります。というのは、yield文を他の機能に使用できる可能性もあるからです。たとえば、他の多くのプログラミング言語では、コルーチンやジェネレータなどの高度な構造を実現する方法として、yieldという単語が採用されています(たとえば、PythonやJavaScript、Scalaでサポートされています)。一方、現在のJavaで文としてのyieldがサポートされているのはswitch式のみであることを覚えておくことは重要です。 yieldという名前が付いた別の機能(Thread.yieldなど)や、他のプログラミング言語の機能とは混同しないでください。 Socket API 多くの場合、OpenJDKなどの実績ある長命のソフトウェア・プロジェクトは、内部に多くの古いコードを抱えています。こういったコードは、メンテナンスと改善が必要であるにもかかわらず、長期間存在しています。最近のJavaリリースでは、古いコードベースを改善して新機能を追加するために、多くの作業が行われています。Java 13もその例外ではありません。Java 13では、古いSocket APIが完全に再実装されています。その対象になった機能と、理由について確認します。 Java標準には、低レベルTCPネットワーク構成を実装する複数のAPIが含まれています。このAPIを大きく分類すると、「New IO」(NIO)サブシステムと、古いI/Oシステムに分かれます。導入されてからすでに15年が経過したNIOは、この2つのうち新しい方の実装で、非同期APIと同期APIの両方が含まれています(現在、一からTCPを記述している方は、古いI/Oシステムではなく、NIOや高レベルライブラリ(Nettyなど)を使うべきです)。 従来のI/Oライブラリは、古いAPIであるにもかかわらず、現在もJavaエコシステム全体で広く使われています。少しGitHubを検索しただけでも、11,046件のコミットがあり、古いSocketクラスは100万回を超えて参照されていることがわかります。そのため、今後のバージョンのJavaでもこのAPIのメンテナンスがやはり必要であることは明らかです。それでは、Java 13でまったく新しく実装し直す必要があったのはなぜでしょうか。この問いに答えるためには、JDK開発の全体像を理解する必要があります。 現在、OpenJDKで行われている大規模なプロジェクトの1つに、Project Loomがあります。このプロジェクトは、JVMによるFiberの実装です。Fiberは、オペレーティング・システムのスレッドとの対応関係が1対1ではない軽量スレッドです。実際には、1つのオペレーティング・システムのスレッドで、数百や、場合によっては数千のFiberが動作している可能性もあります。Fiberでは、スレッドの動作をブロックしないように考慮されています。Fiberでロックの使用やスリープが必要になった場合、ベースとなるスケジューラはそのスレッドに別のFiberをスワップ・インし、動作を継続させます。 Javaのソケット・オブジェクトはスレッドセーフな設計になっているため、内部的にロック(つまり、同期ブロック)を使ってI/O操作の競合状態を回避します。この実装の大部分は古いCコードです。このコードでは、I/Oバッファとしてスレッド・スタックが使われ、ネイティブ・ロックも使われています。 Project Loomでは、ネイティブCのロックではなく、Java 5のロック(つまり、java.util.concurrent.lockパッケージ)を使ってFiberを効率的に切り替える仕組みがサポートされる予定です。そのため、JDKのすべてのブロッキング・コードをJava 5のロックに移行する必要があります。つまり、Project Loomとの両立性を高めるため、古いSocket APIの再実装が必要だったのです。 ただし、書き換えの動機はそれだけではありませんでした。驚くべきことではありませんが、古いSocket APIには、膨大な問題を抱えた、古くてメンテナンス性の低いコードが数多く含まれています。ネイティブ・ロックとネイティブ・バックエンドのコードには、前述のLoomとの連携に関する問題のほか、メンテナンス上の問題もありました。さらに、JDKに古いSocket APIとNIO APIの両方が存在したため、JDK開発者は2つの異なる実装をメンテナンスする必要がありました。1つの実装を使うことができれば、それに伴ってメンテナンスの負荷も低くなるでしょう。 一言で言えば、Java 13では、NIOと同じベースのインフラストラクチャと実装を使って、古いSocket API用の新しいバックエンドを実装しています。Java 11リリースの一環として、NIOはJava 5のロックを使うように移行されました。そのため、すでにFiberとの親和性があります。今回の変更にはその既存作業が活用され、その結果、Java 13の古いSocket APIにもFiberとの親和性が生まれています。また、JDKで2つのI/Oバックエンドをメンテナンスする必要もなくなっています。 移行の負荷を軽減するため、jdk.net.usePlainSocketImplシステム・プロパティをtrueに設定することで、古い実装を有効化できるようになっています。 APIは変更されていないことから、動作の互換性を別にすれば、この変更に関連する主要なリスクはパフォーマンスです。 OpenJDKプロジェクトでは、変更のパフォーマンスを評価するために使われる一連のマイクロベンチマークがメンテナンスされています。このベンチマークをUbuntu Linux 18.04 / AMD Ryzen 7 1700マシンで実行し、Java 8のSocket API実装と、書き換えられたJava 13のSocket API実装のパフォーマンスを比較してみました。  図1と図2は、それぞれタイムアウトがある場合とない場合で、java.net.Socket APIを使ってTCPのパケットを読み書きするSocketReadWrite.echoベンチマークのパフォーマンスを示したものです。このベンチマークを、1バイトから128,000バイトのサイズのメッセージに対して実行しました。このベンチマークでは、Socketオブジェクト自体のtimeoutプロパティを有効および無効にしています。   図1:タイムアウトがある場合のパフォーマンス   図2:タイムアウトがない場合のパフォーマンス さらに、I/O Streams APIを使ってパフォーマンスをテストするSocketStreaming.testSocketInputStreamReadベンチマークも実行しました。このベンチマークでは、Java 8とJava 13の両方の実装で44ミリ秒/opが達成されました。 以上のベンチマークから、パフォーマンス面での差異に気づくJavaプロジェクトが多いとは考えにくいことがわかります。差は極めて小さく、測定ツールの誤差の範囲内です。なお、この分析の注意事項として、このベンチマークはSocket APIが使われるすべてのシナリオを代表したものではないことを付記しておきます。そのため、パフォーマンスの問題を発見した場合は、バグとして報告する方がよいでしょう。 まとめ Java 13では、いくつかの変更や改善がJava開発者に提供されています。そのいずれもが、調べてみるだけの価値があるものです。複数行文字列が導入され、yieldキーワードの追加によってswitch式が改善されています。これらは、Java SEプラットフォームへの継続的な注力や改善を実証するプレビュー機能です。 さらに、古いSocket APIの書き換えは、Fiberといった将来の機能のリリースへの対応に向けて内部的に行われている継続的な作業の一例です。Java言語は、Javaらしいスタイルと大きなライブラリ・エコシステムを維持しつつ、積極的に改善を繰り返そうという気概にあふれています。そのため、Javaは今後も特に人気の高いプログラミング言語であり続けることでしょう。 Java Magazine 日本版Vol.47の他の記事 Javaにテキスト・ブロックが登場 言語の内側:シールド型 TeaVMを使ってブラウザでJavaを動かす ツールをよく知る クイズに挑戦:1次元配列(中級者向け) クイズに挑戦:カスタム例外(上級者向け) クイズに挑戦:ロケールの読取りと設定(上級者向け) クイズに挑戦:関数型インタフェース(上級者向け) 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による”Inside Java 13’s switch Expressions and Reimplemented Socket API“を翻訳したものです。

段階的変更により、将来のメリットが今回のリリースに含まれるように 著者:Raoul-Gabriel Urma、Richard Warburton 2019年10月16日 時がたつのは早いものです。予定どおり、2019年9月にJDK 13がリリースされました。Java 13では、開発者の興味を引くアップデートが主に3つ行われています。 switch式(プレビュー機能)を新しいyield文により改善 言語プレビュ...

津島博士のパフォーマンス講座 第74回 パラレル実行とリソース管理について

津島博士のパフォーマンス講座 Indexページ ▶▶   皆さん、明けましておめでとうございます。今年も素敵な一年になりますようお祈り申し上げます。 今回は、苦労されている方も多い、パラレル実行と同時実行のリソース管理について説明しようと思います。後半に、これが簡単に利用できるようになっているOracle Autonomous Databaseについても説明していますので、参考にしてください。   1. パラレル実行の同時実行管理 パラレル実行は、第20回で説明したように、処理時間を大幅に短縮することができますが、同時実行するとある時点でリソース制限に行きあたるので、SQL文ごとのパラレル度(DOP)と同時実行するSQL数との間でバランスをとることが大事になります。自動DOPフレームワーク(自動DOPとパラレル・ステートメント・キューイング)を使用することで、ユーザーの介在なしにパラレル処理の使用状況を総体的に(バランスよく)制御することが可能です(自動DOPは、Oracle12cからDBMS_RESOURCE_MANAGER.CALIBRATE_IOプロシージャを実行しない場合でも、デフォルト値を使用して動作するようになりました)。ただし、優先順位の高いSQLなどもあるので、第40回で説明したDatabase Resource Managerと一緒に制御する方がより効果的です。これまでは、自動DOPフレームワークとリソース管理の使い方までは説明していなかったので、以下についてのガイドをまとめてみました。 パラレル実行(PX)サーバー・プロセス数の管理 パラレル・ステートメント・キューイング(PSQ)を使用したパラレル処理の管理 Database Resource Manager(DBRM)を使用したパラレル処理の管理   (1)PXサーバー・プロセス数の管理 まずは、パラレル実行に使用されるPXサーバー・プロセス数の決定について説明します。 PXサーバー・プロセスは、プロセスのプール(PARALLEL_MAX_SERVERS初期化パラメータで最大数を設定)からパラレル操作に割り当てます。デフォルトでは、以下のように設定されるので、基本はこれで問題ありません(CPU時間の割合が多いSQL文のときには、CPU使用率の確認を行うようにしてください)。 PARALLEL_MAX_SERVERS = 5 × <concurrent_parallel_users> × <デフォルトDOP> デフォルトDOPは、CPU_COUNT×PARALLEL_THREADS_PER_CPU×起動インスタンス数(RAC環境のみ)になり、自動DOPではデフォルト最大DOPとして使用されます(PARALLEL_THREADS_PER_CPU初期化パラメータのデフォルトは、Oracle18cから1になっていますが、インテルのHyperThreading機能が無効のときは2の方が最適です)。 concurrent_parallel_usersの値は、初期化パラメータMEMORY_TARGET、SGA_TARGET、PGA_AGGREGATE_TARGETによって以下のようになります(参考までに、デフォルトDOPでの同時実行数も載せておきました)。   MEMORY_TARGET/SGA_TARGET PGA_AGGREGATE_TARGET concurrent_parallel_users デフォルトDOPの同時実行数 設定 - 4 10(5*4/2) 未設定 設定 2 5(5*2/2) 未設定 未設定 1 2(5*1/2)   パラレルSQL文は、プール内のプロセスがすべて割り当てられると、シリアル(非パラレル)実行またはDOPがダウングレードされてパフォーマンスが低下します。そのため、自動DOPの場合は、もう1つの制限として、PSQの前に使用可能なPXサーバー・プロセス数(PARALLEL_SERVERS_TARGET初期化パラメータ)が使用されます。デフォルトは、PARALLEL_MAX_SERVERSの40%で、キューを迂回するパラレルSQL文のために、いくらかのバッファが確保されています(存在しない場合は増やしてください)。なお、PSQをアクティブ化しても、シリアルSQL文はすべて即時実行されます。PARALLEL_SERVERS_TARGETが考慮されるのは、PARALLEL_DEGREE_POLICY初期化パラメータをAUTOまたはADAPTIVEに設定している場合のみです(ADAPTIVEでは、第33回で説明したパフォーマンス・フィードバックも動作しますが、Oracle18cからOPTIMIZER_ADAPTIVE_STATISTICS初期化パラメータがFLASEでも動作するようになりました)。   (2)PSQを使用したパラレル処理の管理 次に、同時実行を制御するPSQの調整方法について説明します。 同時実行の制御は、第20回でPARALLEL_ADAPTIVE_MULTI_USER初期化パラメータも可能ですと説明しましたが、Oracle12cR2からデフォルトはFALSEになり非推奨となりましたので、PSQを使用してください。 自動DOPフレームワークでは、PSQを使用してPXリソースが使用可能になってから実行させるので、調整ポイントはSQL文が長時間キューイングされていないかになります(以下の手順で確認するようにしてください)。 SQL文がキューで待機していないかを待機イベント'resmgr: pq queued'(11.2.0.2から)で確認する。 キューイングされているSQL文は、以下のSQL(V$SQL_MONITOR/GV$SQL_MONITORビュー)またはOracle Enterprise ManagerのSQLモニター画面を使用して特定する。 SQL> SELECT sql_id, sql_text, rm_consumer_group FROM V$SQL_MONITOR WHERE status='QUEUED'; SQL文がPSQにより遅くなっている場合は、以下の手順で検討を行ってください。 SQLが最適に実行しているか(できるだけSQLチューニングを行い最適な実行計画にする) パラレルになる最小実行時間(PRALLEL_MIN_TIME_THRESHOLD)を大きくできないか(パラレルSQL文を減らす) 最大DOP(PARALLEL_DEGREE_LIMIT)を下げられないか(またはDBRMで複数レベルの最大DOPにできないか) PARALLEL_SERVERS_TARGETを大きくできないか(リソースに余裕がある場合) ワークロードに対してシステムのサイズが小さすぎないか また、キューイングさせたくないパラレルSQL文は、NO_STATEMENT_QUEUINGヒントを使用するか、第40回で説明したPARALLEL_STATEMENT_CRITICALパラメータがBYPASS_QUEUEに設定しているユーザーで実行すると、PSQをバイパスしてすぐに実行できます。   (3)DBRMを使用したパラレル処理の管理 次に、PSQの優先順位が設定できるDBRMについて説明します。 第40回の「パラレル・ステートメント・キューイングのリソース管理について」で説明したように、DBRMと一緒に使用してリソース・コンシューマ・グループ(コンシューマ・グループ)ごとにPSQのキューを管理することで、以下のことができるようになります。 個々のSQL文やコンシューマ・グループ全体で使用できるPXサーバー数を制御する 優先順位の高いコンシューマ・グループに、より多くのPXリソースを割り当てる ユーザー毎に異なるキューを割り当て、優先順位の高い要求が低い要求の後にキューイングさせない そのため、同時実行環境でパラレル実行する場合は、DBRMで複数のコンシューマ・グループを使用することを強くお勧めします(この後のAutonomous Databaseの設定を参考にすると良いと思います)。 また、これから使用する機会が多い(Autonomous Databaseでも使用されている)PDBのリソース管理についても説明しておきます。   PDB(プラガブル・データベース)について ご存知ない方のために、ここでマルチテナント・アーキティクチャ(MTA)のPDBについて簡単に説明します。 MTAは、データベースのマルチテナント(1つのシステムに複数のサービスが同居している環境)を効果的に実現するために、Oracle12cから一つのインスタンス(メモリー、バックグランド・プロセス)上で、複数の仮想的なデータベースを稼働するようにした機能です。インスタンスが動作するマルチテナント・コンテナ・データベース(CDB)と、それに内包される一つ以上のPDBで構成され、これまでのデータベース(非CDBと呼ぶ)からも変更せずに使用できます。このMTAにより、これまでのデータベース統合の課題(スキーマ分割はリソースを分割できない、複数データベースや仮想マシンは個々に必要なリソースや管理コストなどが増える)を以下のように解決し、ITコストの削減に貢献することが可能になります。 高密度の統合 CDB上のCPUやインスタンスを共有することで、リソースを有効に活用でき(PDB間の動的なリソース管理もでき)、高いパフォーマンスと集約密度(1サーバー当たりのデータベース数)の向上を実現します。 多数のデータベースの一元管理 アップグレードやパッチ適応、バックアップ/リカバリ、インスタンス・チューニングなどを、CDBレベルで行うことで、管理コストを削減します(CDB全体のバックアップからPDB単位でのリカバリ、他のPDBに影響させずにバッファ・キャッシュのフラッシュなども可能です)。 迅速なプロビジョニング 様々なプロビジョニング(CDB内に新しいPDBを作成する、既存のPDBをクローンする、既存の非CDBをPDBとしてCDBに移行、切断したPDBを異なるCDBに接続するなど)を簡単および迅速にできることで、いろいろな場面で効果的に使用することが可能になります。   PDBリソース管理は、以下のように、2つのレベルのリソース・プランをsharesパラメータ(共有値:リソースの割合)で作成して行います(DBRMを使用しないと、初期化パラメータだけで制御することになります)。 CDBリソース・プラン CDBリソース・プランを作成し、優先度に従ってPDBごとにリソースを配分します(作成しないでPDBリソース・プランが作成されると、付属のDEFAULT_CDB_PLANが使用されます)。sharesは、PDBに対するソフト・リミットになるので、CPUリソースの上限値はutilization_limitパラメータ(またはCPU_COUNT初期化パラメータ)、PXサーバー数の上限値はparallel_server_limitパラメータ(またはPARALLEL_SERVERS_TARGET初期化パラメータ)で制御します(Oracle12cR2からメモリー関連やI/O関連も初期化パラメータで指定できます)。 PDBリソース・プラン PDBに接続してPDBリソース・プランを作成し、PDB内のコンシューマ・グループごとにリソースを配分します。これまでの非CDBリソース・プランと同じように行いますが、いくつかの制限(ディレクティブのレベル数、コンシューマ・グループ数、サブプランを設定できない)があります。リソース割り当ては、マルチレベル管理ディレクティブ(mgmt_p2からmgmt_p8)は使用できないので、mgmt_p1またはOracle12cからのsharesを使用します(推奨はsharesです)。 以下に、設定している例を載せておきます(CPUリソースはCPU_COUNTを使用しています)。このCDBリソース・プランは、DEFAULT_CDB_PLANと同じですが、参考のために載せておきました。   -- CDBリソース・プラン作成 DECLARE l_plan VARCHAR2(30) := 'test_cdb_plan'; BEGIN DBMS_RESOURCE_MANAGER.clear_pending_area; DBMS_RESOURCE_MANAGER.create_pending_area; -- CDBのプラン作成 DBMS_RESOURCE_MANAGER.create_cdb_plan(plan=>l_plan, comment=>'test CDB resource plan'); -- PDB(pdb1)にリソース配分 DBMS_RESOURCE_MANAGER.create_cdb_plan_directive(plan=>l_plan, pluggable_database=>'pdb1', shares=>1); …<他のPDBは省略>… DBMS_RESOURCE_MANAGER.validate_pending_area; DBMS_RESOURCE_MANAGER.submit_pending_area; END; / -- PDBリソース・プラン作成 ALTER SESSION SET CONTAINER = pdb1; -- PDB(pdb1)に接続 ALTER SYSTEM SET CPU_COUNT = 10 SCOPE = BOTH; BEGIN DBMS_RESOURCE_MANAGER.clear_pending_area; DBMS_RESOURCE_MANAGER.create_pending_area; -- PDBのプラン作成 DBMS_RESOURCE_MANAGER.create_plan(plan=>'pdb1_plan', comment => 'pdb1 Plan'); -- コンシューマ・グループの作成とサービスのマッピング DBMS_RESOURCE_MANAGER.create_consumer_group(consumer_group=>'high', comment=>'high priority'); DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute=>DBMS_RESOURCE_MANAGER.SERVICE_NAME, value=>'pdb1_high', consumer_group=>'high'); -- プラン・ディレクティブをコンシューマ・グループに割り当てて優先順位やリソース制限を定義する DBMS_RESOURCE_MANAGER.create_plan_directive(plan=>'pdb1_plan', group_or_subplan=>'high', comment=>'High Priority - level 1', shares=>4, parallel_server_limit=>50, parallel_degree_limit_p1=>10); …<他のコンシューマ・グループは省略>… DBMS_RESOURCE_MANAGER.validate_pending_area; DBMS_RESOURCE_MANAGER.submit_pending_area; END; /   2. Oracle Autonomous Database 最後に、パラレル実行と同時実行が簡単に利用できるAutonomous Database(ADW:Autonomous Data WarehouseとATP:Autonomous Transaction Processing)について説明します。 同時実行する環境では、様々なことを考えて設定する必要があり、大変と思っている方も多いのではないでしょうか。そのため、何もしなくても効果的に実行できるように、事前設定されているのがAutonomous Databaseです(パラレルDMLもデフォルトで有効になっています)。また、管理を簡単にするために、それぞれをPDBとして作成されています。ここでは、参考のために、どのように設定されているかを簡単に説明します(非常によく考えられているので、参考になると思います)。 Autonomous Databaseでは、以下のようなコンシューマ・グループ(サービス)で事前定義されているので、どのサービスを使用するかを検討するだけになります(TPとTPURGENTはATPのみです)。そのため、性能を改善するには、OCPU(Oracle Compute Units:CPUコア)の数を増やすだけになります(OCPU数に比例して、メモリーサイズ、I/O帯域幅が拡張されるようになっています)。 サービス (コンシューマ・グループ) SHARES パラレル制御 PX Degree Limit PX Server Limit 同時実行セッション数 HIGH 4 自動DOP CPU_COUNT 50% 3(MEDIUMが存在しない場合) MEDIUM 2 自動DOP 4 84% 1.25×OCPU LOW 1 シリアル 1 - 100×OCPU TP 8 シリアル 1 - 100×OCPU TPURGENT 12 手動 - - 100×OCPU OTHER_GROUP 1 シリアル 1 - 100×OCPU   パラレル実行する場合は、HIGHとMEDIUMの2つのグループで最大DOPを使い分けることが可能です。初期化パラメータPARALLEL_MAX_SERVERSとPARALLEL_SERVERS_LIMITは、どちらもデフォルトではなく(3×4×<デフォルトDOP>)になっているので、HIGHの同時実行数は、MEDIUMの問合せが存在しない場合にpx_server_limitが50%から3となります(MEDIUMの問合せが存在する場合は、px_server_limitが84%になっているので、少なくても1つのSQL文は実行できます)。ATP用のTPURGENTは、最も優先度の高い(緊急度の高い)処理として、手動DOP(ヒントや表レベル)を使用できるようになっています。OTHER_GROUPは、デフォルト・コンシューマ・グループ(HIGH~TPURGENT)を使用しないときのグループになります。 sharesの割合は、2019/04新機能からCS_RESOURCE_MANAGER.update_plan_directiveプロシージャを使用して変更できるようになっています(OCIコンソールの「サービス・コンソール」→「Administration」→「Set Resource Management Rules」からも行えます)。例えば、以下のように変更(HIGHが6、MEDIUMが2、LOWが1)を行います。sharesは、ソフト・リミットになるので、高負荷のときに保証される割合になります。 BEGIN CS_RESOURCE_MANAGER.update_plan_directive(consumer_group => 'HIGH', shares => 6); CS_RESOURCE_MANAGER.update_plan_directive(consumer_group => 'MEDIUM', shares => 2); CS_RESOURCE_MANAGER.update_plan_directive(consumer_group => 'LOW', shares => 1); END; / コンシューマ・グループの設定内容は、以下のSQL(DBA_RSRC_PLAN_DIRECTIVESビュー)で、リソース・プラン・ディレクティブから確認できます('DWCS_PLAN'がADW、'OLTP_PLAN'がATP、そしてsharesがmgmt_p1になります)。 SQL> SELECT plan, group_or_subplan name, mgmt_p1 shares, parallel_server_limit, parallel_degree_limit_p1 2 FROM dba_rsrc_plan_directives 3 WHERE plan IN ('DWCS_PLAN', 'OLTP_PLAN') 4 ORDER BY 1,3 DESC ; PLAN NAME SHARES PARALLEL_SERVER_LIMIT PARALLEL_DEGREE_LIMIT_P1 ---------- ------------ ---------- --------------------- ------------------------ DWCS_PLAN HIGH 6 50 10 DWCS_PLAN MEDIUM 2 84 4 DWCS_PLAN LOW 1 1 DWCS_PLAN OTHER_GROUPS 1 1 このようにクラウド環境でもあるので、非常に簡単に管理ができるようになっています(慣れた管理者には、少し不自由さを感じるかもしれませんが、管理するシステムを削減できると思えば嬉しいことだと思います)。 Autonomous Databaseに興味を持たれた方は、「Autonomous Database ハンズオンラボ(HOL)」にハンズオン情報がありますので、使用してみてください。   3. おわりに 今回は、パラレル実行とリソース管理について説明しましたが、少しは参考になりましたでしょうか。これからも頑張りますので、今年もよろしくお願いします。 それでは、次回まで、ごきげんよう。 ページトップへ戻る▲    津島博士のパフォーマンス講座 Indexページ ▶▶

津島博士のパフォーマンス講座 Indexページ ▶▶   皆さん、明けましておめでとうございます。今年も素敵な一年になりますようお祈り申し上げます。 今回は、苦労されている方も多い、パラレル実行と同時実行のリソース管理について説明しようと思います。後半に、これが簡単に利用できるようになっているOracle Autonomous Databaseについても説明していますので、参考にしてください。   1....

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

Oracle Cloud Infrastructure ハンズオン 「Oracle Cloud Infrastructure」は、オンプレミスからの大規模ワークロード移行に完全対応する次世代インフラ基盤です。本セミナーでは、Oracle Cloud Infrastructureのサービス概要のご紹介とともに、実際のクラウド環境を利用したハンズオンを半日で体感いただけます。 2/7【2月第1回】Oracle Cloud Infrastructure ハンズオン 2/18【2月第2回】Oracle Cloud Infrastructure ハンズオン 一歩進んだ分析でビジネスにチカラを!! ~あなたもデータ アナリスト~ Autonomous Database CloudおよびOracle Analytics Cloudの特徴や活用法についてご紹介し、実際にサービスを利用し、起動からデータロード、分析まで一連の流れを体感いただけます。 2/12 【2月第1回】一歩進んだ分析でビジネスにチカラを!! ~あなたもデータ アナリスト~ 2/19 【2月第2回】一歩進んだ分析でビジネスにチカラを!! ~あなたもデータ アナリスト~ 2/27 実践Kubernetesハンズオン ~OKEでKubernetesを体験しよう~ Oracle Cloudは大規模なコンテナの管理、デプロイおよび運用に適したクラウドサービスを複数提供しています。本ハンズオンセミナーではOracle Cloudが提供するコンテナ・サービスを実際に利用しKubernetes環境の構築からコンテナ・アプリケーションのデプロイ、CI/CDまでの一連の流れを体感いただけます。 AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。

Oracle Cloud Infrastructure ハンズオン 「Oracle Cloud Infrastructure」は、オンプレミスからの大規模ワークロード移行に完全対応する次世代インフラ基盤です。本セミナーでは、Oracle Cloud Infrastructureのサービス概要のご紹介とともに、実際のクラウド環境を利用したハンズオンを半日で体感いただけます。 2/7【2月第1回】Oracl...

OCIでブロック・ボリュームを複数インスタンスにアタッチする

  すばらしいお知らせがあります。ブロック・ストレージ・ボリュームを共有して、Oracle Cloud Infrastructure上の複数のコンピュート・インスタンスに接続できるようになりました。 これまでは、複数のブロック・ボリュームを1つのコンピュート・インスタンスに接続することはできましたが、ボリュームを接続して読取り/書込みアクセスができるのは、一度に1つのコンピュート・インスタンスからのみに制限されていました。他のパブリック・クラウド・ベンダーでも同じような設計上の制限がありますが、一部のベンダーでは、共有の読取り専用ボリュームへのアクセスが可能な場合もあります。 Oracle Cloud Infrastructureからの本発表に伴って、読取り/書込みアクセスに対応する共有可能なボリュームを、複数のコンピュート・インスタンスに接続できるようになりました。クラウド・ストレージでのこの独自の機能により、Oracle Cloud Infrastructure Block Volumeサービスが提供する共有可能なボリュームを使用して、クラスタ対応ソリューションをデプロイおよび管理できます。   クラスタ対応ソリューションによる同時書込み この機能自体が、複数のコンピュート・インスタンスからの同時書込みの調整を処理するわけではありません。クラスタ対応のシステムまたはソリューションを別途用意して、複数インスタンスが接続された共有ストレージの上位にデプロイする必要があります。複数の同時書込みを調整できるクラスタ対応ソリューションには、Oracle RAC(入手およびサポートはOracle Cloud Infrastructure Databaseサービスを通した場合のみ)、Oracle Cluster File System(OCFS2)、IBM Spectrum Scale(オラクルとIBMのパートナーシップを通して利用可能)、GlusterFSなどがあります。 複数インスタンスの接続とOCFS2を使用するソリューションのセットアップ方法の例について詳しくは、「複数インスタンスへのブロック・ボリュームのアタッチ機能を利用したOracle Cloud Infrastructure上の共有ファイル・システムの作成」というブログ記事をご覧ください。   ユースケース この新しい機能は、2017年5月に登場したOracle Cloud Infrastructure Databaseサービスをサポートすることをおもな目的としていました。しかし当社は、この機能を一般にも公開してほしいという多数の要望を受け取りました。この機能によって、可用性が高く、耐久性があり、費用対効果に優れ、柔軟なパフォーマンスを発揮するブロック・ボリュームの利点を活用し続けることができ、クラスタ対応のデプロイメントと、この機能の処理に対応した任意のソリューションを作成および管理できるようになりました。 たとえば、以下のようなユースケースがあります。 大規模データベースに合わせて高度に最適化された共有ファイル・システム 共有ディスクによるパッチ適用と構成の管理 書き込むユーザーが1人、読み取るユーザーが複数いる共有カタログ・コンテンツ 分散システムから共有リポジトリへのログ・ファイルのアップロード この複数インスタンスの接続機能は、オラクルがIO500ベンチマークのトップ15にランクインするに当たっても重要な要素となりました(「Oracle Cloud Joins the IO500 Fastest File Systems in the World」をご覧ください)。   使ってみよう デフォルトでは、ボリュームは、同一機能を持ち常に使用可能な1つのインスタンスからの共有不可能な読取り/書込みアクセスのみが有効な状態で接続されます(ボリュームへの最初の接続で共有可能な読取り/書込みアクセスのオプションが選択されている場合を除く)。ボリュームへの共有可能な接続が確立されると、その後に設定される、同一ボリュームへの他のインスタンスからの接続も共有可能になります。 さらにオラクルは、読取り専用接続も共有できるようにしました。読取り専用アクセスでボリュームを接続すると、複数のコンピュート・インスタンスへの共有可能な読取り専用接続が有効になります。同時アクセス制御やクラスタ対応ソリューションがなくても、読取りを行う複数のユーザー間で共有ブロック・ボリュームのコンテンツを共有できます。 読取り/書込みアクセス用のブロック・ボリュームの共有は簡単でシームレスです。コンソールのAttach Block Volumeのページで1回クリックするだけで済みます。 複数インスタンスが接続された共有可能ボリュームにも、ボリューム・グループ、ディスクからディスクへのディープ・クローン、自動化されたバックアップなど、すべてのブロック・ボリューム機能を適用できます。デタッチされた既存のボリュームでも、未接続の新規ボリュームでも、複数インスタンスの接続機能を利用できます。以前に共有可能な読取り/書込みアクセスで接続されなかったボリュームを共有可能にしたい場合は、ボリュームをデタッチして、共有可能なボリュームとして再接続します。これまでと同様のOracle Cloud Infrastructure Block Volume SLAを引き続き適用できます。 複数インスタンスが接続された共有可能なブロック・ボリュームは、Oracle Cloud Infrastructure Console、CLI、SDK、Terraformを通して、Oracle Cloud Infrastructureのすべてのリージョンで使用できます。この機能に関して詳しくは、ブロック・ボリュームのドキュメントをご覧ください。 ブロック・ボリューム・ストレージに関する情報は、ブロック・ボリューム・サービスの概要およびFAQをご覧ください。OCFS2について詳しくは、OCFS2のユーザー・ガイドをご覧ください。 特徴と機能についての詳しい情報は、本ブログでの発表をご確認ください。エンタープライズの皆様に当社のクラウド・サービスを引き続き役立てていただくために、当社では皆様からのフィードバックを歓迎しています。当社がより良いサービスをご提供し続けるためのアイディアをお持ちの場合や、トピックに関わらず詳しい情報をお知りになりたい場合は、私までお問い合わせください。 ※本記事は、Max Verun (Principal Product Manager)による”Announcing Multiple-Instance Attachment of Shareable Read/Write Block Volumes“を翻訳したものです。

  すばらしいお知らせがあります。ブロック・ストレージ・ボリュームを共有して、Oracle Cloud Infrastructure上の複数のコンピュート・インスタンスに接続できるようになりました。 これまでは、複数のブロック・ボリュームを1つのコンピュート・インスタンスに接続することはできましたが、ボリュームを接続して読取り/書込みアクセスができるのは、一度に1つのコンピュート・インスタンスからのみに...

Data SafeでAutonomous Data Warehouseの安全性を維持する - パート2

この連載のパート1では、既存のADWおよびOCI環境でData Safeを使用するための準備作業について確認しました。まだパート1を読んでいない場合や、ざっと復習したい場合は、こちらをご覧ください。 https://blogs.oracle.com/otnjp/keeping-your-autonomous-data-warehouse-secure-with-data-safe-part-1-ja パート2では、新しくデプロイしたData Safe環境をAutonomous Data Warehouseインスタンスに接続するプロセスについて確認していきます。オラクルのクラウド・サービスはどれもそうですが、Data Safeコントロール・センターは特定のOCI地域データセンター内にデプロイされています。このため、別のデータセンターに切り替える場合は、新しくData Safe環境をデプロイする必要が生じるのでご注意ください。 Data Safeサービス・コンソールの起動 パート1の最後でFrankfurtデータセンター内のData Safeを有効化しました。このため、新しく作成したOCI資格証明を使用してOracle Cloudにログインすると、ハンバーガー・メニューにData Safeが表示されます。 「Data Safe」を選択すると、Data Safeランディング・パッド・ページが表示されます。次のステップでサービス・コンソールを起動します(ランディング・パッド・ページには「Service Console」ボタンしか表示されないのに、なぜ自動的にサービス・コンソールを表示しないのかと不思議に感じるかもしれません。とても良い疑問です。この連載の最後にはランディング・パッド・ページにもっと多くの情報が表示されるので、そのときもう一度この点について考えましょう)。  「Service Console」ボタンをクリックすると、次のような新しいウィンドウが表示されます。   この時点では、グラフにもその他のページにも情報は表示されていません。これはまだデータウェアハウス・インスタンスを登録していないからです。登録は次のステップで行います。  Data SafeへのADWの登録 Data Safeライブラリに含まれるレポートを生成するには、Data SafeにADWを登録する必要があります。 はじめに、ページ上部のメニューから「Targets」タブを選択します。 「Register」ボタンをクリックするとフォームが表示されるので、ADWの接続情報を入力します。 Data Safeと連携できるのはAutonomous Databaseだけではありません。以下のクラウド・データベースも登録できます。 ただし、Data Safeでサポートされているのはサーバーレス・デプロイメントのAutonomous Databaseのみであり、"専用サーバー"は現在サポートされていません。詳しい情報はこちらを参照してください。 https://docs.oracle.com/en/cloud/paas/data-safe/udscs/supported-target-databases.html ADW(およびATP)の場合、最初に接続タイプをTLSに変更します。変更すると、フォームに追加フィールドが表示されます(通常DI/ETLまたはBIツールからADWの接続を設定している場合、この方法が一番わかりやすいでしょう)。  ADWインスタンスの登録には多くの情報を入力する必要があると思えるかもしれませんが、ご安心ください。ほとんどの必要情報を含んだzipファイルをOCI ADBコンソールからダウンロードできます。実質的に必要なのはインスタンスのウォレット・ファイルですが、その前にフォーム上部のフィールドを入力しましょう。   残るはADWインスタンスに関する情報です。入手方法を次のステップで説明します。 ADW接続情報の収集 ADWインスタンスのOCIコンソール・ページに切り替えて、"OCID"というラベルの行を見つけます。これが最初に入手する情報です。"Show"と"Copy"という2つのリンクがあります。 「Copy」をクリックしてからData Safeページに戻り、「OCID」フィールドに貼り付けます。このほかに、ホスト名、ポート、サービス名、ターゲット識別名、セキュア・ウォレット・ファイルなどの情報が必要です。これらを入手するため、ウォレット・ファイルをダウンロードします。ウォレット・ファイルにアクセスするには、「DB Connection」ボタンをクリックします。ポップアップ表示されたフォームで、「Download Wallet」ボタンをクリックしてパスワードを入力します。あとで必要になるので、このパスワードをメモしておいてください。 ファイルをダウンロードしたら、ファイルシステム上にあるファイルを解凍します。解凍したフォルダには以下のファイルが含まれます。  Data Safeのターゲット登録フォームに戻りましょう。次に入力する4つのフィールドのデータはtnsnames.oraファイルから確認できます。Data Safeレポートの実行は緊急性の高いワークロードではないので、今回の接続には"low service"を使用します。 "low service"についてご存知ない場合は、ADWドキュメントのセクション12の「Managing Concurrency and Priorities on Autonomous Data Warehouse」にざっと目を通すことをおすすめします。簡単に言うと、ADWインスタンスに接続するとき、low、medium、highのいずれかのサービスを選ぶ必要があります。このサービスは以下の特性を持つコンシューマ・グループ(LOW、MEDIUM、またはHIGH)にマッピングされます。      HIGH:リソース: もっとも多い、同時実行性: もっとも低い。問合せは並列で実行。 MEDIUM:リソース: HIGHより少ない、同時実行性: HIGHより高い。問合せは並列で実行。 LOW:リソース: もっとも少ない、同時実行性: もっとも高い。問合せは順番に実行。 いずれにせよ、ジョブが実行されれば問題ありません。tnsnames.oraファイルからlow-service接続用の情報を探します。以下のような行が見つかります。  adwdemo_low = (description=(address=(protocol=tcps)(port=1522)(host=xxxxxx.oraclecloud.com))(connect_data=(service_name=xxxxx_adwdemo_low.xxxxxxx))(security=(ssl_server_cert_dn="CN=xxxxxx.oraclecloud.com,OU=Oracle,O=Oracle Corporation,L=Redwood City,ST=California,C=US"))) host、port、service_name、ssl_server_cert_dnの値をコピーして、"TLS"プルダウン・メニュー項目の下にある4つのフィールドに貼り付けます。 フォームは以下のようになります。 あと少しです。Wallet Typeに"JKS Wallet"を設定します。Certificate/Walletには、ダウンロード後に解凍した接続ファイル"truststore.jks"を指定します。同じディレクトリ/フォルダにある"keystone.jks"ファイルをKeystore Walletに指定します。その下のフィールドには、zipされた接続ファイルをダウンロードするときにOCI ADWコンソール・ページで使用したパスワードを入力します。 最後に、ADWインスタンスのユーザー名/パスワードを入力します。このユーザーは、この連載のパート1で作成したDATASAFEです。      「Test Connection」ボタンをクリックする前に、PL/SQLスクリプトを実行する必要があります。このスクリプトは、新しいデータベース・ユーザーであるDATASAFEに権限を付与して、Data Safeのさまざまなチェックを問題なく実行できるようにするものです。 ダウンロード・ボタンをクリックして、dscs_privileges.sqlというPL/SQLスクリプトを見つけます。SQL Developer(または任意のツール)を使用し、標準のADMINユーザーとしてログインしてからスクリプトを実行します(コピー&貼付けを使用)。スクリプトのログをチェックすると、以下のようなメッセージが確認できます。 Enter value for USERNAME (case sensitive matching the username from dba_users) Setting USERNAME to DATASAFE Enter value for TYPE (grant/revoke) Setting TYPE to GRANT Enter value for MODE (audit_collection/audit_setting/data_discovery/masking/assessment/all) Setting MODE to ALL Granting AUDIT_COLLECTION privileges to "DATASAFE" ...  Granting AUDIT_SETTING privileges to "DATASAFE" ...  Granting DATA_DISCOVERY role to "DATASAFE" ...  Granting MASKING role to "DATASAFE" ...  Granting ASSESSMENT role to "DATASAFE" ...  Done. Disconnected from Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production Version 18.4.0.0.0 以上で接続をテストする準備が整いました。「Test Connection」ボタンをクリックすると、接続に成功し、緑のチェック・マークが表示されます。   最後に「Register Target」をクリックします。新しく登録したデータベースの情報がTargetページに表示されます。     ここではADWを新規ターゲットとして登録しただけなので、ホームページを含むその他のページはまだ空白です。     パート2のまとめ パート1では、Data Safeを使用できるように環境を準備し、地域データセンター(今回はFrankfurtデータセンター)内でData Safeを有効化しました。パート2では、既存のADWインスタンスを新しいターゲット・データベースとしてData Safeに登録しました。   パート3を近日公開予定 次の記事では、Data Safeに含まれるデータ検出機能とデータ・マスキング機能について確認する予定です。   詳細情報 オラクルのセキュリティ・チームは、Data Safeの学習に役立つコンテンツを多数作成しています。ここでは、個人的に使用しているブックマークを紹介します。 ドキュメント - まずはここから: https://docs.oracle.com/en/cloud/paas/data-safe/udscs/oracle-data-safe-overview.html Oracle.comのData Safeページ - https://www.oracle.com/database/technologies/security/data-safe.html データベース・セキュリティ・ブログ: https://blogs.oracle.com/cloudsecurity/db-sec https://blogs.oracle.com/cloudsecurity/keep-your-data-safe-with-oracle-autonomous-database-today https://blogs.oracle.com/cloudsecurity/keeping-your-data-safe-part-4-auditing-your-cloud-databases   ※本記事は、Keith Laker (Senior Principal Product Manager)による”Keeping Your Autonomous Data Warehouse Secure with Data Safe Part 2“を翻訳したものです。

この連載のパート1では、既存のADWおよびOCI環境でData Safeを使用するための準備作業について確認しました。まだパート1を読んでいない場合や、ざっと復習したい場合は、こちらをご覧ください。 https://blogs.oracle.com/otnjp/keeping-your-autonomous-data-warehouse-secure-with-data-safe-part-1-ja...

Data SafeでAutonomous Data Warehouseの安全性を維持する - パート1

サンフランシスコで開催されたOpenWorld 2019では、重要な発表の1つにOracle Data Safeがありました。Data Safeは、Oracle Autonomous Data Warehouse(ADW)に対応したまったく新しいクラウドベースのセキュリティ・コントロール・センターで、完全に無償でご使用いただけます。    具体的にはどんな機能があるのでしょうか。簡単に言うと、Oracle Data Safeは、不可欠なデータ・セキュリティ機能をOracle Cloud Infrastructureのサービスとして提供します。これらのサービスは、データの機密性の把握、データ・リスクの評価、機密データのマスキング、セキュリティ管理の実装と監視、ユーザー・セキュリティの評価、ユーザー活動の監視、データ・セキュリティ・コンプライアンス要件への対応に役立ちます。   こちらの短い動画が参考になります。     Data Safeコンソール Data Safeのメイン・コンソールであるダッシュボード・ページは次のように表示されます。  Autonomous Data Warehouse内に保存された各種のデータセットを直接確認できるので、次の処理が可能になります。     データベースがセキュアに構成されているかどうかを評価する GDPRの条項/備考、Oracle DatabaseのSTIGルール、CISベンチマークの推奨事項に基づいてリスクを調査し、軽減する 重要なユーザー、ロール、権限を明確にすることで、ユーザー・リスクを評価する 監査ポリシーを設定し、ユーザー・アクティビティを収集して、異常な行動を特定する 機密データを見つけて、その保存場所を把握する 機密データをマスキングして、本番以外のデータセットからリスクを除外する 既存のAutonomous Data WarehouseをData Safeに接続するのはごく簡単です。ここからは、その方法とデータセットに対して実行できるセキュリティ・レビューの種類を紹介しましょう。 Data Safeと連携するためのADWの設定 ここでは設定を簡単にするため、既存のADWインスタンスにLOCAL_SHという新規ユーザーを作成します。続いて、デモグラフィック、国、顧客の各表を、読取り専用の販売履歴デモ・スキーマから新しいlocal_shスキーマに追加でコピーします。  これにより、ADWをData Safeに接続したときにData Safeが検出する"機密"のデータ・ポイントがスキーマに含まれます。 CREATE USER local_sh IDENTIFIED BY "Welcome1!Welcome1"; GRANT DWROLE TO local_sh; CREATE TABLE local_sh.supplementary_demographics AS SELECT * FROM sh.supplementary_demographics; CREATE TABLE local_sh.customers AS SELECT * FROM sh.customers; CREATE TABLE local_sh.countries AS SELECT * FROM sh.countries; 顧客表にはどのようなデータが含まれるのでしょうか。 間違いなく個人の特定につながりそうな列がいくつかありますね。このような列は、開発者チームやビジネス・ユーザーに表示されないようにするか、マスキングしなくてはなりません。   ここまでで、非常に機密性の高いデータが含まれることが確認できました。 今回の例とは異なり、まっさらなADWにデータをロードする必要がある場合は、オラクルのドキュメント・ガイドに記載された手順を参照してください。このガイドでは、ユーザー・データをADWにロードする方法について説明しています。 https://docs.oracle.com/en/cloud/paas/autonomous-data-warehouse-cloud/tasks_load_data.html   次に、Data Safe接続プロセスで使用するユーザーをADWインスタンスに作成します。新しく作成することで、セキュリティ・レビュー・プロセスがいずれかのアプリケーション・ユーザーに関連付けられることを回避します。 CREATE USER datasafe IDENTIFIED BY "Welcome1!DataSafe1"; GRANT DWROLE TO datasafe; このあとで、インストール・スクリプトを実行する必要がありますが、スクリプトはデータベースの登録時にData Safeコンソールから入手できます。既存のアプリケーション・ユーザーに関連付けないようにしたのは、Data Safeの実行に必要なロールと権限をこのユーザーに付与するからです。   Oracle Cloudアカウントの種類の確認 Oracle Cloudを初めて使用する場合や、アカウントを作成したのが過去12か月以内である場合は、このセクションをスキップして問題ないでしょう。Oracle Cloudのクラウド・アカウントを2年以上前に作成している場合は、Data Safeにアクセスしようとすると、連携アカウントに関する警告メッセージが表示される可能性があります。 Data Safeへのアクセス クラウド・アカウントにログインし、左上にあるハンバーガー(3本の横線)メニューをクリックします。ポップアップ・メニューで、Autonomous Transaction Processingの下にData Safeが表示されます。   「Data Safe」をクリックすると、次のような画面が表示される場合があります。このメッセージが表示されない場合は、"Data Safeの有効化"セクションに進んでください。   メッセージが表示されても心配ありません。必要な作業は新しいOCIユーザーを作成することだけです。ハンバーガー・メニュー・リスト内を下方向にスクロールし、"Governance and Administration"が表示されたら、「Identity」→「Users」の順に選択します。 画面上部にある青色の大きな「Create User」ボタンをクリックし、各フィールドに値を入力して新規OCIユーザーを作成します。このユーザーはあとでData Safe環境の所有者として使用します。 新規ユーザーを作成した後に届く「ようこそ」メールには、以下のようにパスワードをリセットするためのリンクが記載されています。 新規ユーザーを作成したら、Data Safeがテナント内でリソースおよび自律型データベース・インスタンスにアクセスできるようにするため、正しい権限をすべて有効化する必要があります。 ここでは、DataSafeというユーザーのために、Data Safeで必要になる権限をすべて含んだ“administrators”グループをテナント内に作成してあります。  実際には、より慎重な方法として、Data Safe管理者専用の新規グループをセットアップし、このグループだけにOCI権限を割り当てると良いでしょう。このプロセスについての詳細情報はこちらを参照してください。 https://docs.oracle.com/en/cloud/paas/data-safe/udscs/required-permission-enabling-oracle-data-safe.html 簡単なおさらい ここでは、あらかじめセットアップしてあったAutonomous Data Warehouseインスタンスを使用しました。SQL Developerを使用して新規ユーザーを作成し、潜在的な機密情報を含む小さなデータセットの所有者にしました。機密データ・ポイントを含むことがわかっている既存のスキーマから、いくつかの表をコピーして、作業用データセットを作成しました。また、新しいOCIユーザーに対して、Data Safe導入環境の所有者として使用できるように設定しました。   Data Safeの有効化 ここまでで、Data Safeを使用する準備が整いました。ハンバーガー・メニューから「Data Safe」を選択します。このテナント・リージョンでのログインが初めての場合は、次のような画面が表示されます。画面から、Frankfurtデータセンターを使用中で、データセンターへのログインは今回が初めてであることがわかります。 次の段階へ進むには、大きな青色のボタンをクリックしてData Safeを有効化します。いつもの"作業中..."画面が表示されます。   その後、"準備完了"画面になります。   パート1のまとめ パート1はここまでです。次回は、Autonomous Data Warehouseインスタンスの登録方法を確認してから、顧客に関する機密データを含むオブジェクトを突き止めるときに役立つセキュリティ・レポートを実行します。 詳細情報 オラクルのセキュリティ・チームは、Data Safeの学習に役立つコンテンツを多数作成しています。ここでは、個人的に使用しているブックマークを紹介します。 ドキュメント - ますはここから: https://docs.oracle.com/en/cloud/paas/data-safe/udscs/oracle-data-safe-overview.html Oracle.comのData Safeページ - https://www.oracle.com/database/technologies/security/data-safe.html データベース・セキュリティ・ブログ: https://blogs.oracle.com/cloudsecurity/db-sec https://blogs.oracle.com/cloudsecurity/keep-your-data-safe-with-oracle-autonomous-database-today https://blogs.oracle.com/cloudsecurity/keeping-your-data-safe-part-4-auditing-your-cloud-databases ※本記事は、Keith Laker (Senior Principal Product Manager)による”Keeping Your Autonomous Data Warehouse Secure with Data Safe Part 1“を翻訳したものです。

サンフランシスコで開催されたOpenWorld 2019では、重要な発表の1つにOracle Data Safeがありました。Data Safeは、Oracle Autonomous Data Warehouse(ADW)に対応したまったく新しいクラウドベースのセキュリティ・コントロール・センターで、完全に無償でご使用いただけます。    具体的にはどんな機能があるのでしょうか。簡単に言うと、Oracl...

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

1/28 Oracle Cloud Infrastructure ハンズオン 「Oracle Cloud Infrastructure」は、オンプレミスからの大規模ワークロード移行に完全対応する次世代インフラ基盤です。本セミナーでは、Oracle Cloud Infrastructureのサービス概要のご紹介とともに、実際のクラウド環境を利用したハンズオンを半日で体感いただけます。 1/31 実践Kubernetesハンズオン ~OKEでKubernetesを体験しよう~ Oracle Cloudは大規模なコンテナの管理、デプロイおよび運用に適したクラウドサービスを複数提供しています。本ハンズオンセミナーではOracle Cloudが提供するコンテナ・サービスを実際に利用しKubernetes環境の構築からコンテナ・アプリケーションのデプロイ、CI/CDまでの一連の流れを体感いただけます。 AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。

1/28 Oracle Cloud Infrastructure ハンズオン 「Oracle Cloud Infrastructure」は、オンプレミスからの大規模ワークロード移行に完全対応する次世代インフラ基盤です。本セミナーでは、Oracle Cloud Infrastructureのサービス概要のご紹介とともに、実際のクラウド環境を利用したハンズオンを半日で体感いただけます。 1/31...

クイズに挑戦:ループ構造の比較(中級者向け)

ユーザーの手動入力をループ処理する場合に使用するループ構造、そこには多くの微妙な違いが... 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しめのものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 ループ構造を比較したいと思います。 以下のことを行うインタラクティブなコンソール・アプリケーションを書いているものとします。 プロンプトを表示する。 入力としてコマンドを読み取る。 入力がQだった場合、終了する。そうでない場合は、指定されたコマンドを実行し、手順1に戻る。   次のコード構造のうち、このシナリオを実装するために最適な選択肢はどれですか。1つ選んでください。 break文があるforループ 拡張forループ continue文があるwhileループ do/whileループ   解答:この設問では価値判断を求めています。多くの場合、こういった選択問題は悩ましいものです。しかし、この設問で求められているのはかなり一般的な判断です。すべての説明が終わるころには、皆さんにもそう思っていただけるはずです。 設問から2つの重要なポイントが読み取れます。この2つが、適切なループを選ぶ際の手がかりになります。1つ目は、手順1と2を少なくとも1回実行する必要があることです。つまり、少なくとも1回コマンドを読み取るまで、コードは終了しないはずであるということです。そのこととも密接に関連していますが、もう1つわかることは、コマンドを読み取ってからでなければ、プログラムを終了する判断はできないということです。 whileループを使おうとする場合、以上の要件が実装にどう影響するかを考えてみます。まずわかるのが、whileループの条件判定はループに入るときに行われるということです。つまり、条件判定はループ本体より先に実行されます。そのため、このループを使ってプログラムの実行と終了を制御する場合の1つの可能性としては、次の疑似コードに示すように、ループの開始前にプロンプトを表示してコマンドを読み取ることが考えられます。  プロンプトを表示する コマンドを読み取る While (コマンドが"Q"でない) コマンドを実行する ... 現時点では、このアプローチに問題はないように思えるかもしれません。しかし、さらに、次のプロンプトを表示して次のコマンドを読み取る必要があります。この処理はループごとに行う必要があるため、関連するコードをループの中に入れる必要があります。すなわち、同じコードを2か所に書かなければなりません。この条件は、コードに重複する部分が必要だと間接的に述べているのと同じことです。このコードは次のようになります。 プロンプトを表示する コマンドを読み取る While (コマンドが"Q"でない) コマンドを実行する プロンプトを表示する // 重複 コマンドを読み取る // 重複 End-while コードの重複は美しくなく、メンテナンスの際にエラーが起きやすくなります。別のアプローチとして、ループの前にダミーの値をコマンドに設定し、そのコマンドを実行しても何も行わないようにする方法も考えられます。こうすれば重複は回避されますが、実質的に動作を「ハッキング」する特殊な値を使っているため、やはりコードが複雑になり、理解しにくくなる可能性があります。 この問題は、whileループの使用にとって確かに打撃と考えられますが、価値判断を行う前に、他の可能性について考えてみます。 次は、標準forループについて見てみます。実は、forループは、whileループに飾り付けをしただけのものにすぎません。その飾りによって、ループ構造でよくある2つの状況に対処します。1つはループ内で使われる変数の初期化(同時に宣言が行われることもあります)、もう1つは反復に伴う、変数の更新です。しかし、forループの実体は、飾りが付いたwhileループにすぎません。その飾りのいずれかが今回のシナリオに役立つ可能性もありますが、プロンプトと入力読取りのコードが重複するという根本的な問題があることは変わりません。 次に、拡張forループについて見てみます。拡張forループは、Iterableオブジェクトのすべての要素を処理する際に、よりクリーンな手段を提供します(対象となるIterableは、Listなどのコレクションによって提供されるのが一般的ですが、常にそうであるとは限りません)。この問題では、コンソールから入力として項目を読み取る必要があり、入力値がQであればループは終了します。それに対して、拡張forループでは、Iterableから項目を取得し、利用できる項目がなくなったときに終了します。 ユーザーにプロンプトを表示して1行のテキストを読み取り、ユーザーの入力がQでなかった場合にStringを返し、ユーザーの入力がQだった場合には反復を終了するIterableオブジェクトを作ることはできますが、今回のシナリオで使うにはあまりに遠回りで複雑すぎます。この状況で拡張forループが推奨される理由が多いとは考えられないため、最後の選択肢について考えてみます。 残る選択肢は、do/whileループです。このループを繰り返すかどうかを決める条件判定は、do/while構造の最後に置かれます。これによって、いくつかの結果が生まれます。do/whileループは、常に少なくとも1回は実行されます。ループの条件判定に到達するためには、その本体を実行しなければならないからです。別の言い方をすれば、条件判定が行われる前にループの本体が実行されるということです。今回のシナリオには、こちらの方がはるかに適しています。ループ本体でプロンプトを表示し、コマンド入力を読み取ることができるからです。その結果、コードを重複させることなく、条件判定の前にプロンプトの表示と入力の読取りが行われることが保証され、クリーンでシンプルな構造が実現します。 この時点で、今回のシナリオに最適なのは選択肢Dであり、他の選択肢よりもはるかに優れていることは明らかでしょう。そのため、選択肢Dが正解で、A、B、Cは誤りであることがわかります。 いくつか補足しておきます。選択肢Aにはbreak文を使うとあり、選択肢Cにはcontinueを使うとあります。forとbreakの構造を代わりに使用することもできますが、いずれにしても、エレガントな解決策にはなりません。選択肢Aは、次のようにコーディングすることもできるでしょう。 for (;;) { // 事実上の無限ループ // プロンプトを表示する String input = // ユーザーの入力を読み取る if (input.equals("Q")) break; // ループの外に出る executeCommand(input); } しかし、do/whileループを使用したものに比べれば、やはり美しくありません。同じように、whileループでcontinueを使っても、クリーンな解決策に直結することはありません。 正解は選択肢Dです。   Java Magazine December 2019の他の記事 プロパティベース・テストを習得する Arquillian:簡単なJakarta EEテスト ArchUnitでアーキテクチャの単体テストを行う 新しいJava Magazine 作ってみよう:自分だけのテキスト・エディタ(パート1) クイズに挑戦:Collectorsの使用(上級者向け) クイズに挑戦:スレッドとExecutor(上級者向け) クイズに挑戦:ラッパー・クラス(中級者向け) 書評:Core Java, 11th Ed. Volumes 1 and 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 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しめのものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく...

クイズに挑戦:スレッドとExecutor(上級者向け)

ExecutorServiceを使って行う具体的な処理の詳細 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 設問(上級者向け):RunnableおよびCallableを使ってワーカー・スレッドを作成し、ExecutorServiceを使って複数のタスクを同時に実行したいと思います。次のクラスについて: class Logger implements Runnable { String msg; public Logger(String msg) { this.msg = msg; } public void run() { System.out.print(msg); } } さらに、次のコード部分について: Stream<Logger> s = Stream.of( new Logger("Error "), new Logger("Warning "), new Logger("Debug ")); ExecutorService es = Executors.newCachedThreadPool(); s.sequential().forEach(l -> es.execute(l)); es.shutdown(); es.awaitTermination(10, TimeUnit.SECONDS); すべてのimport文(表示されていません)が適切に構成されており、コードがコンパイルできると仮定した場合、出力される可能性があるものはどれですか。2つ選んでください。 Error Debug Warning Error Warning Debug Error Error Debug Error Debug   解答:試験対象には、ExecutorsクラスおよびExecutorServiceインタフェースに関連するトピックが含まれています。Executorsクラスが提供するExecutorServiceインタフェースを実装したスレッド・プールも同様です。 Javaの初期のバージョンでは、スレッドの作成や管理はプログラマーの責任でした。また、スレッドは作成が高価であり、限られた、カーネルレベルのリソースでもあるため、「スレッド・プール」を作って、少数のスレッドで独立した小さなジョブを数多く処理できるようにするのが一般的でした。スレッド・プールという考え方は、小さなバックグラウンド・ジョブごとにスレッドを作り、各ジョブの完了後にそのスレッドを破棄するという方法に代わるものです。スレッド・プールでは、少数のスレッドを作って構成し、まとまった仕事(たとえば、Runnableオブジェクト)をスレッドに渡せるようにします。仕事がないスレッドのうち最初のものがジョブを引き受けて実行し、そのジョブが完了すると、そのスレッドは別のジョブを探します。 スレッド・プールは、Java 5でJavaのAPIの一部になりました。ExecutorインタフェースおよびExecutorServiceインタフェースは、スレッド・プールと、スレッド・プールがサポートする操作を汎用化したものです。また、静的ファクトリ・メソッドを含むクラスから、多くのExecutorService実装のインスタンスを生成できます。そのクラスがExecutorsです。java.util.concurrentパッケージには、これら3つの型のほか、多数の高水準なクラスやインタフェースが含まれています。こういったクラスやインタフェースは、並行プログラミングでよく生じる問題に対処しようとする開発者に活用してもらうことを意図したものです。 ベース・インタフェースのExecutorは、Runnableを実装したタスクを実行できます。通常は、ExecutorServiceを使うことが多いでしょう。これはExecutorのサブインタフェースです。ExecutorServiceには、Callableインタフェースを実装したタスクを処理する機能と、スレッド・プールの停止を制御する機能が追加されています。Callableインタフェースを使うことで、マネージャ・タスクから非同期的にタスクの結果を取得できるようになります。多くの場合、このマネージャ・タスクのコードが最初にジョブを送信しますが、必ずそうであるとは限りません。 ExecutorやExecutorServiceの実装は、送信された仕事を実行するために特定の戦略を使う必要はありません。実装の中には、固定サイズのプールを使って同時実行するものもあります。その場合、新しい仕事は、スレッドが利用できるようになるまで待機します。ワークロードが増加した際により多くのスレッドを開始し、需要が少なくなった際にスレッドをクリーンアップする実装もあります。また、1つのスレッドを使って単純にジョブを逐次処理する実装もあります。すべては個々の実装次第です。そのため、プログラマーはアプリケーションのアーキテクチャのニーズに適した実装を注意深く選択する必要があります。Executorsクラスの3つのファクトリ・メソッドでは、前述の動作が生成されます。 newFixedThreadPool newCachedThreadPool newSingleThreadExecutor 最初の2つは複数のワーカー・スレッドを使うプールを作成しますが、newSingleThreadExecutorはすべてのタスクを1つのバックグラウンド・スレッドで次々に実行するサービスを作成します。 本設問のコードでは、キャッシュド・スレッド・プールを使っています。このタイプのExecutorServiceは、必要に応じて新しいワーカー・スレッドを生成し、一定期間使用されなかったスレッドをクリーンアップします。しかし、キャッシュド・スレッド・プールには、作成できるスレッドの最大数に制限がないという重大な欠点があります。この動作により、高負荷時にリソース消費量が高くなり、パフォーマンスが低下する可能性があります。 設問のコードで作成しているプールには複数のスレッドがあることを踏まえれば、プールに送信されたジョブはおそらく同時実行されると考えることができます。そのため、開始される順番がどうであれ、相対的な処理の進み具合を予測することはできません。つまり、出力されるメッセージはどんな順番にもなり得るということです。ここから、選択肢AとBがいずれも正解であることがわかります。 ExecutorServiceが、送信された各ジョブを実行するのは、多くても1回です。状況によっては、ジョブが実行されないことや、完了前に停止されることもあるかもしれません。しかし、ジョブが複数回実行されることはありません。つまり、メッセージが重複することは絶対にありません。よって、選択肢Cは誤りです。 ExecutorServiceに対してshutdownメソッドを呼ぶと、新しいジョブのリクエストは拒否されるようになりますが、実行は最後のジョブが完了するまで続きます。そのため、設問のコードでは、3つのメッセージのうちいくつかが表示されないということはありません。以上より、選択肢Dも誤りであることがわかります。 1点補足しておきます。10秒以内にシャットダウンが完了しない場合、コードは末尾に到達することから、選択肢Dは正解かもしれないと考える方もいるかもしれません。3つのメッセージが確実に表示されると断言することはできるのでしょうか。 ここで、いくつかの見解が関連してきます。まず、このジョブが完了するまでに10秒かかる可能性は非常に低いということです。また、2つの選択肢(AとB)が明らかに正解であることを考えれば、このような起こりそうもないことは除外できます。 もちろん、ホストで極端な状況が発生すれば、ジョブが10秒以内に完了しない可能性もあります。そこから、選択肢Dはやはり正解かもしれないと思うかもしれません。たとえば、設問のコードが起動した瞬間に、OSのアップデートのインストールが始まった場合を考えてみてください。設問のコードには、仮想マシンが強制シャットダウン中であることを示す内容は含まれていないことに注意してください。このスレッド・プールのスレッドは非デーモン・スレッドであるため、ジョブが完了するまで仮想マシンはシャットダウンされません。そのため、プログラムの実行が許可されれば、3つのメッセージが表示されます。 正解は選択肢AとBです。   Java Magazine December 2019の他の記事 プロパティベース・テストを習得する Arquillian:簡単なJakarta EEテスト ArchUnitでアーキテクチャの単体テストを行う 新しいJava Magazine 作ってみよう:自分だけのテキスト・エディタ(パート1) クイズに挑戦:Collectorsの使用(上級者向け) クイズに挑戦:ループ構造の比較(中級者向け) クイズに挑戦:ラッパー・クラス(中級者向け) 書評:Core Java, 11th Ed. Volumes 1 and 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版にわたってテクニカル・レビューを務めた。

ExecutorServiceを使って行う具体的な処理の詳細 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しか...

クイズに挑戦:ラッパー・クラス(中級者向け)

Integerラッパー・クラスを使って2つの整数のインスタンスを作成したときに、値を比較する正しい方法 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 設問(中級者向け):Boolean、Double、Integer、などのラッパー・クラスを使ったコードを作成したいと思います。 次のコード部分について: String one = "1"; Boolean b1 = Boolean.valueOf(one); // line n1 Integer i1 = new Integer(one); Integer i2 = 1; if (b1) { System.out.print(i1 == i2); } どのような結果になりますか。1つ選んでください。 line n1で実行時例外が発生する true false コードは実行されるが、何も出力されない   解答:この設問は、プリミティブのラッパー・クラス、とりわけBooleanクラスとそのファクトリに関する奇妙な側面について問うものです。この設問で取り上げた内容がそっくりそのまま実際の試験に出題される可能性は低いでしょう。というのも、解けるかどうかは仕様を丸暗記しているか次第であり、試験ではそのような設問は回避されることが多いからです。ただし、この設問では、1つの問題で複数の側面にわたる理解と知識を確認しています。運が良ければ、その点がさらにおもしろいものになります。 ラッパーでは、インスタンスを取得する方法が主に3つあります。各ラッパーでは、valueOfという名前のメソッドで静的ファクトリが提供されています。また、ラッパー型の変数に、型が一致するプリミティブから代入する場合のように、コンテキストが十分明示的であれば、オートボクシングが行われます。オートボクシングは、構文の単なる省略表記で、コンパイラにvalueOfメソッドを呼び出すコードを書かせることができ、ソース・コードが簡潔になります。3つ目のアプローチは、newキーワードを使ってコンストラクタを呼び出すというものです。実は、この3つ目のアプローチはJava 9で非推奨になっています。この設問のねらいの1つは、なぜ非推奨になったのかを問うことにあります。 Javaで、newキーワードを使ってコンストラクタを呼び出すたびに起きる可能性があるのは、2つのことだけです。すなわち、指定されたとおりの名前の型の新しいインスタンスが生成されて返されるか、例外が発生するかのいずれかです。実のところ、これは制約です。現在では、一般的にファクトリ・メソッドが好まれています。これら2つの効果を持たせることができるうえに、追加の結果を提供することもできるからです。 コンストラクタでは不可能で、ファクトリでは可能な機能の1つに、リクエストに沿った既存のオブジェクトを返こすとがあります。Integerラッパーが不変であることを考えれば、同じ数値を表す、この型の2つのオブジェクトは完全に交換可能です。そのため、同じ値を表す2つのオブジェクトを作るのは、メモリの無駄です。さらに、このアプローチでは、このようなオブジェクトをequals(Object o)メソッドではなく==を使って比較できます。Integerクラスでは、通常、-128から+127までの値がこのようにして再利用されます。 (補足ですが、この動作は、new String("1")のようにしてStringオブジェクトを作成する代わりに、Stringリテラルを使用することと実質的に同じです。) ファクトリ・メソッドには有利な点があと2つあります。複数のファクトリを作成する場合、各メソッドを異なる名前にすることができます。つまり、適切であれば、同じ引数型リストを受け取れるということです。コンストラクタでは、互いが有効なオーバーロードである必要があるため、これを行うのは不可能です。 3つ目の有利な点は、コンストラクタでは指定されたとおりの名前の型のオブジェクトを必ず返すということです。ファクトリでは、代入において宣言された型に対応するものであれば、何でも返すことができます(インタフェースの実装も含みます。こうすることにより、実装の詳細をうまく隠すことができます)。 この設問にもっとも関連するのは、Boolean.valueOf(...)を使う場合です。この場合、厳密に2通りの定数オブジェクトを得ることになります。1つはBoolean.TRUE、もう1つはBoolean.FALSEです。この2つは、追加のメモリを占有することなく、必要に応じて何度でも再利用されます。この動作は、newの呼出しでは不可能です。 ところで、ほとんどのラッパーのファクトリは、NULL引数や、作成する型を適切に表していない文字列が渡された場合、例外をスローします。たとえば、Integer.valueOfファクトリを"5"ではなく"five"という引数で呼び出した場合です。ただし、java.lang.Booleanクラスのファクトリは、引数の文字列が存在し、"true"という値(大文字、小文字は区別しません)が含まれているかどうかを確認します。その条件を満たす場合は、値Boolean.TRUEを返します。そうでない場合は、引数についてそれ以上何も知らせることなく、Boolean.FALSEを返します。つまり、NULL引数やテキスト"nonsense"でファクトリを呼び出しても、Boolean.FALSEが返され、例外はスローされません。 そのため、line n1のコードでは、例外はスローされず、変数b1にBoolean.FALSEが代入されると判断できます。そのため、選択肢Aは誤りです。 次は、if文の動作と、そこで行われている比較について吟味する必要があります。 一般的なルールに、if文の条件式はboolean型でなければならないというものがあります。Booleanオブジェクトがアンボクシングされて、そのままboolean型になることは明らかでしょう。ここで疑問を感じたとしたら、コードがコンパイルできないのではということかもしれません。そう考えるのは、おかしなことではありません。Java 5でオートボクシングが導入されるまで、このコードはコンパイルできなかったからです。しかし、そう心配したとしても、その点について触れている選択肢はないため、見た目どおりの動作が起こると考えて問題ありません。 この場合、b1が参照するオブジェクトはfalse値を表していることを確認しました。そのため、ifの条件は満たされず、コードの本体は実行されません。そこから、選択肢Dは正解であると判断できます。 実行されないことはわかりましたが、この議論に関連して、if文の中にあるprint呼出しの引数が評価されるとしたら何が起こるかについて考えることには価値があると思われます。 Javaでは、2つの形態の等価比較を提供しています。1つは、コア言語の一部である==演算子です。もう1つは、実質的にAPI機能と言えるequals(Object o)メソッドです。このメソッドはjava.lang.Objectクラスで定義されているため、すべてのオブジェクトで利用できます。しかし、対象のクラスでこのメソッドが実装されていない場合、有用な動作とならない可能性があります。この2つは、どの場面にいずれを使うかを把握し、正しく使い分けることが重要です。しかし、この設問には登場するのは==演算子だけであるため、こちらを詳しく見てみます。 ==演算子では、2つの式の値を比較します。これは一見簡単なように思えますが、それぞれの値で式の基本型が異なる場合、簡単とは言い切れません。==の外見上の効果は2つの型でまったく異なるため、この事実は重要です。ところで、ここでは意図的に「式」という用語を使っています。変数はシンプルな式の1つです。変数の値と型という面から考えてみても、この議論は成り立ちます。ただしその場合、真実の一部だけを捉えていることになります。 式を大きく2つに分ければ、プリミティブ(boolean、byte、short、char、int、long、float、doubleという8つの型のいずれか)と、いわゆる参照となります。参照は、メモリ内の別の場所にあるオブジェクトを見つけるために使う値という意味で、ポインタとよく似ています。 式がプリミティブタイプである場合、実のところ、関心の対象はその式の値です。そのため、あるint式の値が32であれば、事実、その式の値は32をバイナリで表現したものになります。そんなことは当たり前だと言われるかもしれません。問題は、変数がたとえばInteger型への参照タイプであり、その変数が、32という値を含むオブジェクトを参照するために使用されているような場合です。この場合、変数の値は32にはなりません。そうではなく、32を含むオブジェクトをJVMが見つけられるようにするための魔法の数値(参照)になります。つまり、参照タイプ(前述の8つのプリミティブを除いたすべてのものを指すことを思い出してください。したがって、Integer式も含まれます)の場合、==という条件で判定されるのは、2つの式が同じ意味を持つかどうかではなく、厳密に同じオブジェクトを参照しているかどうかです。重要なことに、32という値を含む2つのIntegerオブジェクトがあり、その2つが異なるオブジェクトである場合、それぞれの参照値は異なることになります。そのため、==を使って、それぞれを参照する式を比較すると、結果はfalseになります。 ここまで来れば、次のコードがあった場合、 Integer v1 = new Integer("1"); Integer v2 = new Integer("1"); System.out.print(v1 == v2); 確実にfalseが出力されることは明白でしょう。先ほども触れましたが、newを呼び出すと、指定されたとおりの名前の型の新しいオブジェクトか、または例外が必ず生成されます。つまり、v1とv2は必ず違うオブジェクトを参照します。すなわち、==操作は必ずfalseを返します。 代わりに次のコードがあったとします。 Integer v1 = new Integer("1"); Integer v2 = 1; System.out.print(v1 == v2); 設問のコードととてもよく似ており、片方ではコンストラクタを呼び出し、もう片方ではアンボクシングを使っています。このコードでも、確実にfalseが出力されます。 コンストラクタで生成されるオブジェクトは一意な新しいオブジェクトであるため、オートボクシングされた値を生成するファクトリから返されるものとは異なります。 多くの場合、不変オブジェクトのファクトリは、同じ引数で呼ばれた場合には毎回同じオブジェクトを返すようにコードが書かれています。IntegerクラスのvalueOf(int)メソッドのAPIドキュメントには、次のように書かれています。 「このメソッドは、-128から127の範囲の値を常にキャッシュしますが、この範囲に含まれないその他の値をキャッシュすることもあります」 言い換えれば、次のコード Integer v1 = Integer.valueOf(1); Integer v2 = Integer.valueOf(1); System.out.print(v1 == v2); では、確実にtrueが出力されます。 先ほど引用した仕様は、valueOf(int)メソッドのドキュメントにのみ書かれており、valueOf(String)では触れられていません。しかし実際は、両方のメソッドで同じプーリング動作が見られます。 もちろん、この設問で扱っているのは2つのIntegerオブジェクトです。片方はコンストラクタで生成され、もう片方は(Integer.valueOf(int)メソッドを使って)オートボクシングで生成されています。つまり、if文の本体に入力されたとすれば、出力はfalseになりました。しかし、選択肢Dが正解であることはすでに確定しているため、選択肢BとCは誤りとなります。ここまで説明してきたことは、おもしろい補足にすぎません。もちろん、実際におもしろいと思っていただけると幸いです。正解は選択肢Dです。   Java Magazine December 2019の他の記事 プロパティベース・テストを習得する Arquillian:簡単なJakarta EEテスト ArchUnitでアーキテクチャの単体テストを行う 新しいJava Magazine 作ってみよう:自分だけのテキスト・エディタ(パート1) クイズに挑戦:Collectorsの使用(上級者向け) クイズに挑戦:ループ構造の比較(中級者向け) クイズに挑戦:スレッドとExecutor(上級者向け) 書評:Core Java, 11th Ed. Volumes 1 and 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版にわたってテクニカル・レビューを務めた。

Integerラッパー・クラスを使って2つの整数のインスタンスを作成したときに、値を比較する正しい方法 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度...

クイズに挑戦:Collectorsの使用(上級者向け)

クイズに挑戦:Collectorsの使用(上級者向け) Collectorsクラスから想定どおりの結果を得るために注意すべきこと 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 collectメソッドと、Collectorsクラスのグループまたはパーティションのデータを使ってコレクションに結果を保存したいと思います。次のStudentクラスがあり、初期化済みで未使用のStream<Student> sがスコープ内にあるとします。このsには、さまざまな年齢の学生が含まれています。 class Student { private String name; private Integer age; Student(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public Integer getAge() { return age; } } 次のコード部分のうち、18歳未満の学生の数と18歳以上の学生の数が最適な方法で表示されるものはどれですか。1つ選んでください。 s.collect(Collectors.groupingBy( Student::getAge() >= 18, Collectors.counting())) .forEach((c, d) -> System.out.println(d)); s.collect(Collectors.groupingBy( a -> a.getAge() >= 18, Collectors.mapping( Student::getName, Collectors.counting()))) .forEach((c, d) -> System.out.println(d)); s.collect(Collectors.partitioningBy( a -> a.getAge() >= 18, Collectors.counting())) .forEach((c, d) -> System.out.println(d)); List<Integer> l = Arrays.asList(0, 0); s.forEach(w -> { if (w.getAge() >= 18) { l.set(1, l.get(1) + 1); } else { l.set(0, l.get(0) + 1); }}); l.forEach(System.out::println); 解答:この設問は、データを収集し、ある基準に従ってグループ化するいくつかの方法を示しています。選択肢A、B、Cでは、CollectorsクラスのユーティリティとStream.collectメソッドを使ってこれを実現しようとしています。選択肢Dでは、その処理を手作業で行おうとしています。 まずは、簡単な選択肢から見ていきます。選択肢Aのコードには、重大な構文エラーが存在します。ここでは、メソッド参照を使用し、次の式でメソッドを呼び出そうとしています。 Student::getAge() この構文は正しくありません。メソッド参照の構文をこのような形で使うことはできません。そのため、選択肢Aは誤りです。 通常、この試験では「人間コンパイラ」的な問題は避けられる傾向にあります。IDE全盛のこの時代では、コードを打ち込めばすぐに構文チェックが行われるため、そのようなスキルは役立つものではないからです。この選択肢が誤りであると判断できる理由がそれだけなら、実際の試験でこの選択肢が採用されることはないでしょう。しかし、この選択肢には、誤りであると判断できる理由が他に2つあります。1つは、別の選択肢にあるアプローチの方が設計的に優れていることです。少しばかりではありますが、実際の改善であることはもうすぐわかっていただけるはずです。2つ目の理由は、他に正解があるものの、この設問が単一選択問題であることです。そのため、別の答えを見つけた時点で、注意深くコードを見るようになり、エラーに気づくことになるはずです。ここでヒントとなるのは、この試験の際に、正しいと思われる最初の答えをそのまま解答に選ぶのは賢明でないということです。時間が足りない場合以外、他のすべての答えが確かに誤りであると納得するまで時間をかけるべきです。 注目すべきは、構文エラーを含む式 Student::getAge() >= 18 を正しい式 a -> a.getAge() >= 18 に置き換えれば、選択肢Aから正しい出力が得られ、その場合に問題となるのは設計の質だけであるという点です。 ただし、この時点で、選択肢Aをすぐに除外すべきであることは明らかであり、選択肢Aよりも優れた設計のアプローチが見つかって、その設計が優れている理由がわかることははっきりしています。 選択肢Bのコードは問題なくコンパイルでき、正しい結果が出力されます。これを理解するために、Collectionsクラスが持ついくつかの重要な動作について詳しく見てみます。groupingByメソッドは、関数(Function)を引数として受け取るCollector動作を生成します。この関数を、分類関数(classifier)と呼びます。たとえば、次のコードについて考えてみます。名前のストリームに対してgroupingByコレクション操作を実行するものです。 Stream.of("Fred", "Jim", "Sheila", "Chris", "Steve", "Hermann", "Andy", "Sophie") .collect(Collectors.groupingBy(n -> n.length())) ここでの分類関数は、名前(String)を受け取ってその長さを返す次の式です。 n -> n.length() 結果として、表1に示すようなMap<Integer, List<String>>が生成されます。 表1:キーと、各キーに関連付けられた値 分類関数からキーとして返されるそれぞれの値がこのMapにどのように格納されているかと、各キーに関連付けられた値が、そのキーを生成したストリームのアイテムすべてを含むListであることに注目してください。 groupingBy動作の亜種として、「ダウンストリーム・コレクタ」を持つものがあります。ダウンストリーム・コレクタを使うことにより、特定のキーを生成するアイテムを、同種のアイテムすべてからなるリストにそのまま追加するのではなく、後続のコレクション操作を使って各アイテムを処理することができます。これは、Listに送信されるアイテムを処理するセカンダリ・ストリーム・プロセスのようなものです。ダウンストリームでよく使われるコレクタの1つが、countingコレクタです。このコレクタを処理で使うことにより、値を名前のリストからリストの要素数に変えることができます。 次のコード Stream.of("Fred", "Jim", "Sheila", "Chris", "Steve", "Hermann", "Andy", "Sophie") .collect(Collectors.groupingBy( n -> n.length(), Collectors.counting())) は、表2に示すMap<Integer, Long>を返します。 表2:ダウンストリーム・コレクタを使用した場合のキーと、各キーに関連付けられた値 選択肢Bの動作はこの考え方に似ていますが、異なる点もいくつかあります。まず、分類関数(すなわち、結果のMapのキー)が数値ではなくブール値であることです。これは問題ありません。18歳以上の学生と18歳未満の学生を分類するという目的に沿っているからです。しかし、選択肢Bのコードでのダウンストリーム操作は1つではなく、2つの操作を連鎖させています。もちろんこのような連鎖は許可されており、大変役立つこともあります。しかし、この設問の場合、最初のダウンストリーム・コレクタで実際に行っているのは、各StudentオブジェクトからStudentの名前を抽出するマッピング操作です。2つ目のダウンストリーム・コレクタでは、その結果として得られる名前の数を数えています。 マッピング操作が結果を誤ることはありません。このコードでは実質的に、18歳以上の学生と18歳未満の学生の名前が数えられ、同じ数値が生成されます。ただし、これは無駄な努力です。そのため、このコードは効率の面でも、可読性の面でも、最適なものではありません。無関係で紛らわしいコードが含まれており、混乱が生じるリスクがあるからです。このような無駄な努力をしていない別の選択肢が後ほど登場するため、そこから選択肢Bは誤りであることがわかります。選択肢Bでは正しいレスポンスが生成されますが、もっとも効率のよい選択肢ではありません。 Collectorsクラスには、groupingBy動作のファクトリに加えて、似たような結果を生成する別のファクトリも存在します。このファクトリは、partitioningByと呼ばれています。動作の違いは、任意の型のキーを持つMapを作成するのではなく、Boolean型のキーを持つMapを作成するという点だけです。そのため、分類関数として関数(Function)ではなく条件(Predicate)を指定します。選択肢Cでは、partitioningBy動作を使って正しい出力が行われています。これは、2つの理由で選択肢Bよりも優れています。1つ目は、学生の名前を抽出するという無駄な手順が含まれていないことです。2つ目は、partitioningByが、単純なtrue/falseの結果を使ってグループ化するという目的のみに特化したものであることです。そのため、設計的に(少しばかり)優れた選択です。同じ論理で、(構文が有効だったとしても)選択肢Aより優れています。 手作業で分類するコードを使っている選択肢Dでも、正しい結果が生成される可能性があります。重大な問題は、このコードが副作用によって動作していることです。具体的に言えば、ラムダ式の外側にある変数を変更している部分です。この種の動作は、安全に同時実行(パラレル実行)することができません。ストリームは簡単にパラレル・モードで実行できるからこそ、特にストリームベースのシステムではこの種のコードは避けるべきです。 なお、この考え方が、ラムダ式(およびその登場より前のメソッド・ローカル・クラス)が設計されたときに漠然と示されていたことに注目すべきです。具体的に言うなら、ラムダ式やネスト・クラスの中からは、実質的にfinalである場合を除き、他のメソッド・ローカル変数へのアクセスが禁止されているという点です。選択肢Dのコードでは、実質的にfinalな参照を使って、参照先のListに格納されている可変データにアクセスすることで副作用が発生しています。 このコードは問題なくコンパイルでき、ストリームが逐次実行されれば、正しい結果が出力されます。ただし、設計はよくなく、ストリームがパラレルであれば失敗します。設計に問題があり、ストリームがパラレルの場合は失敗するため、選択肢Dは誤りです。 正解は選択肢Cです。   Java Magazine December 2019の他の記事 プロパティベース・テストを習得する Arquillian:簡単なJakarta EEテスト ArchUnitでアーキテクチャの単体テストを行う 新しいJava Magazine 作ってみよう:自分だけのテキスト・エディタ(パート1) クイズに挑戦:ループ構造の比較(中級者向け) クイズに挑戦:スレッドとExecutor(上級者向け) クイズに挑戦:ラッパー・クラス(中級者向け) 書評:Core Java, 11th Ed. Volumes 1 and 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版にわたってテクニカル・レビューを務めた。

クイズに挑戦:Collectorsの使用(上級者向け) Collectorsクラスから想定どおりの結果を得るために注意すべきこと 著者:Simon Roberts、Mikalai Zaikin 2019年8月26日 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」とい...

作ってみよう:自分だけのテキスト・エディタ(パート1)

新連載:階層化設計と反復型開発を使ってライン・エディタをテキスト・エディタに進化させる 著者:Ian Darwin 2019年8月20日 本記事のタイトルを読んだ方は、「なぜJavaでテキスト・エディタを作りたいのだろうか。JavaはエンタープライズWebアプリのためのものではないのか」と思うかもしれません。私の最初の答えは、「とんでもない」です。Javaは現在もデスクトップで使われており、活躍しています。私の次の答えは、一部のJava開発者にとって主な「安全地帯」であるWeb環境から離れることで、設計や、興味深い実装の問題に焦点を移すというものです。 本記事では、反復型実装により、簡単なラインモード・テキスト・エディタを作ることに的を絞ります。つまり、シンプルな実装から始め、新機能を追加しながら設計を更新し、その再実装を行います。 次回の記事では、このエディタをグラフィカルなデスクトップ・エディタに進化させます。形はさまざまに変わっても、実体が純粋なテキスト・エディタである点、つまり、プレーン・テキストのファイルを変更するプログラムであることは変わりません。プレーン・テキストとは、フォントや色が変わらないテキストです。バッチ・ファイルやスクリプト・ファイル、プログラムのソース・ファイル、構成ファイル、ログ・ファイルなど、今でも世界中でコンピュータ向けの情報の大部分に使われています。プレーン・テキスト・エディタと完全なワード・プロセッサの間には、大きな隔たりがあります。ワード・プロセッサは、文字の書体やサイズや色の選択、画像やスプレッドシートの埋め込み、テキストの左揃え、右揃え、中央揃えなどの多くの機能を備えています。短い連載記事でこれらすべてを紹介するのは難しいと思われることから、まずはプレーン・テキストに焦点を当てることにします。 エディタを設計する場合、主に2つのことを考える必要があります。1つはユーザー・インタフェース(UI)またはコマンド・レイヤー、もう1つはバッファ管理です。UIまたはコマンド・レイヤーでは、ユーザーがメモリ内のバッファに対して何をすることができるかを定義します。こういったタスクは、たとえば、ライン・エディタであれば簡単なコマンド、スクリーン・エディタならマウス操作、インタラクティブ・システムなら音声コマンドになるでしょう。バッファ管理とは、ユーザーがバッファ内のテキストをどのように扱うかということです。通常、テキスト・エディタ(およびその強力な親戚であるワード・プロセッサ)は、変更しているファイルのコピーをメモリ内に保持しています。このコピーは、メモリ内のバッファに保管されます。エディタでは、ユーザーがエディタを終了したとき、または何らかの「保存」コマンドが発行されたときに、この変更されたテキストが元のファイルの代わりにディスクに書き込まれます。バッファ管理はモデル・コードとしても知られ、メモリ内にある編集中のファイルの内容を管理することに関係しています。最終的なプロダクトを便利でメンテナンスしやすいものにするためには、UIとバッファ管理、そしてその間にあるすべての重要なインタフェースをうまく設計する必要があります。   コードのレイヤー間の境界 Javaインタフェースの定義は、コードの論理レイヤーを分離するための強力(で一般的)な方法です。アプリケーションのレイヤー間や、(現在または将来的に)複数の実装を持つ可能性が高いクラスでは、インタフェースを使うべきです。その結果、クラスが特定の実装ではなく、インタフェースに依存するようにすることができます。 この場合、BufferPrimsインタフェースが両方のニーズに合致します。このインタフェースはレイヤー境界であり、複数の実装を持ちます。これは、バックエンドにデータベースがあるアプリケーションに似ています。このようなアプリケーションではおそらく、中間(ビジネス・ロジック)レイヤーとデータベースのコードの間にインタフェースが存在するでしょう。これにより、JDBC、Java Persistence API(JPA)/Hibernate、場合によってはNoSQLデータベースを切り換えて使用できるようになります。JPAのEntityManagerやHibernateのSessionは、この目的では十分に汎用的だと言う方もいるでしょう。また、アプリケーション固有のインタフェースを使うことを提案する方もいるかもしれません。すべてのアプリケーションに当てはまる正しい1つの答えはなく、これは設計上の考慮事項になります。 エディタには、コマンド・レイヤーとバッファ管理レイヤーとの間のインタフェースが必要です。このレイヤー間のインタフェースはとても簡単なもので、たとえば次のようになります。 public interface BufferPrims { addLineAfter(int afterLine, String newLine); deleteLine(int lineNumber); replaceText(oldText, newText); } このインタフェースでは、バッファ管理がどのように動作するかについてコマンド・コードにほとんど伝えていません。同時に、UIがどのように動作するかについてバッファ管理に何も伝えていません。この点は重要です。相手方に影響を与えずに片方を交換できることが望ましいからです。これは、レイヤー化されたソフトウェア全般に当てはまります。レイヤーは、その上にあるレイヤーについて何も知るべきではありません(たとえば、さまざまなUIから呼び出せるようにするためです)。また、知っているべきなのは、直下のレイヤーを呼び出す方法だけで、他には何も必要ありません。 実際のエディタを作るためには、もう少し包括的なインタフェースが必要です。ここでは、次のようにまとめました。 public interface BufferPrims { final int NO_NUM = 0, INF = Integer.MAX_VALUE; void addLines(List<String> newLines); void addLines(int start, List<String> newLines); void deleteLines(int start, int end); void clearBuffer(); void readBuffer(String fileName); void writeBuffer(String fileName); int getCurrentLineNumber(); String getCurrentLine(); int goToLine(int n); int size(); // 古いコレクションの時点での行数 /** 1つまたは複数の行を取得 */ String getLine(int ln); List<String> getLines(int i, int j); /** 現在の行のみを対象に、最初またはすべての * 「古い」正規表現を「新しい」テキストに置き換える */ void replace(String oldRE, String newStr, boolean all) /** 各行の最初またはすべての検索対象を置き換える */ void replace(String oldRE, String newStr, boolean all, int startLine, int endLine); boolean isUndoSupported(); /** 直近の操作を元に戻す * オプション・メソッド */ default void undo() { throw new UnsupportedOperationException(); } } ほとんどの操作は単純と思われます。本記事のソース・コードでは、BufferPrimsインタフェースの3つの実装を提示しています。オプションのundoメソッドは、1つの実装には含まれていますが、他の2つには含まれていません。この実装については、以前にCommandデザイン・パターンについての記事で説明しました。 ここにはread()やwrite()を入れるべきではないと思う人や、メイン・プログラムで1行ずつファイルを読み取り、いずれかのadd()メソッドを使って行を取り込むべきだと考える人もいるかもしれません。このインタフェースにread()メソッドやwrite()メソッドがあるのは、効率のためです。1回の読取り操作でファイル全体を読み取るバージョンがあっても構わないでしょう。   バッファ管理 大きく異なる複数の実装が存在するのは、インタフェースが合理的な設計に基づいている証拠です。しかし、このことは効率について何も触れていません。効率を気にしないのであれば、1つのStringオブジェクトにすべてを保管することもできます。しかし、文字列は不変であるため、このアプローチでは多くの再割当てが必要になります。そのため、純粋な実装の基盤という面では、StringBuilderやStringBufferの方が優れています。実際には、バッファ・プリミティブ(BufferPrims)の最初の実装では、BufferPrimsStringBufferを使っています。 StringBufferには、バッファの内容を変更する組込みメソッドなどのいくつかのメリットがあります。しかし、本質的に単語のリストのリスト(さらに正確に言えば、行のリスト)であるものを表す自然な構造ではありません。StringBufferの実装(この実装が可能であることを示すために書くことにしました)では、行が始まる場所と終わる場所を見つけるためだけに、いくらかの作業が必要になります。 整合性を保つため、各行は改行文字1文字('\n')で終わり、キャリッジ・リターン('\r')は完全に禁止することを前提としました。このアプローチにより、改行文字を探すだけで行が終わる場所と次の行が始まる場所を見つけることができます。このクラスのほとんどの操作は、StringBufferのメソッドを呼び出して、そのStringBufferの特定の位置にある文字の取得や設定を行うことで終わります。 たとえば、次に示すのは現在の行の内容を取得するコードです。 @Override public String getCurrentLine() { int startOffset = findLineOffset(current); int len = findLineLengthAt(startOffset); return buffer.substring(startOffset, startOffset + len); } findLineOffsetメソッドでは、正規表現を使って行を検索します。これは改行文字を探すforループほど簡単ではありませんが、実行可能で純粋な実装だと言えます。 同様に、StringBufferベースの行検出にも、同じ2つのメソッドを使っています。 @Override public void deleteLines(int startLine, int endLine) { if (startLine > endLine) { throw new IllegalArgumentException(); } int startOffset = findLineOffset(startLine), endOffset = findLineOffset(endLine); buffer.delete(startOffset, endOffset + findLineLengthAt(endOffset) + 1); } おそらく、もっとも複雑なコマンドは置換コマンド(s)でしょう。「UIとコマンド構造」のセクションでは、いくつかの種類の置換コマンドについて説明します。UIレイヤーでは、すべてのコマンド・オプションを解析した後、次に示す2つのメソッドのいずれかを呼び出す必要があります。 void replace(String oldRE, String newStr, boolean all); void replace(String oldRE, String newStr, boolean all, int startLine, int endLine); all変数では、置換対象となるのが一致したすべての結果なのか、1つ目だけなのかを制御します。次に示すのが、replaceを1行で実装したものです。 @Override public void replace(String oldRE, String newStr, boolean all) { int startOffset = findLineOffset(current); int length = findLineLengthAt(startOffset); String tmp = buffer.substring(startOffset, length); tmp = all ? tmp.replaceAll(oldRE, newStr) : tmp.replace(oldRE newStr); buffer.replace(startOffset, length, tmp); } 第2の実装:バッファ・コードの第2の実装となるのが、BufferPrimsNoUndoです。この実装では、バッファのデータをList<String>に格納しています。行単位での処理は、こちらのデータ構造を使った方が簡単です。たとえば、現在の行を取得する処理は、次のコードだけで実現できます。 @Override public String getCurrentLine() { return buffer.get( lineNumToIndex(current)); } (行番号は1から始まりますが、Listのインデックスは0から始まります。そのため、lineNumToIndex()メソッドで行番号からリストのインデックスに変換しています。こうする方が、毎回1を引くよりもきれいに見えます。) 「行の削除」操作も簡単になります。 @Override public void deleteLines(int startLnum, int end) { int startIx = lineNumToIndex(startLnum); for (int i = startIx; i < end; i++) { if (buffer.isEmpty()) { System.out.println( "?Deleted all lines!"); break; } buffer.remove(startIx); // iではない } current = startLnum; } なお、このクラスのメソッドの一部が、AbstractBufferPrimsクラスに格納されていることに注意してください。このクラスは、両方のListベースの実装で使用しています。 第3の実装:最後のBufferPrimsWithUndoは、一歩先に進んだ実装で、その名のとおり「元に戻す」操作を実装しています。簡潔に言えば、バッファを変更する操作を行うたびに、その逆の操作を行うコードを含むラムダ式も作成されます。たとえば、ここでの「行の削除」操作は次のようになります。 @Override public void deleteLines(int startLnum, int end) { List<String> undoLines = new ArrayList<>(); for (int i = startLnum; i < end; i++) { if (buffer.isEmpty()) { System.out.println("?Deleted all lines!"); break; } undoLines.add(buffer.remove(i - 1)); } current = startLnum; if (!undoLines.isEmpty()) { pushUndo("delete lines " + startLnum + " to " + end, () -> addLines(startLnum, undoLines)); } } 一部の操作はBufferPrimsNoUndoととてもよく似ているため、実際には、super()を使ってAbstractBufferPrimsクラスの処理を再利用しています。 @Override public void addLine(String newLine) { super.addLine(newLine); pushUndo("add 1 line", () -> deleteLines(current, current)); } pushUndo()メソッドでは、説明文字列(GUIのあるエディタで使うためのもの)と、現在のコマンドを「元に戻す」操作を行うラムダ式をまとめるラッパー・オブジェクトを作成しています。deleteLines()の「元に戻す」操作では、削除された行すべてを追加できることが必要です。そのため、すべての行をListに格納し、削除の逆の操作であるaddLines()に渡しています。「元に戻す」のメカニズムと、そのメカニズムを実装するCommandデザイン・パターンの詳しい説明は、先ほど触れた記事をご覧ください。   UIとコマンド構造 すべてをゼロから再発明(通常、これはよくない考え方です)しなくてもよいように、このライン・エディタのUIには、UNIXのedコマンドのUIを堂々と拝借することにします。このUIは、何十年もの間にわたって標準であり続けており、exおよびviの両者のベースとなり、さらにはストリーム・エディタsedの大部分のベースとなりました。 次に示すのは、すべてのコマンドの基本フォーマットです。 [n,m]C[operands] この意味は次のとおりです。 角括弧は、その中のテキストが省略可能であることを示します。 nとmは行番号です(1から始まり、デフォルトは現在の行です。現在の行とは、最後に操作を行った行のことです)。 Cは1文字コマンドです(たとえば、dは削除、aは追加、sは置換)。適時最新のコマンド・リストは、ソース・プロジェクトのREADMEファイルに記載されています。 operands(オペランド)は、使用するコマンドによって異なります。 なお、小文字1文字でコマンドを表すという、このインタフェースの設計は、最初にこの設計が生み出されたころの、ユーザーとコンピュータとの間の非常に低速なキーボード・インタフェースの影響を受けたものであることには注目する価値があります。しかしこれは、設計を無制限に拡大させないことを意図した選択でもありました。つまり、可能なコマンドは26個ほどしか想定しなかったということです。よいことか悪いことか、後の開発者はいくつかの大文字コマンドを追加しました。今回のエディタで大文字コマンドは実装しませんが、最近のUNIXやLinuxのedではおそらく実装されているでしょう。 すべてのJavaアプリケーションには、起動のためのpublic static void main(String[] args)メソッドが必要です。通常、このメソッドではコマンドライン・オプション(存在する場合)を処理し、ファイルのチェックやオープンを行い、処理用のメソッドに制御を委譲します。一般的に、処理用のメソッドは静的でないインスタンス・メソッドです。今回の例でのメイン・プログラムは、LineEditor.javaに含まれています。 ここでのmainメソッド内にはループがあります。そこでは、コンソールから1行を読み取り、解析してエディタのコマンドである可能性が高いかを確認し、コマンドであれば、その文字を文字コマンド固有のルーチンに渡しています。メイン・コードのループは次のようになっています。 // エディタのメイン・ループ: while ((line = in.readLine()) != null) { ParsedCommand pl = LineParser.parse(line, buffPrims); if (pl == null) { System.out.println("?"); continue; } EditCommand c = commands[pl.cmdLetter]; if (c == null) { System.out.println( "?Unknown command in " + line); } else { c.execute(pl); } } 1文字コマンドを処理するコードは、Commands.javaで設定しています。具体的には、EditCommandオブジェクトの配列を使い、1文字と、コマンドを実装したラムダ式とのマッピングを行っています。任意のASCII文字を使えるよう、この配列の長さは255にしていますが、実際に実装されているのは20ほどしかありません。コマンド処理の簡単な例として、次にpコマンド(印刷を表す"print"の略です)の実装を示します。 // p - 行の印刷 commands['p'] = pl -> { buffPrims.getLines(pl.startNum, pl.endNum) .forEach(System.out::println); }; このコードの変数plは、ParsedCommand(以前はParsedLineでした)を表します。ParsedCommandは、コマンド、行番号の範囲、そしてオペランドを表すクラスです。ユーザーがコマンドを入力するたびに、LineParserクラスのparse()メソッドによって、そのコマンドを表すParsedCommandが作成されます。 コマンド・ハンドラの中には、先ほどのpのように、短くて扱いやすいものもあります。しかし、中にはかなり長いものもあります。興味がある方は、sコマンド(置換を表す"substitute"の略です)の実装をご覧ください。このコマンドでは、ある範囲の行に対して反復処理を行わなければならないだけでなく、デリミタに任意の文字を使用できなければなりません。さらに、デリミタが2回または3回存在することを確認し、最後のデリミタの後にgとpが任意の順番で存在しているかどうかも確認する必要があります。gは"global"(グローバル)または"go across the line"(行をまたぐ)を表します。このコマンドがない場合、edjでは最初に見つかった古いテキストのみが置き換えられます。最後にpを指定した場合、対象の行が印刷されます。以下に、有効なコマンドの例をいくつか示します。 s/him/them/ # 現在の行を対象に、最初の"him"を"them"に変更 s=him=them= # 上記と同じだが、違うデリミタを使用 2,3/him/them/ # 2行目から3行目を対象に、最初の"him"を"them"に変更 5,s/him/them/ # 5行目から末尾までを対象に、最初の"him"を"them"に変更 ,s/him/them/ # すべての行を対象に、最初の"him"を"them"に変更 s/him/them/g # すべての行を対象に、すべての"him"を"them"に変更 s/him/them/p # 現在の行を対象に、最初の"him"を"them"に変更し、 # 行を印刷 s/him/them/gp # 上記の2つの例を組み合わせたもの この構文を学ぶためには時間が必要です。また、すべての種類を正確に解釈するためには、数行のコードが必要になります(parseSubstitute()をご覧ください)。ただし、このコマンドの簡潔さと強力さを見れば、ed(およびそこから派生したviなどのエディタ)が今もオープンソースの世界でシステム管理者や開発者に愛用されている理由がわかります。   パラメータ化テスト 「早い段階から頻繁にテストする」というのは、信頼性の高いコードを書くための格言です。多数の単体テストを作成し、少しでも変更が発生したときに実行するということです。バッファ・プリミティブのテストは簡単で、ほとんどのコードに対してテストが存在します。3種類の実装に対してテストをコピー・アンド・ペーストすることを避けるため、ここではパラメータ化テストという、JUnit 4の機能を使います。@Parametersアノテーションを付加したメソッドからは、コンストラクタの引数(任意の型)リストが返されるようにします。このリストは、テスト・クラスの個々のインスタンスを作成するために使われます。テスト・クラスのフィールドは、コンストラクタによって設定されます。引数は任意の型でよいため、クラス・ディスクリプタ(たとえば、Reflection APIクラス)を使用しています。次に、BufferPrimsTestの関連部分を示します。 @RunWith(Parameterized.class) public class BufferPrimsTest { protected BufferPrims target; public BufferPrimsTest(Class<BufferPrims> clazz) throws Exception { target = clazz.newInstance(); } @Parameters(name = "{0}") public static Class<BufferPrims>[] params() { return (Class<BufferPrims>[]) new Class<?>[] { BufferPrimsStringBuffer.class, BufferPrimsNoUndo.class, BufferPrimsWithUndo.class }; } // @Testアノテーションを付加したメソッド... } コマンドおよび置換オペランドを解析するコードのテストも存在します。   さらなる作業 この状態のedjは、たとえ完全版が手元にあっても、使いたいと思うようなものではないでしょう。あるいは、viやそのさまざまな派生物のようなスクリーンベースのエディタであっても同じでしょう。edのコマンドの一部は実装されておらず、実装されているものもほとんどが不完全です。たとえば、行の範囲を解析する部分には、「検索」の機能がありません。実際のUNIXやLinuxにおけるedの実装では、開始行または終了行の行番号のいずれか(または両方)の代わりに、検索文字列を指定できます。検索文字列を、/pattern/と指定した場合は現在の行から前方(下方向)に検索でき、?pattern?と指定した場合は上方向に検索できます。コードは全体的に、このような機能を追加する必要がある場合、誰でも拡張して完全な実装にできるような設計になっています。   コードの再利用 設計全体を検証するために、UNIXのストリーム・エディタsedのとても簡単な概念実証実装を作成しました。UNIXのsedコマンドは、名前のついたファイル(ファイルが指定されていない場合は標準入力)を1行ずつ読み取り、コマンドラインで指定されていたすべての編集コマンドを各行に適用します。次に例を示します。 sed -e 's/old/new/g' -e 's=more=less=' file1 想像どおりかもしれませんが、file1内のすべての"old"を"new"に置換し、各行の最初の"more"を"less"に置換するものです。これは、バッチ・エディタというよりはストリーム・エディタです。file1は変更せず、変更結果を標準出力に書き込みます。 バッチ・エディタの作成、すなわち、コマンドにエラーがあったときやディスクがいっぱいになったときにファイルを安全に保つことは、今回の概念実証の範囲外です。 このStreamEditorでは、LineEditorとは異なり、1つのコマンドしか実装していません。ただし、実装しているのはもっとも複雑なs(置換)コマンドです。StreamEditorでは、行を解析するコードと置換コマンドの解析コードを再利用しています。このStreamEditorのプロトタイプでは、実装されている1つのコマンドは問題なく動作します。この概念実証のデモとして、stests(stream tests)という名前のシェル・スクリプトを作成しています。次回の記事では、バッファ処理コードを再利用する予定です。   コードの入手 このバージョンのエディタを、edj(editor in Java、「エッジ」のように発音します)と呼んでいます。繰り返しになりますが、全体を1か所で見てみたい方は、GitHubからコードをダウンロードできます。   まとめ 本記事では、かなり単純なテキスト・エディタをJavaだけで実装するために必要な構造の設計を行う際の問題や、その場合のコードの一部について見てきました。ここで、重要な点をいくつか挙げておきます。 行指向のパラダイムで正規表現の力を活用するUIの設計 UIと、土台になるバッファ管理コードとの間のインタフェースの設計 効率的である一方でわかりやすいバッファ管理の実装 UIの設計は、UNIXのedのUIをベースにしています。また、BufferPrimsのインタフェースは、筆者がかなり前に作成したこのエディタのC言語による再実装にかすかに似たものとなっています。その再実装自体は、『Software Tools』という書籍向けにRatForという教育用言語で書いた、さらにその前のバージョンがベースになっています。今回紹介したバージョンのインタフェースは、最初から完全な形で考えついたわけではなく、エディタの進化とともに何度か改訂を重ねた結果です。次回の記事では、このインタフェースを再利用して、実際に使用できるGUIテキスト・エディタを作成する方法を紹介したいと思います。   Java Magazine December 2019の他の記事 プロパティベース・テストを習得する Arquillian:簡単なJakarta EEテスト ArchUnitでアーキテクチャの単体テストを行う 新しいJava Magazine クイズに挑戦:Collectorsの使用(上級者向け) クイズに挑戦:ループ構造の比較(中級者向け) クイズに挑戦:スレッドとExecutor(上級者向け) クイズに挑戦:ラッパー・クラス(中級者向け) 書評:Core Java, 11th Ed. Volumes 1 and 2 Ian Darwin Ian Darwin(@Ian_Darwin):Java Champion。メインフレーム・アプリケーションから、UNIXおよびWindows向けのデスクトップ・パブリッシング・アプリケーション、Javaによるデスクトップ・データベース・アプリケーションやAndroid向けのヘルスケア・アプリまで、あらゆる開発を手がける。『Java Cookbook』、『Android Cookbook』(いずれもO'Reilly)の著者。また、Learning Tree Internationalでいくつかのコースを作成し、多くのコースで教えている。

新連載:階層化設計と反復型開発を使ってライン・エディタをテキスト・エディタに進化させる 著者:Ian Darwin 2019年8月20日 本記事のタイトルを読んだ方は、「なぜJavaでテキスト・エディタを作りたいのだろうか。JavaはエンタープライズWebアプリのためのものではないのか」と思うかもしれません。私の最初の答えは、「とんでもない」です。Javaは現在もデスクトップで使われており、活躍しています...

Arquillian:簡単なJakarta EEテスト

Arquillianフレームワークを使ってJakarta EEアプリケーションをテストする方法 著者:Josh Juneau 2019年8月20日 Webアプリケーションやエンタープライズ・アプリケーションのテストは、Java SEプロジェクトのテストよりもはるかに面倒になる場合があります。スコープが異なり、ほとんどの場合にテストするフェーズも異なる、さまざまな種類のファイルが存在するからです。Arquillianは、長い年月をかけて進化してきた強力で堅牢な、Java EEおよびJakarta EE向けのテスト・フレームワークです。JBossプロジェクト(現在はRed Hatの一部)によって開発されたもので、テストやその出力を拡張できる複数のアドオン拡張機能もあります。 Arquillianフレームワークはとても強力ですが、その背景にはある程度の複雑さがあります。テスト・クラスの設定は、行う必要があるテストのレベルに応じて、とても簡単な場合もあれば、かなり複雑な場合もあります。 本記事では、Arquillianフレームワークの構成と、このフレームワークを使用したテストについて紹介します。基本的なJakarta EE 8(Java EE 8と同等)アプリケーションの構成とビルドに加え、JUnit 4とArquillianを使ういくつかのテストについても触れます。いくつかの基本的な例を示した後は、特に重要な機能や拡張機能について説明します。いずれも、Java EEおよびJakarta EEのプロジェクトの検証機能を拡張できるものです。本記事の内容を理解するためには、Java EEまたはJakarta EEプロジェクトの基本的知識が必要です。 サンプル・アプリケーションはMavenを使ってビルドしています。Mavenプロジェクトに対応した任意のIDEを使えるはずですが、本記事の例ではApache NetBeans IDEを使っています。GitHubから、ArquillianExampleという名前の、Maven Webアプリケーション・プロジェクトのサンプルを取得しておくことをお勧めします。   環境を設定する Arquillianフレームワークを使う際に特に難しいことの1つが環境設定です。基本的なテストに必要となるのはわずかなMaven依存性だけですが、正しいバージョンの依存性を使わなければなりません。バージョンが正しくなかった場合、エラーにつながる不整合が起きる可能性があります。ゼロから始める場合は、コマンドラインまたはIDE(Apache NetBeansなど)からMavenアーキタイプを使ってMaven Webアプリケーション・プロジェクトを作成します。Apache NetBeansを使っている場合は、プラットフォームとしてJava EE 8を選んでください。 プロジェクトを作成したら、プロジェクトを右クリックしてPOMファイルを開き、リスト1に示す依存性を追加します。この依存性には、Arquillianの要件が含まれています(注:本記事のサンプル・ソース・コードを使用する場合、Jakarta EEプラットフォームとApache Derbyデータベースに関連する追加の依存性もあります)。 この依存性を眺めてみます。arquillian-bomアーティファクトがArquillianフレームワークの「部品表」になっており、すべての推移的依存性で使われます。POMファイルの標準の<dependencies>セクションには、JUnitフレームワークを追加