Azure ストレージアカウントの作り方を理解する
Azure ストレージアカウントとは
Azure のストレージサービスというと、BLOB やファイル共有といったものを思い浮かべると思いますが、それらサービスを利用するのに先立って、ストレージアカウント、というものを作成する必要があります。 今回取り上げるのは、このストレージアカウントのお話です。
ストレージアカウントにはいくつかの種類があり、Microsoft の公式ドキュメントでは以下の 6 つであるとされています。
- Standard 汎用 v2
- Premium ブロック BLOB
- Premium ファイル共有
- Premium ページ BLOB
- Standard 汎用 v1(レガシー)
- ブロブストレージ(レガシー)
これらのストレージアカウントは、AZ-104 の試験でもよく問われるため対策が必要なのですが、どうしてこうなったと思うくらい複雑でわかりにくいので、調査したものをまとめてみます。
種類がいくつあるのかよくわからない
前節で 6 種類と書いたばかりですが、AZ-104 の対策講座等で動画を見ていると、モノによっては以下の様なストレージアカウントがある、という説明がされていたりします。
- Premium 汎用v2
- Premium 汎用v1
そもそも前半部分の Standard や Premium というのはパフォーマンスに関する分類で、後半のブロックBLOBやファイル共有というのは利用可能なサービスを示しています。汎用とはどのサービスも利用できるということです。 これだけ聞くと、パフォーマンスとサービスとはそれぞれ独立して選択できるように思えます。
ですが、総当たり的にすべての組み合わせが利用できるわけではなく、一部の組み合わせのストレージアカウントのみが存在する、というのが実際のところです。 ここはちょっともやっとするので、個人的には Premium 汎用v2 があったほうがよほどすっきりするようにも思えます。
そこで試しに少し Azure ポータルを触ってみると、パフォーマンスを Premium にしてやることで、Premium StoraveV2 というアカウントを作成することができてしまいます。 やっぱりあるんだ?と思って出来上がったストレージアカウントを見てみると、実際に利用できるのは BLOB のコンテナ―のみになっていて、ますますよくわからなくなります。 汎用とは。。?
作成方法もよくわからない
ちなみに、Azure ポータルでのストレージアカウント作成についても、今一つ直感的ではない印象です。
まず、直接ストレージアカウントの種類を選択できるような項目はありません。 その代わりに、Standard と Premium を選択できる「パフォーマンス」という項目と、「プライマリサービス」という項目があり、「BLOB(または Data Lake)」、「Files」、「その他」から選択できるようになっています。 Standard 汎用v2 のストレージアカウントを作りたいときは、パフォーマンスを Standard にするのはわかりますが、プライマリサービスは何を選べばよいのか不明です。 結論から言うと、プライマリサービスは何を選んでも Standard 汎用v2 を作成できるし、そもそもプライマリサービスの項目は入力必須ではないので、未選択のままにすることもできてしまいます。
一方、パフォーマンスを Premium にすると、Premium アカウントの種類という項目が新たに表示され、「ブロックBLOB」、「ファイル共有」、「ページBLOB」から選択が可能です。 ここの選択はそれぞれのストレージアカウントの種類に対応していると思えるのですが、デフォルト値である「ページBLOB」を選択して作成すると、前述の Premium汎用v2 のアカウントが出来上がる、という具合です。
Azure CLI によるストレージアカウントの作成
Azure ポータルでの作成は前節のような感じですが、CLI はどうかというと、こちらはかなりわかりやすくなっています。
Azure Storage アカウントを作成する - Azure Storage | Microsoft Learn
PowerShell、Azure CLI、Bicep、Azure テンプレート、または Azure Developer CLI を使用してストレージ アカウントを作成する場合、ストレージ アカウントの種類は kind パラメーター (例: StorageV2) で指定されます。 パフォーマンスレベルと冗長構成は、sku パラメーターまたは SkuName パラメーター (例: Standard_GRS) で一緒に指定されます。
「ストレージアカウントの種類は kind パラメーターで指定する」これはとても分かりやすいです。 リファレンスも見てみると、kind パラメーターに指定できる値は 5 種類です。
az storage account | Microsoft Learn
--kind
ストレージ アカウントの種類を示します。指定可能な値: BlobStorage, BlockBlobStorage, FileStorage, Storage, StorageV2
規定値: StorageV2
一方、「パフォーマンスレベルと冗長構成は sku パラメーター (または SkuName パラメーター) で一緒に指定されます」というのは、なぜ一緒にしたのかという疑問は残りますが、ともあれ言っていることや、仕組みとしては理解できます。
こちらもリファレンスを見てみると、指定可能な値が記載されています。
--skuストレージ アカウントの SKU。指定可能な値: Premium_LRS, Premium_ZRS, Standard_GRS, Standard_GZRS, Standard_LRS, Standard_RAGRS, Standard_RAGZRS, Standard_ZRS
規定値: Standard_RAGRS
そして、これら 2 つのパラメーターの組み合わせについても、最初のドキュメントに表形式での記載があります。
Azure Storage アカウントを作成する - Azure Storage | Microsoft Learn
| ストレージ アカウントの種類 | サポートされている冗長構成 | kind パラメーターでサポートされる値 | sku パラメーターまたは SkuName パラメーターでサポートされる値 | 階層型名前空間のサポート |
|---|---|---|---|---|
| Standard 汎用 v2 | LRS、GRS、RA-GRS、ZRS、GZRS、RA-GZRS | StorageV2 | Standard_LRS、Standard_GRS、Standard_RAGRS、Standard_ZRS、Standard_GZRS、Standard_RAGZRS | はい |
| Premium ブロック BLOB | LRS、ZRS | BlockBlobStorage | Premium_LRS、Premium_ZRS | はい |
| Premium ファイル共有 | LRS、ZRS | FileStorage | Premium_LRS、Premium_ZRS | いいえ |
| Premium ページ BLOB | LRS | StorageV2 | Premium_LRS | いいえ |
| レガシ Standard 汎用 v1 | LRS、GRS、RA-GRS | ストレージ | Standard_LRS、Standard_GRS、Standard_RAGRS | いいえ |
| レガシ BLOB ストレージ | LRS、GRS、RA-GRS | BlobStorage | Standard_LRS、Standard_GRS、Standard_RAGRS | いいえ |
この表を見ると、Premium ページ BLOB は、kind を StorageV2、sku を Premium_LRS として作成するということがわかります。 つまり、Azure ポータルで、パフォーマンスを Premium、アカウントの種類を「ページ BLOB」として作成されたストレージアカウントは「Premium ページ BLOB」であるわけですが、kind と sku に指定された値からすると「Premium StoraveV2」ともいえる、ということなのでしょう。
まとめ
Azure のストレージアカウントは、CLI での作成方法を調べてみると理解しやすくなります。 kind パラメータは 5 種類のみですが、StorageV2 を指定した場合のみ、sku の選択により 2 種類のストレージアカウントを作成可能です。
| kind | sku | 作成されるストレージアカウント |
|---|---|---|
| StorageV2 | Standard_LRS Standard_GRS Standard_RAGRS Standard_ZRS Standard_GZRS Standard_RAGZRS |
Standard 汎用 v2 |
| StorageV2 | Premium_LRS | Premium ページ BLOB |
| BlockBlobStorage | Premium_LRS Premium_ZRS |
Premium ブロック BLOB |
| FileStorage | Premium_LRS Premium_ZRS |
Premium ファイル共有 |
| Storage | Standard_LRS Standard_GRS Standard_RAGRS |
(レガシー)Standard 汎用 v1 |
| BlobStorage | Standard_LRS Standard_GRS Standard_RAGRS |
(レガシー)BLOB ストレージ |
ポータルで作成する場合は以下の様に考えることができます。
パフォーマンスを Standard とした場合: Standard 汎用v2 のみが作成可能 レガシーアカウントは現在 ポータルでは作成できない プライマリサービスを選択してもストレージアカウントの種類には影響しない
パフォーマンスを Premium とした場合: ストレージアカウントの種類の選択により、以下の 3 つを作成可能。 * Premium ブロック BLOB * Premium ファイル共有 * Premium ページ BLOB
AWS クラウド導入フレームワークの覚え方
先日 AWS クラウドプラクティショナー試験 を受験したのですが、AWS クラウド導入フレームワーク (AWS Cloud Adoption Framework: AWS CAF) というのをを覚えるのに一苦労しました。 その際、自分なりに工夫した暗記法を紹介してみます。
AWS CAF とは何か
AWS クラウド導入フレームワークとは、「クラウド導入を成功させるための組織の準備・計画・実行を体系的に支援するベストプラクティス集」といったようなものです。 ビジネス、人員、ガバナンス、プラットフォーム、セキュリティ、オペレーションの6 つのパースペクティブ(観点、視点といった意味)から成り、それぞれに複数のケイパビリティ(機能、能力)が含まれます。
具体的には、以下の様な構成になっています。
| ビジネス | 人員 | ガバナンス |
|---|---|---|
|
戦略管理 ポートフォリオ管理 イノベーション管理 製品管理 戦略的パートナーシップ データの収益化 ビジネスインサイト データサイエンス |
文化の進化 トランスフォーメーションのリーダーシップ クラウドフルエンシー ワークフォースのトランスフォーメーション 変革の促進 組織設計 組織の連携 |
プログラムおよびプロジェクト管理 利益管理 リスク管理 クラウド財務管理 アプリケーションポートフォリオ管理 データガバナンス データキュレーション |
| プラットフォーム | セキュリティ | オペレーション |
|
プラットフォームアーキテクチャ データアーキテクチャ プラットフォームエンジニアリング データエンジニアリング プロビジョニングとオーケストレーション モダンアプリケーション開発 CI/CD |
セキュリティガバナンス セキュリティ保証 ID およびアクセス管理 脅威検出 脆弱性管理 インフラストラクチャ保護 データ保護 アプリケーションのセキュリティ インシデント対応 |
可観測性 イベント管理 インシデントおよび問題管理 変更およびリリース管理 パフォーマンスとキャパシティ 構成管理 パッチ管理 可用性と継続性 アプリケーション管理 |
とにかく覚えにくい
前節の AWS CAF のパースペクティブとケイパビリティのリストを見ると、以下の様に感じるのではないかと思います。
- ケイパビリティの数が多い
- 一貫性のない、雑多な項目の羅列(に見える)
一貫性については、「〇〇管理」が多いのが見て取れますが、それ以外の項目もたくさん含まれています。 一応、パースペクティブはそれぞれ、どのような立場の視点なのかという定義はあるのですが、インシデント対応はセキュリティのパースペクティブであるのに対し、インシデント管理はオペレーションのパースペクティブと、分類の基準がよくわからないものも複数あります。
ですが、事前に模試等の問題に取り組んでみると、どのケイパビリティがなんのパースペクティブに含まれるか、といった問題が少なからず出題されるようでした。 そこで、何とか丸暗記の負担を減らそうと思い考案したのが今回ご紹介する方法です。
いくつかの切り口に分解して覚える
全体をそのまま覚えようとすると大変なので、いくつかの切り口に分割してみます。
「管理」の切り口
おそらく一番多く登場しそうな「管理」というワードを含むケイパビリティのみを抽出してみると、以下の様になります。
| ビジネス | 人員 | ガバナンス |
|---|---|---|
|
戦略管理 ポートフォリオ管理 イノベーション管理 製品管理 |
(なし) |
プログラムおよびプロジェクト管理 利益管理 リスク管理 クラウド財務管理 アプリケーションポートフォリオ管理 |
| プラットフォーム | セキュリティ | オペレーション |
| (なし) |
ID およびアクセス管理 脆弱性管理 |
イベント管理 インシデントおよび問題管理 変更およびリリース管理 構成管理 パッチ管理 アプリケーション管理 |
このようにするとまず、人員とプラットフォームのパースペクティブには「〇〇管理」は含まれないということがわかるので、この点を覚えておくだけでも選択肢を減らすことができそうです。 その他の4つのパースペクティブについては、オペレーションに含まれる「構成管理」、「変更(およびリリース)管理」などは、ITIL に関するものとしてまとめて覚えられると思います。またセキュリティに含まれる「IDおよびアクセス管理」と「脆弱性管理」は、セキュリティから連想できそうではないでしょうか? 残るビジネスとガバナンスの間では判断に迷うものが多く、それらは丸暗記に頼るしかないかもしれませんが、負担は大きく下がっていると思います。
「データ」の切り口
同じようにして、次に多く登場しそうな「データ」というワードについてみてみます。
| ビジネス | 人員 | ガバナンス |
|---|---|---|
|
データの収益化 データサイエンス |
(なし) |
データガバナンス データキュレーション |
| プラットフォーム | セキュリティ | オペレーション |
|
データアーキテクチャ データエンジニアリング |
データ保護 |
(なし) |
ここでも、人員とオペレーションのパースペクティブには「〇〇データ」は含まれないということがわかります。 また、「管理」に比べて数も少ないため、丸暗記でも頑張れそうな気もしてきます。
パースペクティブに固有のワードによる切り口
いずれのパースペクティブにおいても、パースペクティブ名自体を含むケイパビリティはそのパースペクティブに含まれています。 例えば「ビジネス」というワードを含むものは「ビジネスインサイト」のみですが、これはビジネスパースペクティブに含まれる、といった具合です。 また、各パースペクティブと関連の深い(と思われる)ワードで、各パースペクティブに固有で現れるものがあります。 こちらも例を挙げると、「戦略」というワードを含むケイパビリティは「戦略管理」、「戦略的パートナーシップ」の 2 つがあり、これらはどちらもビジネスパースペクティブに含まれます。 こうしたものを他にも探すと、以下のようなものが見つかります。
| ビジネス | ビジネス、戦略 | ||
|---|---|---|---|
| 人員 | 進化、トランスフォーメーション、変革、組織 | ||
| ガバナンス | ガバナンス | ||
| プラットフォーム | プラットフォーム、アーキテクチャ、エンジニアリング | ||
| セキュリティ | セキュリティ、保護 | ||
| オペレーション | オペレーション | ||
| ビジネス | 人員 | ガバナンス |
|---|---|---|
|
戦略管理 戦略的パートナーシップ ビジネスインサイト |
文化の進化 トランスフォーメーションのリーダーシップ ワークフォースのトランスフォーメーション 変革の促進 組織設計 組織の連携 |
データガバナンス |
| プラットフォーム | セキュリティ | オペレーション |
|
プラットフォームアーキテクチャ データアーキテクチャ プラットフォームエンジニアリング データエンジニアリング |
セキュリティガバナンス セキュリティ保証 インフラストラクチャ保護 データ保護 アプリケーションのセキュリティ |
ここでは前述の「管理」や「データ」の切り口で抽出した項目にも、先に挙げた固有のワードを含むものがあり、組合せることでさらに覚えやすくなりそうです。 なお例外として、「セキュリティガバナンス」は「セキュリティ」と「ガバナンス」の両方のパースペクティブ名を含みますが、「セキュリティ」が前に来ているのでセキュリティに含まれる、と考えると覚えられると思います。
その他
以上の 3 つの切り口のいずれにも該当しなかったものを挙げると以下の様になります。 これらはこれ以上どうしようもなさそうですが、分類も明確なものが多いので、覚えるのも容易ではないでしょうか。
| ビジネス | 人員 | ガバナンス |
|---|---|---|
| クラウドフルエンシー | ||
| プラットフォーム | セキュリティ | オペレーション |
|
プロビジョニングとオーケストレーション モダンアプリケーション開発 CI/CD |
脅威検出 インシデント対応 |
可観測性 パフォーマンスとキャパシティ 可用性と継続性 |
まとめ
ここまでの内容を総合して、改めて AWS CAF のパースペクティブとケイパビリティを見てみると以下のようになります。 個人的には一つの大きな表やリストを丸暗記するより、このように分割して部分的に覚えていく方が容易であるように思います。
| ビジネス | 人員 | ガバナンス |
|---|---|---|
|
戦略管理 ポートフォリオ管理 イノベーション管理 製品管理 |
プログラムおよびプロジェクト管理 利益管理 リスク管理 クラウド財務管理 アプリケーションポートフォリオ管理 |
|
|
データの収益化 データサイエンス |
データガバナンス データキュレーション |
|
|
戦略的パートナーシップ ビジネスインサイト |
文化の進化 トランスフォーメーションのリーダーシップ ワークフォースのトランスフォーメーション 変革の促進 組織設計 組織の連携 クラウドフルエンシー |
|
| プラットフォーム | セキュリティ | オペレーション |
| (なし) |
ID およびアクセス管理 脆弱性管理 |
イベント管理 インシデントおよび問題管理 変更およびリリース管理 構成管理 パッチ管理 アプリケーション管理 |
|
データアーキテクチャ データエンジニアリング |
データ保護 |
(なし) |
|
プラットフォームアーキテクチャ プラットフォームエンジニアリング プロビジョニングとオーケストレーション モダンアプリケーション開発 CI/CD |
セキュリティガバナンス セキュリティ保証 インフラストラクチャ保護 アプリケーションのセキュリティ 脅威検出 インシデント対応 |
可観測性 パフォーマンスとキャパシティ 可用性と継続性 |
Elasticsearch で日次の集計結果をさらに集計する方法
日次でどれくらいデータが来ているかを確認したい
Elasticsearch クラスター上のインデックスを調査していて、日々流入するデータの件数がどのくらいなのかがふと気になることがありました。
確認する方法としてまず思いつくのは、Aggregation で date_histgram を出してみることです。
ここでは例として、サンプルデータの kibana_sample_data_logs に対して実行してみます。
GET kibana_sample_data_logs/_search
{
"size": 0,
"aggs": {
"daily_hist": {
"date_histogram": {
"calendar_interval": "day",
"field": "@timestamp"
}
}
}
}
レスポンスは以下のようになります。
(Aggregation の結果部分のみ抜粋)
"aggregations": {
"daily_hist": {
"buckets": [
{
"key_as_string": "2025-01-12T00:00:00.000Z",
"key": 1736640000000,
"doc_count": 249
},
{
"key_as_string": "2025-01-13T00:00:00.000Z",
"key": 1736726400000,
"doc_count": 231
},
{
"key_as_string": "2025-01-14T00:00:00.000Z",
"key": 1736812800000,
"doc_count": 230
},
...
(中略)
...
{
"key_as_string": "2025-03-12T00:00:00.000Z",
"key": 1741737600000,
"doc_count": 230
},
{
"key_as_string": "2025-03-13T00:00:00.000Z",
"key": 1741824000000,
"doc_count": 205
}
]
}
}
どうやら一日あたり 230 件前後の流入があるようですが、日によって変動している様子も見られるので、最大、最小がいくつなのかも気になるところです。 こういう時は Sub-aggregation で算出できるはず...と思ってうろ覚えでクエリを追記してみます。
GET kibana_sample_data_logs/_search
{
"size": 0,
"aggs": {
"daily_hist": {
"date_histogram": {
"calendar_interval": "day",
"field": "@timestamp"
},
"aggs": {
"daily_max": {
"max": {
"field": "doc_count"
}
}
}
}
}
}
しかしこれだと、以下のようになって結果を得ることができません。
(Aggregation の結果部分のみ抜粋)
"aggregations": {
"daily_hist": {
"buckets": [
{
"key_as_string": "2025-01-12T00:00:00.000Z",
"key": 1736640000000,
"doc_count": 249,
"daily_max": {
"value": null
}
},
{
"key_as_string": "2025-01-13T00:00:00.000Z",
"key": 1736726400000,
"doc_count": 231,
"daily_max": {
"value": null
}
},
...
(中略)
...
{
"key_as_string": "2025-03-13T00:00:00.000Z",
"key": 1741824000000,
"doc_count": 205,
"daily_max": {
"value": null
}
}
]
}
この方法ではダメなようなので正しいやり方を調べてみました。
なぜ期待した結果が得られないのか
まず、やりたいのは各バケットごとのデータ件数を出したうえで全バケットの中での最大件数を出すことなのですが、前節の結果を見ると各バケット毎に最大値が算出されようとしているようです。 公式ドキュメントを見ると、Sub-aggregation について以下の記載があります。
Aggregations | Elasticsearch Guide [8.17] | Elastic
Run sub-aggregations
Bucket aggregations support bucket or metric sub-aggregations. For example, a terms aggregation with an avg sub-aggregation calculates an average value for each bucket of documents.
次に、前節の結果では算出された daily_max の値がいずれも null になっています。
Sub-aggregation では、各バケットのドキュメントに対して Aggregation が行われるということですが、前節のクエリで field として指定した doc_count はドキュメントに含まれるフィールドではないので、これが問題であるようです。
実際に、Sub-aggregation のフィールドを @timestamp とすると、以下のように結果が得られます。
GET kibana_sample_data_logs/_search
{
"size": 0,
"aggs": {
"daily_hist": {
"date_histogram": {
"calendar_interval": "day",
"field": "@timestamp"
},
"aggs": {
"daily_max": {
"max": {
"field": "@timestamp"
}
}
}
}
}
}
(Aggregation の結果部分のみ抜粋)
"aggregations": {
"daily_hist": {
"buckets": [
{
"key_as_string": "2025-01-12T00:00:00.000Z",
"key": 1736640000000,
"doc_count": 249,
"daily_max": {
"value": 1736718496637,
"value_as_string": "2025-01-12T21:48:16.637Z"
}
},
{
"key_as_string": "2025-01-13T00:00:00.000Z",
"key": 1736726400000,
"doc_count": 231,
"daily_max": {
"value": 1736800334523,
"value_as_string": "2025-01-13T20:32:14.523Z"
}
},
...
(中略)
...
{
"key_as_string": "2025-03-13T00:00:00.000Z",
"key": 1741824000000,
"doc_count": 205,
"daily_max": {
"value": 1741902326749,
"value_as_string": "2025-03-13T21:45:26.749Z"
}
}
]
}
クエリが動作することは確認できたものの、これはバケット内のドキュメントの @timestamp の最大値を算出するものなので、今回やりたいこととは異なります。
集計結果をさらに集計する方法
別の Aggregation の結果に対してさらに Aggregation を行う場合は、Pipeline aggretation を行う必要があるようです。 公式ドキュメントでは、Pipeline aggregation について以下のように書かれています。
Pipeline aggregations | Elasticsearch Guide [8.17] | Elastic
Pipeline aggregations work on the outputs produced from other aggregations rather than from document sets, adding information to the output tree.
以上を踏まえると、今回やりたいことは以下の様に実現できそうです。
- ドキュメントを日毎にバケット分割する
- バケットごとにドキュメント件数を算出する(1. の doc_count とは別に)
- の結果に対して Pipeline aggregation で最大、最小を算出する
上記の 1. と 2. については、以下のクエリで実行できます。
GET kibana_sample_data_logs/_search
{
"size": 0,
"aggs": {
"daily_hist": {
"date_histogram": {
"calendar_interval": "day",
"field": "@timestamp"
},
"aggs": {
"count": {
"value_count": {
"field": "@timestamp"
}
}
}
}
}
}
ここで、指定する field は必ずしも @timestamp でなくてもよいと思いますが、ドキュメントに必ず含まれていて、値が null ではないフィールドとしてこれを選択しています。
このときの結果は以下のとおりです。
(Aggregation の結果部分のみ抜粋)
"aggregations": {
"daily_hist": {
"buckets": [
{
"key_as_string": "2025-01-12T00:00:00.000Z",
"key": 1736640000000,
"doc_count": 249,
"count": {
"value": 249
}
},
{
"key_as_string": "2025-01-13T00:00:00.000Z",
"key": 1736726400000,
"doc_count": 231,
"count": {
"value": 231
}
},
...
(中略)
...
{
"key_as_string": "2025-03-13T00:00:00.000Z",
"key": 1741824000000,
"doc_count": 205,
"count": {
"value": 205
}
}
]
}
これに Pipeline aggregation を追加すると以下のようになります。
GET kibana_sample_data_logs/_search
{
"size": 0,
"aggs": {
"daily_hist": {
"date_histogram": {
"calendar_interval": "day",
"field": "@timestamp"
},
"aggs": {
"count": {
"value_count": {
"field": "@timestamp"
}
}
}
},
"daily_stats": {
"stats_bucket": {
"buckets_path": "daily_hist>count"
}
}
}
}
このクエリを実行すると、末尾に Pipeline aggregation の結果が追加されます。
(Aggregation の結果部分のみ抜粋)
"aggregations": {
"daily_hist": {
"buckets": [
{
"key_as_string": "2025-01-12T00:00:00.000Z",
"key": 1736640000000,
"doc_count": 249,
"count": {
"value": 249
}
},
{
"key_as_string": "2025-01-13T00:00:00.000Z",
"key": 1736726400000,
"doc_count": 231,
"count": {
"value": 231
}
},
...
(中略)
...
{
"key_as_string": "2025-03-13T00:00:00.000Z",
"key": 1741824000000,
"doc_count": 205,
"count": {
"value": 205
}
}
]
},
"daily_stats": {
"count": 61,
"min": 173,
"max": 329,
"avg": 230.72131147540983,
"sum": 14074
}
ここでは Pipeline aggregation で stats_bucket を使って複数の統計値をまとめて算出していますが、個別の値のみが必要な場合は max_bucket や min_bucket を使用することもできます。
Microsoft Azure Fundamentals 試験に合格しました
記事のネタが尽きて久しいので、先日受験した Microsoft Azure Fundamentals (AZ-900) 試験について書こうと思います。
試験内容
AZ-900 については広く知られていることと思いますが、Microsoft Azure の入門的な内容の試験となります。
個々のサービスについて深い知識は求められないものの、そもそもクラウドサービスとはどういうものか、Microsoft Azure のサービス群にはどのようなものがあるか、といった内容について、広い知識が求められます。 このため、初級レベルの試験とはいえ、しっかり準備をしておく必要があります。
申し込み方法
申し込みは上記公式サイトに Peason Vue のサイトへのリンクがあるので、そこから行いました。 テストセンターでオンサイトで受験する他に、自宅でオンラインで受験することもでき、今回は後者を選択しまています。 申し込みの手続きの中で、オンライン受験を行うための要件についても確認することができます。
- 受験に使用する PC
- 受験を行う部屋
PC については、専用のソフトウェアをダウンロードしてシステムテストを行うことで、要件を満たしているか確認することができます。 インターネット接続はもちろん、Web カメラとマイクがあること、動作しているソフトウェアについてもチェックされるようです。
受験を行う部屋は、静かで、外部から干渉を受けないような場所を選ぶ必要がありますが、自分の場合はリモートワークに使用している個室で受験しました。 場所の確保が難しい場合は、テストセンターへ足を運んだ方が無難かもしれません。
勉強方法
過去に受験した資格試験では書籍を購入して勉強することがほとんどでしたが、今回は Udemy のオンライン講座や問題集を利用してみました。 実際に利用したものはこちらです。
他のコースとの比較や、口コミなどでの事前調査はほとんど行っていません。 Udemny 状で AZ-900 と検索して、上の方に出てきたもののうち、星が多そうなものを選んでいます。
今になって調べてみるともっと安いコースもあるようですが、、正直会社の契約で Udemy Business を利用していたので、当時は値段を見ることさえしていませんでした。 ともあれ、内容として不足はないですし、上記2つのコースのみを利用して、結果、初回の受験で合格することができました。
ポイントとしては、対策講座だけではなく、問題集を使って実践に近い形で問題にあたってみることが大切だと思います。 そうすることで、自分が苦手としている部分を知ることができ、効率よく知識の補強をすることができます。 また、問題集で高い点数を出せるまで勉強することで、本番に向けて自信をつけることもできます。
事前準備
勉強して知識を身に着けることに加えて、当日の受験に対する準備も重要です。 試験の申し込みを行うと、登録したメールアドレスに受験当日の試験開始方法が記載されたメールが届きます。 ここに記載された内容をしっかり読んで、当日必要となる手続きについて確認しておくことをお勧めします。
また、サンドボックスや模擬試験を利用することもでき、実際の試験で出題される問題形式に慣れておくことができます。 こうした準備によって、試験当日の不安要素を減らして実力を発揮できるようにすることでも、合格の可能性を高めることができると思います。
試験当日
試験当日は、30分前からチェックインすることができます。 オンラインの場合では、チェックイン後、試験開始までに、受験する部屋の確認等、やるべきことがいくつかあります。 ギリギリまで勉強するのもいいですが、時間に余裕をもってチェックインしておかないと、結局試験開始までに手間取って時間通りに開始できなかったり、最悪試験を受けられなくなると、本末転倒になってしまいます。
私はオンライン受験は今回が 2 回目だったので、少し早めにチェックインしていましたが、それでも机上や部屋の四方の写真を撮ったりなどでそれなりに時間を要してしまいました。 また、試験開始時前には、オンラインで試験官のチェックを受けるタイミングもあります。 口頭で少し話す場面もあるのですが、今回は試験官は日本語の話せる方を選択できたので、その点は助かりました。
一点、写真付き身分証明書をすぐに提示できるように机上に出しておいたところ、本来は NG だったらしく、これを「手の届かないところにしまう」という作業にも時間を取られてしまいました。 こういったこともあるので、やはり準備はいくらやってもやりすぎということはないなと思います。
結果通知
試験結果は、試験終了直後に画面上で確認できます。 また、別途 Peason Vue のサイトより、以下のようなスコアレポートを表示することができます。

これを見ると、合格ラインは 700 点となっているようです。 今回は初級レベルの試験だったこともあり、かなり高い得点で合格することができました。
Microsoft Lern のサイトでも、自身のプロファイルの資格証明のページで、過去の試験結果や、取得した認定資格について確認することができます。
このように目に見える形で結果を確認できると、次の資格取得へのモチベーションにつながりますね。
Kibana REST API を DevToos のコンソールで実行する
DevTools のコンソールでは、ログインしたユーザーの権限で Elasticsearch の API を実行できるので便利です。 しかし、以下のようにエラーになってしまうことがあります。
GET /api/data_views
# レスポンス
{
"error": "Incorrect HTTP method for uri [/api/data_views?pretty=true] and method [GET], allowed: [POST]",
"status": 405
}
POST は許可されているということですが、実際にやってみるとエラーになってしまうので、メソッドの間違いでもなさそうです。
POST /api/data_views
# レスポンス
{
"error": "no handler found for uri [/api/data_views?pretty=true] and method [POST]"
}
Kibana REST API の実行方法
調べてみると、Data Views API は Elasticsearch ではなく、Kibana の API であるようです。
Get all data views API | Kibana Guide [8.12] | Elastic
Requestedit
GET <kibana host>:<port>/api/data_views
GET <kibana host>:<port>/s/<space_id>/api/data_views
この場合、ホスト名やポート番号についても、Kibana のものを指定する必要があるようです。 実際にDevTools ではなく curl で実行してみたところ、Kibana のホスト名やポート番号を指定することで実行可能でした。
# curl -s -u admin http://localhost:5601/api/data_views | jq .
Enter host password for user 'admin':
{
"data_view": [
{
"id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f",
"namespaces": [
"default"
],
"title": "kibana_sample_data_ecommerce",
"typeMeta": {},
"name": "Kibana Sample Data eCommerce"
}
]
}
しかし、DevTools では実行するホスト名やポート番号を指定しても、URI の先頭に / が補完されてしまうようで、うまく実行できません。
GET http://localhost:5601/api/data_views
# レスポンス
{
"error": "no handler found for uri [/http%3A//localhost%3A5601/api/data_views?pretty=true] and method [GET]"
}
DevTools のコンソールにおける Kibana API の実行方法
何か方法はないものかと探してみたところ、Elastic のドキュメントに記載がありました。
REST API | Kibana Guide [8.12] | Elastic
Using the APIsedit Prepend any Kibana API endpoint with kbn: and send the request through Dev Tools > Console. For example:
GET kbn:/api/index_management/indices
どうやら URI の先頭に kbn: をつけることで、DevTool のコンソール上で Kibana API を実行できるようです。
実際に試してみると、うまく実行することができました。
GET kbn:/api/data_views
# レスポンス
{
"data_view": [
{
"id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f",
"namespaces": [
"default"
],
"title": "kibana_sample_data_ecommerce",
"typeMeta": {},
"name": "Kibana Sample Data eCommerce"
}
]
}
DevTools コンソールにおける Kibana API 実行時の注意事項
API はうまく実行できましたが、ドキュメント上では上記引用部分に続けて以下の記載もあります。
Note: this will automatically prefix
s/{space_id}/on the API request if ran from a non-default Kibana Space.
デフォルト以外のスペースで実行した場合には、スペース ID の指定が自動で追加されるようです。
Data views API は、Data view の一覧を表示するものですが、Data view は特定のスペースに対して設定するものです。 スペース ID を指定して Data views API を実行することで、指定したスペースに設定された Data view の一覧を取得することができます。
前節の実行例はデフォルトスペースで実行したもので、スペース ID の指定は行っていません。 同様にデフォルトスペースでスペース ID を指定して実行すると以下のようになります。
GET kbn:/s/default/api/data_views
# レスポンス
{
"data_view": [
{
"id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f",
"namespaces": [
"default"
],
"title": "kibana_sample_data_ecommerce",
"typeMeta": {},
"name": "Kibana Sample Data eCommerce"
}
]
}
デフォルトスペースの ID を指定すると、未指定の場合と同様の結果になります。 この場合は未指定とすることでデフォルトスペースの情報が表示されているものと考えられます。
GET kbn:/s/test1/api/data_views
# レスポンス
{
"data_view": [
{
"id": "7c9b9a4e-91e5-4ea1-b2d5-7445481a588f",
"namespaces": [
"test1"
],
"title": "test1-*",
"typeMeta": {},
"name": "test1"
}
]
}
デフォルト以外のスペース ID を指定すると、先ほどとは異なる結果となり、指定したスペースの情報が表示されているようです。
一方で、Kibana のスペースを test1 に切り替えて実行すると以下のようになります。
GET kbn:/api/data_views
#
{
"data_view": [
{
"id": "7c9b9a4e-91e5-4ea1-b2d5-7445481a588f",
"namespaces": [
"test1"
],
"title": "test1-*",
"typeMeta": {},
"name": "test1"
}
]
}
ここではスペース ID の指定を行っていませんが、先ほど test1 のスペース ID を指定した場合と同様の結果となっています。これは URI の先頭に /s/test1 が追加されたためと考えられます。
一方で、明示的にスペース ID の指定を行った場合は、いずれのスペース ID でも 404 エラーとなってしまいました。
GET kbn:/s/default/api/data_views
# レスポンス(404 Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
GET kbn:/s/test1/api/data_views
# レスポンス(404 Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
Data view API 以外での挙動は確認していませんが、DevTools のコンソールで Kibana API を実行する際の知識として覚えておいた方がよさそうです。
Elasticsearch のドキュメント更新をすっきり理解する
Elasticsearch におけるドキュメントの更新は、ドキュメントの登録の場合と同じく、いくつかやり方がありそうです。 こちらも自分なりに調べた結果をまとめてみます。
Index API の _doc エンドポイント
前回の記事で調べた通り、_doc エンドポイントでは既存のドキュメントに対して、同じドキュメント ID で PUT または POST メソッドでリクエストを行うことで、ドキュメントの更新が可能です。
ここでは、あらかじめ以下のようにドキュメントを登録しておいたものとします。
PUT test-index/_doc/1
{
"field1": "foo"
}
この時、以下のようにリクエストを行うことで、フィールドの値を変更することができます。
PUT test-index/_doc/1
{
"field1": "bar"
}
# レスポンス
{
"_index": "test-index",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
レスポンスの result フィールドが updated となっていて、更新が行われたことがわかります。POST メソッドを用いた場合も、同様の応答となります。
更新されたドキュメントを実際に取得してみると、フィールドの値が更新時に指定したものになっています。
GET test-index/_doc/1
# レスポンス
{
"_index": "test-index",
"_id": "1",
"_version": 2,
"_seq_no": 1,
"_primary_term": 1,
"found": true,
"_source": {
"field1": "bar"
}
}
では、異なるフィールド名を指定した場合はどうなるか試してみます。
PUT test-index/_doc/1
{
"field2": "bar"
}
この場合も同様にレスポンスの result フィールドは updated となりますが、更新されたドキュメントを確認してみると、以下のようになっています。
GET test-index/_doc/1
# レスポンス
{
"_index": "test-index",
"_id": "1",
"_version": 3,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"field2": "bar"
}
}
更新後のドキュメントには field2 が含まれていますが、field1 は含まれていません。つまりここでのドキュメントの更新は、全体の上書き、または置き換えになっています。
更新時に既存のドキュメントのすべてのフィールドとその値を記述し、加えて追加のフィールドと値を記述することで、フィールドを追加することができます。しかし、既存のドキュメントが多数のフィールドを含む場合にはこれは大変な作業になります。
では、既存ドキュメントの一部のみを更新したい場合には、どうするのが良いのでしょうか。
Update API を使ったドキュメントの更新
既存のドキュメントの一部を更新する方法について、公式ドキュメント では以下のように紹介されています。
Update part of a documentedit
The following partial update adds a new field to the existing document:
POST test/_update/1 { "doc": { "name": "new_name" } }
前節の例に続けて以下のようにすることで、フィールドの追加を行うことができます。
POST test-index/_update/1
{
"doc": {
"field3": "baz"
}
}
# レスポンス
{
"_index": "test-index",
"_id": "1",
"_version": 4,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
更新されたドキュメントを見てみると、field3 が追加され、もともとあった field2 も残っていることがわかります。
GET test-index/_doc/1
{
"_index": "test-index",
"_id": "1",
"_version": 4,
"_seq_no": 3,
"_primary_term": 1,
"found": true,
"_source": {
"field2": "bar",
"field3": "baz"
}
}
もちろん、既存のフィールドの値を変更することもできます。
POST test-index/_update/1
{
"doc": {
"field3": "foo"
}
}
GET test-index/_doc/1
{
"_index": "test-index",
"_id": "1",
"_version": 5,
"_seq_no": 4,
"_primary_term": 1,
"found": true,
"_source": {
"field2": "bar",
"field3": "foo"
}
}
ここでは、更新時に指定した field3 の値が変更されている一方で、指定していない field2 については元の値のままになっています。
既存ドキュメントのフィールドを削除する方法
既存のドキュメントに対して、フィールドの値の変更やフィールドの追加を行えることがわかりました。
_doc エンドポイントを用いる方法では、更新時に指定した内容でドキュメントの上書きを行うため、必要ないフィールドについては更新時に指定しないことで、結果として削除することができます。
しかし、やはり多数のフィールドを持つドキュメントから少数のフィールドを削除したい場合には、記述する内容が多くなるため、大変な作業になります。
Update API を用いて、指定したフィールドのみを削除する方法はないものでしょうか。
まず思いつくのは、値を null にすることです。
PUT test-index/_doc/1
{
"field2": null
}
# レスポンス
{
"_index": "test-index",
"_id": "1",
"_version": 6,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 5,
"_primary_term": 1
}
しかしこの方法では、対象のフィールドの値が null に変更されるのみで、フィールドそのものは削除されません。
GET test-index/_doc/1
# レスポンス
{
"_index": "test-index",
"_id": "1",
"_version": 6,
"_seq_no": 5,
"_primary_term": 1,
"found": true,
"_source": {
"field2": null,
"field3": "foo"
}
}
Update APIでは、doc 要素を指定して更新を行うだけでなく、スクリプト実行を実行することができ、これによりフィールドの削除を行うことができます。
(むしろ公式ドキュメントではスクリプトの実行の方が先に記載されています)
Conversely, this script removes the field new_field:
POST test/_update/1 { "script" : "ctx._source.remove('new_field')" }
前節までの例では、以下のようにして field2 を削除することができます。
POST test-index/_update/1
{
"script": "ctx._source.remove('field2')"
}
{
"_index": "test-index",
"_id": "1",
"_version": 6,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 5,
"_primary_term": 1
}
GET test-index/_doc/1
{
"_index": "test-index",
"_id": "1",
"_version": 6,
"_seq_no": 5,
"_primary_term": 1,
"found": true,
"_source": {
"field3": "foo"
}
}
参考
Elasticsearch へのドキュメント登録をすっきり理解する
Elasticsearch を利用するにあたり、ドキュメントの登録の仕方を調べようと思ってググってみると、いろいろな記述が見つかります。
どうやらやり方はいくつかありそうなものの、リクエストのメソッドは PUT と POST、どちらにするのが良いのか、ドキュメント ID の指定は必要なのか等、すっきりしないことが多々ありました。
ここでは、自分なりに調べた結果をまとめてみます。
_doc と _create
Elasticsearch へのドキュメントの登録について、多くの記事では _doc エンドポイントに関する記述があります。一方、Elasticsearch の Index API には、_create エンドポイントというのもあるようです。
Elastic の公式ドキュメントには、以下の記述があります。
Request
PUT /<target>/_doc/<_id>
POST /<target>/_doc/
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>
Prerequisites
(略)
- To add or overwrite a document using thePUT /<target>/_doc/<_id>request format, you must have thecreate,index, orwriteindex privilege.
- To add a document using thePOST /<target>/_doc/,PUT /<target>/_create/<_id>, orPOST /<target>/_create/<_id>request formats, you must have thecreate_doc,create, index, orwriteindex privilege.
<target> の部分には対象のインデックス名を指定します。上記の記述を見る限り、PUT /<target>/_doc/<_id> という形式でリクエストを行うことで、ドキュメントの追加、または上書きができるようです。
一方、その他の形式に関しては、ドキュメントの追加については記述がありますが、上書きについては記述がありません。
_doc エンドポイントによるドキュメント登録
_doc エンドポイントでは、メソッドとして PUT と POST を使うことができます。また、リクエスト URI についても、/<target>/_doc/<_id> と /<target>/_doc/ のように、ドキュメント ID を含むものと含まないものの2つのパターンがあるようです。
ドキュメント ID を含む URI を指定した場合は、PUT と POST どちらのメソッドでもリクエストを行うことができます。指定したドキュメント ID がまだ存在しない場合は、リクエストボディとして送信したドキュメントが、指定したドキュメント ID で登録されることとなります。
レスポンスコードはいずれのメソッドの場合も 201 Created となり、レスポンスボディの result フィールドは created となります。
PUT test-index/_doc/1
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
POST /test_index/_doc/2
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "2",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
ドキュメント ID を含まない URI に対しては、POST メソッドでのみリクエストを行うことができます。この場合、ドキュメント ID は Elasticsearch により自動採番されます。
一方、ドキュメント ID を含まない URI に対して PUT メソッドでリクエストを行った場合は、下記のようにエラーとなってしまいます。
PUT /test_index/_doc/
{
"field1": "foo"
}
# レスポンスコード 405 Not Allowed
{
"error": "Incorrect HTTP method for uri [/test-index/_doc/] and method [PUT], allowed: [POST]",
"status": 405
}
POST test-index/_doc/
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "s8jQ44wBuSrg6d_5yvgh",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
こうした挙動は、Elasticsearch の API の実装というより、元々の PUT メソッドの用途に起因するようです。
(Wikipediaより)
PUT
指定したURIにリソースを保存する。URIが指し示すリソースが存在しない場合は、サーバはそのURIにリソースを作成する。画像のアップロードなどが代表的。
PUT メソッドでは、リソース (ここではドキュメント) の URI を指定して保存を行うので、ドキュメント ID を含む URI を指定する必要があるものと考えられます。実際のところ、対象のドキュメントを取得する場合は、同じくドキュメント ID を含む URI を指定して GET メソッドによりリクエストを行います。
GET test-index/_doc/1
# レスポンスコード 200 OK
{
"_index": "test-index",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"field1": "foo"
}
}
_doc エンドポイントによるドキュメントの上書き
ドキュメント ID を含む URI を指定して PUT または POST メソッドでリクエストを行う際、指定したドキュメント ID が既に存在している場合は、ドキュメントの上書きが行われます。
この場合はいずれのメソッドでもレスポンスコードは 200 OK となり、result フィールドは updated となります。また、_version フィールドの値が 1 ずつ増加していることがわかります。
PUT test-index/_doc/1
{
"field1": "foo"
}
# レスポンスコード 200 OK
{
"_index": "test-index",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
POST /test_index/_doc/1
{
"field1": "foo"
}
# レスポンスコード 200 OK
{
"_index": "test-index",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1
}
ドキュメント ID を含まない URI を指定する場合は、ドキュメント ID は自動採番されるため、指定したドキュメント ID が既に存在しているということはあり得ません。このため、ドキュメントの上書きも発生し得ないことになります。
ドキュメント ID を含まない URI を指定して POST メソッドで複数回リクエストを行うと、毎回新たなドキュメント ID でドキュメントが新規作成されます。
POST test-index/_doc/
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "tMjW44wBuSrg6d_5QviD",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 5,
"_primary_term": 1
}
POST test-index/_doc/
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "tcjW44wBuSrg6d_5kPiU",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 6,
"_primary_term": 1
}
_create エンドポイントによるドキュメント登録
_create エンドポイントでも、メソッドとして PUT と POST を使うことができます。URI については、ドキュメント ID を含むものを指定する必要があります。指定したドキュメント ID が存在しない場合は、ドキュメントの新規作成が行われます。
PUT test-index/_create/3
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "3",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 7,
"_primary_term": 1
}
POST /test_index/_create/4
{
"field1": "foo"
}
# レスポンスコード 201 Created
{
"_index": "test-index",
"_id": "4",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 8,
"_primary_term": 1
}
_create エンドポイントでは、ドキュメントの上書きは行われません。URI の中で指定したドキュメント ID が既に存在する場合は、上書きは行われず、下記のようにエラーとなります。
PUT test-index/_create/1
{
"field1": "foo"
}
# レスポンスコード 409 Conflict
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
},
"status": 409
}
POST /test_index/_create/1
{
"field1": "foo"
}
# レスポンスコード 409 Conflict
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
},
"status": 409
}
また、ドキュメント ID を含まない URI を指定した場合は、いずれのメソッドを用いた場合でもエラーとなります。ただし、エラーの内容はそれぞれ異なります。
PUT メソッドでリクエストを行った場合は、メソッドが利用できないとのメッセージがレスポンスとして返されます。一方で、POST メソッドは許可されているようなので、こちらは実行できそうなものの、実際にリクエストを行ってみると Bad Request エラーとなってしまいます。
PUT test-index/_create/
{
"field1": "foo"
}
# レスポンスコード 405 Method Not Allowed
{
"error": "Incorrect HTTP method for uri [/test-index/_create/] and method [PUT], allowed: [POST]",
"status": 405
}
POST test-index/_create/
{
"field1": "foo"
}
# レスポンスコード 400 Bad Request
{
"error": "no handler found for uri [/test-index/_create/] and method [POST]"
}
op_type パラメータの利用
ElasticSearch の Index API においてドキュメント登録時の挙動に関係する要素としては、op_type パラメータというものもあります。
(Elastic の公式ドキュメントより)
op_type(Optional, enum) Set tocreateto only index the document if it does not already exist (put if absent). If a document with the specified_idalready exists, the indexing operation will fail. Same as using the<index>/_createendpoint. Valid values:index,create. If document id is specified, it defaults toindex. Otherwise, it defaults tocreate.
このパラメータの値は index または create とすることができ、create とした場合は作成のみを行うことができます。リクエスト URI の中でドキュメント ID を指定した場合は、パラメータのデフォルト値は index であり、そうでない場合は create となるようです。ドキュメント ID を指定しない場合は、メソッドが利用できないなどの理由でエラーとなる場合を除いては、自動採番されたドキュメント ID とともにドキュメントが新規作成されます。このため、op_type が create または index どちらの値であっても、挙動に違いはないものと考えられます。
実際の挙動を確認すると、_doc エンドポイントへのリクエストで op_type を create とすると、指定したドキュメント ID が既存のものであった場合には以下のようにエラーとなります。
PUT /test-index/_doc/1?op_type=create
{
"field1": "foo"
}
# レスポンスコード 409 Conflict
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
},
"status": 409
}
POST test-index/_doc/1?op_type=create
{
"field1": "foo"
}
# レスポンスコード 409 Conflict
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [3])",
"index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
"shard": "0",
"index": "test-index"
},
"status": 409
}
_create エンドポイントについても op_type パラメータの指定は可能ですが、create 以外の値は利用できなさそうです。
PUT /test-index/_create/1?op_type=index
{
"field1": "foo"
}
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "opType must be 'create', found: [index]"
}
],
"type": "illegal_argument_exception",
"reason": "opType must be 'create', found: [index]"
},
"status": 400
}
POST /test-index/_create/1?op_type=index
{
"field1": "foo"
}
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "opType must be 'create', found: [index]"
}
],
"type": "illegal_argument_exception",
"reason": "opType must be 'create', found: [index]"
},
"status": 400
}
まとめ
以上をまとめると、op_type を明示的に指定しない場合、_doc エンドポイントへのリクエストとその結果は以下のようになります。
| リクエストメソッド | URI | ドキュメント ID | |
|---|---|---|---|
| 新規 | 既存 | ||
| PUT | /<target>/_doc/<_id> | 新規作成(201 Created) | 上書き(200 OK) |
| POST | /<target>/_doc/<_id> | 新規作成(201 Created) | 上書き(200 OK) |
| PUT | /<target>/_doc/ | エラー(405 Method Not Allowed) | - |
| POST | /<target>/_doc/ | 新規作成(201 Created) * ドキュメント ID は自動採番 | - |
同様に _create エンドポイントについては以下のとおりです。
| リクエストメソッド | URI | ドキュメント ID | |
|---|---|---|---|
| 新規 | 既存 | ||
| PUT | /<target>/_create/<_id> | 新規作成(201 Created) | エラー(409 Conflict) |
| POST | /<target>/_create/<_id> | 新規作成(201 Created) | エラー(409 Conflict) |
| PUT | /<target>/_create/ | エラー(405 Method Not Allowed) | - |
| POST | /<target>/_create/ | エラー(400 Bad Request) | - |
まず、ドキュメント ID を含まない URI に対しては、_doc エンドポイントに POST メソッドでリクエストを行う以外の方法ではエラー (400 Bad Request や 405 Method Not Allowed) となるため、やり方は一通りしかないことがわかります。また、リクエストの結果については、ドキュメント ID が新規に自動採番されるため、ドキュメント新規作成のみ、となります。
次に、ドキュメント ID を含む URI へのリクエストについては、それぞれ op_type がどのようになっているかにより挙動が異なります。op_type は create または index の値をとることができ、create とした場合はドキュメントの新規作成のみ、index とした場合は、ドキュメントの新規作成、または上書きのいずれかが行われます。
_create エンドポイントでは op_type の値を index にしようとするとエラー(400 Bad Request)となるため、op_type の値は常に create となっているものと考えられます。このため、ドキュメントの新規作成のみを行うことができ、URI の中で既存のドキュメント ID を指定してリクエストを行うとエラー(409 Conflict)となります。
_doc エンドポイントでは、URI の中でドキュメント ID を指定する場合は op_type はデフォルトで index となるため、指定したドキュメント ID が既に存在するかどうかに応じて、ドキュメントの新規作成、または上書きのいずれかが行われます。一方、_doc エンドポイントでもリクエスト時に op_type の値を create とすることができ、この場合は既存のドキュメント ID を指定して上書きが発生するような状況ではエラー (409 Conflict) が発生します。
結局どうするのが良いのか
以下は私見となりますが、利用する場面に応じて以下のように使い分けするのが良さそうです。
ドキュメントの登録時にドキュメント ID にはこだわらない場合
POST /<target>/_doc/
_doc エンドポイントに POST メソッドでリクエストを行う必要があり、そのほかの方法ではエラーとなります。
ドキュメント ID を指定しなくても自動採番されるため、指定が必要な情報が最も少なく、スムーズかと思います。
ドキュメントの登録時にドキュメント ID を明示的に指定したい場合
PUT /<target>/_doc/<_id>
POST /<target>/_doc/<_id>
いずれの方法でも違いはなさそうなので、ドキュメント ID を指定しない場合と同様、POST メソッドのみ使うのでもよいように思えます。
ドキュメントの登録時にドキュメント ID を明示的に指定したい、かつ新規作成のみを行いたい場合
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>
PUT /<target>/_doc/<_id>?op_type=create
POST /<target>/_doc/<_id>?op_type=create
指定したドキュメント ID が既に存在する場合はエラー(409 Conflict)応答を受け取ることができます。
こちらもいずれの方法でも違いはなさそうですが、_create エンドポイントと op_type の指定はやりやすい方を選べばよいかと思います。
参考
Index API | Elasticsearch Guide [8.11] | Elastic