データを保存・検索・分析するための分散型検索エンジン
主な特徴
- 分散アーキテクチャ:複数のノードに分散してデータを保存・処理できるため、大量のデータを効率的に扱える
- リアルタイム検索:データの投入とほぼ同時に検索可能になる
- RESTful API:HTTPベースのAPIで簡単にデータの投入・検索ができる
- スキーマレス:事前にデータ構造を定義せずに柔軟にデータを格納できる
- 全文検索:高度な全文検索機能とフィルタリング機能を提供
KibanaはElasticsearchに格納されたデータを可視化・分析するためのWebベースのツールです。 主な機能
- データ可視化:グラフ、チャート、地図などでデータを視覚的に表現
- ダッシュボード:複数の可視化を組み合わせた監視画面を作成
- 検索・フィルタリング:直感的なUIでデータを検索・絞り込み
- リアルタイム監視:データの変化をリアルタイムで追跡
- アラート機能:特定の条件に基づいて通知を設定
- データ収集:Logstashやbeatsがログやメトリクスを収集
- データ蓄積:Elasticsearchにデータを格納・インデックス化
- データ可視化:Kibanaでデータを分析・可視化
Elasticsearchは、特に大量の非構造化または半構造化テキストデータに対して、ユーザーが求める情報を高速かつ柔軟に探し出すことに特化したシステム。
通常のデータベースがデータの厳密な管理と整合性を重視するのに対し、Elasticsearchは検索のパフォーマンスと多様な検索機能に重点を置いている。
そのため、両者は異なる目的で利用され、多くのシステムでは両方を組み合わせて活用されてる。
例えば、マスターデータはRDBに格納し、検索用のデータはElasticsearchに同期して全文検索を提供する、といった連携が一般的。
docker-compose up -dで起動後、下記URLにアクセス。※少し時間がたったあとにアクセス
http://localhost:5601/
データ投入(実際にはログをこのようにみることが多い)
# 投入データ
curl -X POST "localhost:9200/sample-logs/_doc" \
-H "Content-Type: application/json" \
-d '{
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)'",
"level": "INFO",
"message": "Application started successfully",
"service": "web-app",
"host": "server01"
}'
# レスポンス
{"_index":"sample-logs","_id":"pmmzA5gBDAT1gSav8rtQ","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}
クエリを入力後RUNを押下して、ログを表示。Table形式かJSON形式でログが見れる

※elastic Search生で見ることも可能
リクエスト
curl "localhost:9200/sample-logs/_search?size=5&sort=timestamp:desc"
レスポンス
{"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":null,"hits":[{"_index":"sample-logs","_id":"pmmzA5gBDAT1gSav8rtQ","_score":null,"_source":{
"timestamp": "2025-07-13T12:13:01.3NZ",
"level": "INFO",
"message": "Application started successfully",
"service": "web-app",
"host": "server01"
},"sort":[1752365581300]}]}}%
web上から確認できるアプリで、Monologをelastic-searchにログを流し込み
設定ファイル作成
php ./vendor/bin/phinx init
Phinx by CakePHP - https://phinx.org. 0.16.10
created /var/www/html/phinx.php
migrationファイル作成
php ./vendor/bin/phinx create Reviews
Phinx by CakePHP - https://phinx.org. 0.16.10
using config file phinx.php
using config parser php
using migration paths
- /var/www/html/db/migrations
using seed paths
- /var/www/html/db/seeds
using migration base class Phinx\Migration\AbstractMigration
using default template
created db/migrations/20251004050146_reviews.php
# マイグレーション実行
php vendor/bin/phinx migrate
# ロールバック
php vendor/bin/phinx rollback
状況把握
root@0793a7f44b41:/var/www/html# php ./vendor/bin/phinx status
Phinx by CakePHP - https://phinx.org. 0.16.10
using config file phinx.php
using config parser php
using migration paths
- /var/www/html/db/migrations
using seed paths
- /var/www/html/db/seeds
warning no environment specified, defaulting to: development
ordering by creation time
Status [Migration ID] Started Finished Migration Name
----------------------------------------------------------------------------------
down 20251004050146 Reviews
down 20251004050246 Tags
down 20251004050309 ReviewTags
seeder
php ./vendor/bin/phinx seed:run
Phinx by CakePHP - https://phinx.org. 0.16.10
using config file phinx.php
using config parser php
using migration paths
- /var/www/html/db/migrations
using seed paths
- /var/www/html/db/seeds
warning no environment specified, defaulting to: development
using adapter mysql
using database development_db
== DummyData: seeding
== DummyData: seeded 0.0381s
All Done. Took 0.0501s
reviewインデックスへの登録(読み取り専用)
登録処理
public function syncReview(): void
{
$reviews = DB::table('reviews')
->select('reviews.*')
->get();
foreach ($reviews as $review) {
try {
// オブジェクト型でないとESに入らないためここで
$tags = DB::table('review_tags')
->join('tags', 'review_tags.tag_id', '=', 'tags.id')
->where('review_tags.review_id', $review->id)
->select('tags.id', 'tags.tag_name')
->get();
// ElasticSearchに保存
$this->client->index([
'index' => 'reviews',
'id' => $review->id,
'body' => [
'title' => $review->title ?? '',
'content' => $review->content ?? '',
'tags' => $tags->toArray(),
'created_at' => $review->created_at
]
]);
} catch (\Exception $e) {
echo "✗ レビューID {$review->id} の同期に失敗: {$e->getMessage()}\n";
}
}
}
検索
public function searchIndex(string $keyword)
{
// キーワードで検索して最初の1件を取得
$response = $this->client->search([
'index' => 'reviews',
'body' => [
'query' => [
'match' => [
'content' => $keyword
]
],
'size' => 1 // 1件だけ取得
]
]);
return $response;
}
- 検索用のES(負荷軽減のための読み取り専用)
- 100万件以上のデータ
- 大規模ECサイト(Amazon、楽天クラス)
- ログ分析(1日数億レコード)
- SNSの投稿検索
- 大手ニュースサイト
RDBとの比較
| 概念 | RDB | ElasticSearch |
|---|---|---|
| データベース | データベース全体 | Cluster(クラスター) |
| サーバー | DBサーバーインスタンス | Node(ノード) |
| データの保存場所 | Table(テーブル) | Index(インデックス) |
| 1件のデータ | Row(行) | Document(ドキュメント) |
| データ項目 | Column(列) | Field(フィールド) |
| スキーマ定義 | CREATE TABLE | Mapping |
| 分割 | パーティーション | シャード |
インデックスのサンプル
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"product_id": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"description": {
"type": "text"
},
"price": {
"type": "float"
},
"category": {
"type": "keyword"
},
"stock_count": {
"type": "integer"
},
"is_available": {
"type": "boolean"
},
"created_at": {
"type": "date",
"format": "yyyy/MM/dd HH:mm:ss
}
}
}
}
indexの登録
bash put_mapping.txt
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "products_index"
}
データの投入
python bulk_import.py
settingについて
-
number_of_shards
意味: そのインデックスが分割されるプライマリシャード(Primary Shard)の数。
役割: シャードは、インデックス内のデータを水平に分割し、クラスター内の複数のノード(サーバー)に分散して格納するための、Elasticsearchにおける最小の作業単位です。
重要性:データの分散と並列処理を可能にし、スケーラビリティ(データ量と処理能力の拡張性)とパフォーマンス(検索速度)に直結します。一度インデックスを作成すると、この値は後から変更できません(変更するにはReindexが必要です)。
サンプル値 1 の意味: このインデックスのデータは1つのプライマリシャードにのみ格納されます。小規模な環境や学習用途では 1 が一般的です。 -
number_of_replicas
意味: 各プライマリシャードに対して作成されるレプリカシャード(Replica Shard)の数。
役割: プライマリシャードの完全なコピーであり、ノードの障害時などに冗長性(耐障害性)を提供します。また、検索リクエストを処理することで読み込みスケーラビリティを向上させます。
重要性:耐障害性: プライマリシャードが配置されているノードがダウンしても、レプリカシャードが新しいプライマリとして自動的に昇格するため、サービスを継続できます。
検索負荷分散: 検索リクエストはプライマリシャードとレプリカシャードの両方に分散されます。
サンプル値 1 の意味: 各プライマリシャード(今回は1つ)に対して、1つのレプリカシャードが作成されます。結果として、合計で$1 \text{ (プライマリ)} + 1 \text{ (レプリカ)} = 2$ つのシャードセットが論理的に存在することになります。
mapping項目の説明
| フィールド名 | データ型 (type) | 目的・解説 | 重要なポイント |
|---|---|---|---|
| product_id | keyword | 商品IDなど、一意で変更しない識別子に使用。完全一致検索や集計用。 | **ソートや集計(Aggregation)**に使えます。 |
| name | text + keyword (Multi-fields) | 商品名の全文検索用。 | text型は単語に分解されます。fields.keywordはソート/フィルタリング用に完全一致検索を可能にします。 |
| description | text | 商品の説明文など、長文の全文検索用。 | 関連性の高いドキュメントを抽出するのに使われます。 |
| price | float | 価格データ。 | 範囲検索(例:1000円以上2000円以下)に使えます。 |
| category | keyword | カテゴリ名など、フィルタリングに使われる値。 | 高速な絞り込みや**集計(カテゴリごとの商品数など)**に最適です。 |
| stock_count | integer | 在庫数。 | 範囲検索やソートに使えます。 |
| is_available | boolean | 販売可否。 | 真偽値による高速なフィルタリングに使えます。 |
| created_at | date | 作成日時。 | 期間指定検索(例:先月登録された商品)に使えます。 |
migrationライブラ入り
https://qiita.com/macchaka/items/3decc5f48a15f00e188c
https://qiita.com/KWS_0901/items/c300b5ee010cb48dbaa3
https://qiita.com/wooooo/items/8be2c7e1ab75a7a54851
