Elasticsearch

2024年9月にAGPLv3ライセンスでオープンソース回帰した検索・分析エンジン。PyTorch機械学習統合、ベクトル検索機能強化、Lucene 9による性能向上が特徴。

監視サーバー検索エンジンログ分析ELKスタック分散検索全文検索リアルタイム分析

監視サーバー

Elasticsearch

概要

Elasticsearchは2024年9月にAGPLv3ライセンスでオープンソース回帰した検索・分析エンジンです。PyTorch機械学習統合、ベクトル検索機能強化、Lucene 9による性能向上が特徴で、ELKスタック(Elasticsearch、Logstash、Kibana)の中核コンポーネントとして継続成長しています。AI時代に対応した次世代検索プラットフォームとして進化を続けています。

詳細

Elasticsearchは2010年にShay Banonによって開発が開始され、2024年9月にオープンソース回帰で注目を集めています。PyTorch機械学習統合、ベクトル検索機能強化でAI時代に対応し、ELKスタックの中核コンポーネントとして継続成長を続けています。バージョン8.16.0+では更なる機能強化が図られ、分散リアルタイム検索の新時代を牽引しています。

主要な技術的特徴

  • 分散アーキテクチャ: 水平スケーリングとハイアベイラビリティ
  • RESTful API: HTTP/JSONベースの統一インターフェース
  • リアルタイム検索: near real-time検索とインデックス機能
  • 豊富なクエリDSL: 複雑な検索条件の柔軟な表現
  • アグリゲーション: 高度なデータ分析と集計機能

用途

  • ログ分析とモニタリング
  • 全文検索システム
  • ビジネス分析とダッシュボード
  • セキュリティ情報・イベント管理(SIEM)
  • アプリケーション性能監視(APM)

メリット・デメリット

メリット

  • 高性能検索: 大規模データでの高速検索機能
  • スケーラビリティ: 水平分散による高いスケーラビリティ
  • 豊富なエコシステム: Kibana、Logstash等との統合
  • リアルタイム分析: 即座のデータ分析と可視化
  • 柔軟なスキーマ: 動的マッピングによる柔軟なデータ構造
  • オープンソース回帰: 2024年9月からAGPLv3で利用可能

デメリット

  • メモリ消費: 大量のヒープメモリが必要
  • 複雑な運用: クラスター管理の複雑さ
  • データ永続性: プライマリストレージとしての制限
  • ライセンス複雑性: 複数ライセンス体系の存在
  • パフォーマンスチューニング: 最適化に専門知識が必要

参考ページ

書き方の例

基本的なElasticsearch設定

# elasticsearch.yml
cluster.name: my-application
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300

# パス設定
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch

# メモリ設定
bootstrap.memory_lock: true

# ディスカバリー設定
discovery.seed_hosts: ["host1", "host2", "host3"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]

# セキュリティ設定
xpack.security.enabled: true
xpack.security.http.ssl:
  enabled: true
  certificate: elastic-certificates.p12
  key: elastic-certificates.p12

xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  certificate: elastic-certificates.p12
  key: elastic-certificates.p12

# 監視設定
xpack.monitoring.collection.enabled: true

# 機械学習設定
xpack.ml.enabled: true

# インデックス設定
action.destructive_requires_name: true
action.auto_create_index: .monitoring*,.watches,.triggered_watches,.watcher-history*,.ml*

# ネットワーク設定
http.compression: true
http.cors.enabled: true
http.cors.allow-origin: "*"

# パフォーマンス設定
indices.memory.index_buffer_size: 10%
indices.memory.min_index_buffer_size: 48mb
thread_pool.write.queue_size: 1000

インデックステンプレート設定

{
  "name": "logs-template",
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "index.refresh_interval": "10s",
      "index.max_result_window": 10000,
      "analysis": {
        "analyzer": {
          "custom_analyzer": {
            "type": "custom",
            "tokenizer": "standard",
            "filter": ["lowercase", "stop"]
          }
        }
      }
    },
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date",
          "format": "date_optional_time||epoch_millis"
        },
        "level": {
          "type": "keyword"
        },
        "message": {
          "type": "text",
          "analyzer": "custom_analyzer",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "service": {
          "type": "keyword"
        },
        "host": {
          "properties": {
            "name": {
              "type": "keyword"
            },
            "ip": {
              "type": "ip"
            }
          }
        },
        "response_time": {
          "type": "long"
        },
        "status_code": {
          "type": "integer"
        },
        "user_agent": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "geoip": {
          "properties": {
            "location": {
              "type": "geo_point"
            },
            "country_name": {
              "type": "keyword"
            },
            "city_name": {
              "type": "keyword"
            }
          }
        }
      }
    }
  },
  "priority": 500,
  "version": 1,
  "_meta": {
    "description": "Template for application logs"
  }
}

検索クエリの例

{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "now-24h",
              "lte": "now"
            }
          }
        },
        {
          "term": {
            "level": "ERROR"
          }
        }
      ],
      "filter": [
        {
          "terms": {
            "service": ["web", "api", "auth"]
          }
        }
      ],
      "should": [
        {
          "match": {
            "message": "exception"
          }
        },
        {
          "match": {
            "message": "error"
          }
        }
      ],
      "minimum_should_match": 1
    }
  },
  "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ],
  "size": 100,
  "highlight": {
    "fields": {
      "message": {
        "pre_tags": ["<mark>"],
        "post_tags": ["</mark>"],
        "fragment_size": 200
      }
    }
  },
  "_source": {
    "includes": ["@timestamp", "level", "message", "service", "host.name"],
    "excludes": ["user_agent"]
  }
}

アグリゲーションクエリの例

{
  "size": 0,
  "query": {
    "range": {
      "@timestamp": {
        "gte": "now-1d",
        "lte": "now"
      }
    }
  },
  "aggs": {
    "error_trends": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "1h",
        "time_zone": "Asia/Tokyo"
      },
      "aggs": {
        "error_count": {
          "filter": {
            "term": {
              "level": "ERROR"
            }
          }
        },
        "warning_count": {
          "filter": {
            "term": {
              "level": "WARNING"
            }
          }
        }
      }
    },
    "top_services": {
      "terms": {
        "field": "service",
        "size": 10
      },
      "aggs": {
        "avg_response_time": {
          "avg": {
            "field": "response_time"
          }
        },
        "error_rate": {
          "bucket_script": {
            "buckets_path": {
              "errors": "error_count",
              "total": "_count"
            },
            "script": "params.errors / params.total * 100"
          }
        },
        "error_count": {
          "filter": {
            "term": {
              "level": "ERROR"
            }
          }
        }
      }
    },
    "status_code_distribution": {
      "terms": {
        "field": "status_code",
        "size": 20
      }
    },
    "geographic_distribution": {
      "geo_distance": {
        "field": "geoip.location",
        "origin": "35.6762, 139.6503",
        "ranges": [
          {
            "to": 100,
            "key": "Within 100km"
          },
          {
            "from": 100,
            "to": 500,
            "key": "100-500km"
          },
          {
            "from": 500,
            "key": "Over 500km"
          }
        ]
      }
    }
  }
}

マッピング定義の例

{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          },
          "suggest": {
            "type": "completion"
          }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "custom_html_analyzer",
        "search_analyzer": "standard"
      },
      "tags": {
        "type": "keyword"
      },
      "published_date": {
        "type": "date",
        "format": "yyyy-MM-dd||yyyy-MM-dd'T'HH:mm:ss||epoch_millis"
      },
      "author": {
        "type": "nested",
        "properties": {
          "name": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword"
              }
            }
          },
          "email": {
            "type": "keyword"
          },
          "bio": {
            "type": "text"
          }
        }
      },
      "metadata": {
        "type": "object",
        "enabled": false
      },
      "view_count": {
        "type": "integer"
      },
      "rating": {
        "type": "float"
      },
      "location": {
        "type": "geo_point"
      },
      "ip_address": {
        "type": "ip"
      },
      "binary_data": {
        "type": "binary"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_html_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "char_filter": ["html_strip"],
          "filter": [
            "lowercase",
            "asciifolding",
            "stop"
          ]
        }
      }
    }
  }
}

インデックスライフサイクル管理(ILM)

{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_size": "10GB",
            "max_docs": 10000000,
            "max_age": "1d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "set_priority": {
            "priority": 50
          },
          "allocate": {
            "number_of_replicas": 0
          },
          "shrink": {
            "number_of_shards": 1
          },
          "forcemerge": {
            "max_num_segments": 1
          }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "set_priority": {
            "priority": 0
          },
          "allocate": {
            "number_of_replicas": 0,
            "require": {
              "box_type": "cold"
            }
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

Ingest Pipeline設定

{
  "description": "Log processing pipeline",
  "processors": [
    {
      "grok": {
        "field": "message",
        "patterns": [
          "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \\[%{DATA:thread}\\] %{DATA:logger} - %{GREEDYDATA:log_message}"
        ]
      }
    },
    {
      "date": {
        "field": "timestamp",
        "formats": ["ISO8601"]
      }
    },
    {
      "geoip": {
        "field": "client_ip",
        "target_field": "geoip",
        "database_file": "GeoLite2-City.mmdb"
      }
    },
    {
      "user_agent": {
        "field": "user_agent",
        "target_field": "user_agent_parsed"
      }
    },
    {
      "script": {
        "lang": "painless",
        "source": """
          if (ctx.status_code >= 400) {
            ctx.error_flag = true;
          } else {
            ctx.error_flag = false;
          }
        """
      }
    },
    {
      "remove": {
        "field": ["timestamp", "message"]
      }
    }
  ],
  "on_failure": [
    {
      "set": {
        "field": "error.message",
        "value": "{{ _ingest.on_failure_message }}"
      }
    }
  ]
}

Docker Compose設定

version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.16.0
    container_name: elasticsearch
    restart: unless-stopped
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
      - xpack.security.enabled=false
      - xpack.security.http.ssl.enabled=false
      - xpack.security.transport.ssl.enabled=false
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
      - ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
    ports:
      - "9200:9200"
      - "9300:9300"
    networks:
      - elastic

  kibana:
    image: docker.elastic.co/kibana/kibana:8.16.0
    container_name: kibana
    restart: unless-stopped
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - xpack.security.enabled=false
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch
    networks:
      - elastic

  logstash:
    image: docker.elastic.co/logstash/logstash:8.16.0
    container_name: logstash
    restart: unless-stopped
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
      - ./logstash.yml:/usr/share/logstash/config/logstash.yml
    ports:
      - "5044:5044"
      - "9600:9600"
    environment:
      - "LS_JAVA_OPTS=-Xmx1g -Xms1g"
    depends_on:
      - elasticsearch
    networks:
      - elastic

  filebeat:
    image: docker.elastic.co/beats/filebeat:8.16.0
    container_name: filebeat
    restart: unless-stopped
    user: root
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - elasticsearch
    networks:
      - elastic

volumes:
  elasticsearch-data:
    driver: local

networks:
  elastic:
    driver: bridge

パフォーマンスチューニング設定

# elasticsearch.yml
# クラスター設定
cluster.routing.allocation.disk.threshold.enabled: true
cluster.routing.allocation.disk.watermark.low: 85%
cluster.routing.allocation.disk.watermark.high: 90%
cluster.routing.allocation.disk.watermark.flood_stage: 95%

# インデックス設定
indices.memory.index_buffer_size: 10%
indices.memory.min_index_buffer_size: 48mb
indices.memory.max_index_buffer_size: unbounded

# 検索設定
search.max_buckets: 65536
search.allow_expensive_queries: false

# スレッドプール設定
thread_pool:
  write:
    queue_size: 1000
  search:
    queue_size: 1000
  get:
    queue_size: 1000

# HTTPクライアント設定
http.max_content_length: 100mb
http.max_chunk_size: 8kb
http.max_header_size: 8kb

# ディスカバリー設定
discovery.zen.minimum_master_nodes: 2
discovery.zen.ping.timeout: 3s
discovery.zen.fd.ping_timeout: 30s
discovery.zen.fd.ping_retries: 3

JavaクライアントAPIの例

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;

public class ElasticsearchExample {
    public static void main(String[] args) throws Exception {
        // クライアント作成
        RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
        
        ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
        
        ElasticsearchClient client = new ElasticsearchClient(transport);

        // ドキュメントのインデックス
        IndexRequest<Object> indexRequest = IndexRequest.of(i -> i
            .index("products")
            .id("1")
            .document(Map.of(
                "name", "Laptop",
                "price", 999.99,
                "description", "High performance laptop"
            ))
        );
        
        IndexResponse indexResponse = client.index(indexRequest);
        System.out.println("Document indexed: " + indexResponse.id());

        // 検索
        SearchRequest searchRequest = SearchRequest.of(s -> s
            .index("products")
            .query(q -> q
                .match(t -> t
                    .field("name")
                    .query("laptop")
                )
            )
        );

        SearchResponse<Object> searchResponse = client.search(searchRequest, Object.class);
        
        for (Hit<Object> hit : searchResponse.hits().hits()) {
            System.out.println("Found: " + hit.source());
        }

        // アグリゲーション
        SearchRequest aggRequest = SearchRequest.of(s -> s
            .index("products")
            .size(0)
            .aggregations("price_stats", a -> a
                .stats(st -> st.field("price"))
            )
        );

        SearchResponse<Object> aggResponse = client.search(aggRequest, Object.class);
        System.out.println("Price stats: " + aggResponse.aggregations());

        // リソースのクリーンアップ
        transport.close();
        restClient.close();
    }
}