気軽に楽しくプログラムと遊ぶ

自分が興味があってためになるかもって思う情報を提供しています。

Elastic Searchとは?

概要

  • 拡張性に優れた全文検索エンジン、複数DBの横断検索が可能、スキーマレスのNoSQL
  • 分析ツール:リアルタイムデータ分析、ログ解析などの分析ができる
  • 関連ツール:ログ集約のLogstashやfluentd、可視化ツールのkibanaと組み合わせて使うことが多い
  • REST APIJSONの送受信で操作する

Elastic Searchの構成要素

Cluster(クラスタ

  • 概要:1つ以上のノードの集合で、データ全体を包括する
  • 検索:インデックスおよびノードの横断的な検索機能を提供する。
  • 識別子:デフォルトではelasticsearch

Node(ノード)

  • 概要:クラスタを構成するサーバ
  • 識別子:デフォルトではノードの起動時に生成されるUUID
  • クラスタ接続:ノードは、クラス名指定でそのクラスタに接続可能。デフォルトはelasticsearchクラスタに接続

ノードの下にインデックスと呼ばれるRDBにおけるDBに値するものを保持する。
以下にRDBと比較しつつ、記載します。

RDBとの違い_概要

ElasticSearchは気軽にデータを保存、削除し、用途に応じたデータ保管形式で保存。
RDBは基本的にデータを永続保管。

  • RDB
    • データ保管:永続的に保管する
    • DB:基本削除しない
    • パフォーマンス:ElasticsearchやNoSQLDBを超えられない。結合処理などがあるため。
  • Elasticsearch
    • データ保管:永続保管ではない。DB(インデックス)を時間間隔やユーザーで区切ったりする、分析や検索用のデータを保管する
    • DB(インデックス):不要になったら捨てる
    • パフォーマンス:拡張性に優れており、パフォーマンスが出しやすい
RDB Elasticsearch
データベース インデックス
テーブル マッピングタイプ
カラム(列) フィールド
レコード(行) ドキュメント

テキスト処理

データ格納時

ElasticsearchではRDBと異なり、テキストデータに関して、挿入データがそのまま格納される訳ではない。
Analyzerという機能により、文字整形や文字分割が行われて格納されている。

  • テキストデータ:textタイプのフィールド ※V5より
  • Analyzer:Tokenizer、Token Filter、Character Filterという文字列分割や変換を行う機能
  • 元データ:stored field(store=trueなフィールド)、または _sourceという特殊フィールドに格納される
  • 特殊フィールド _source:全フィールド(ドキュメント)を保持。設定でデータ圧縮も可能、検索条件不使用フィールドはdisableでインデックスサイズを節約

データ格納例

例1 受信データ:AAA → Analyzer(変換) → aaa
例2 受信データ:「This is a pen」 → Analyzer(分割) ↓

this
is
a
pen

※空白区切り文字で分割、検索時不要文字のピリオドを除去。

データ参照時

検索条件文字列を用いて、検索する際もAnalyzerによって文字列変換や分割文字列の位置情報の利用などが行われます。

データ参照例

例1 クエリ:"term": {"field": "pen"} → 検索ヒット (フィールドには基本小文字で格納されている) 例2 クエリ:"term": {"field": "PEN"} → 検索ヒットせず (termクエリはAnalyzerを用いないため大文字指定ではヒットしない) 例3 クエリ:"match": {"field": "PEN a"} → 検索ヒットせず (matchクエリはAnalyzerを用いるため大文字指定でもヒット。penとaの両方を含む文書がヒット)

マッピングタイプ(テーブル)の特徴

  • マッピングタイプ単位で削除やカラム定義はできないので、RDBのテーブルと同じものと考えてはいけない
  • マッピングタイプ毎に別々のスキーマを保持可能
  • マッピングタイプ全体で、同名のフィールドは同一定義を保持する必要あり

マッピングタイプの定義

フィールド

  • 型:暗黙的に配列型、JSONオブジェクトもほぼそのまま入れられる
  • 値:同じ型の複数の値を保持可能、入力データをトークン毎に切り分けて格納するため、multi-valuedな挙動になっている
  • ソート:配列内の平均値、最大値、中央値といった値を指定してソート条件とする
{
  "f1" : { "p1": 1, "p2": "ABC" } 
}

↓ 変換して扱われる

{
  "f1.p1" : 1, 
  "f1.p2": "ABC" } 
}

フィールドにオブジェクト配列を入れることも可能。その場合は、「nested」というデータタイプを指定する。

データタイプ

Elasticsearch公式

  • 日時の型:DATE, TIME, TIMESTAMPなどはdateにまとまっている
  • 文字列長:CHARとVARCHARのような固定長・可変長の区別なし、最大長は通常指定しない

文字コードUnicodeのみ。異なる文字コードの場合、クライアント側での変更が必要

制約(Constraint)

  • 制約の機能なし。ユニークキーや主キーでレコード(ドキュメント)の一意性を強制はできない
  • ドキュメントの一意性:id、uidで一意性を担保
    • _id:マッピングタイプ内で一意な値。ドキュメントIDを保持するフィールド
    • uid:インデックス内で一意な値。マッピングタイプ名とidの値を区切り文字#で連結したもの

※ドキュメントID:indexAPIの場合はURLの一部、bulkAPIは_idフィールドを指定、未指定の場合は、適当な文字列が設定される

デフォルト値

  • null_valueオプション:nullを明示的に指定した場合のみ有効となり、デフォルト値に近いことができる
  • その他機能でのデフォルト値の機能
    • avg, sumなどの集合関数:metric aggregation
    • ソート: missingオプション

マッピングタイプ変更

  • 変更は基本できない。ALTER TABLE(テーブル変更のクエリ)の代替機能なし
  • 変更時は、インデックス(DB)の再作成、データ再投入が必要

マッピングタイプ削除

  • 削除できない
  • マッピングタイプ内のドキュメントすべて削除するということはできる
POST /index1/_delete_by_query // index名と削除クエリを指定
{
  "query": {
    "type": {
      "value": "type1"  // マッピングタイプを指定
    }
  }
}

elastic(ELK)

ELスタックと呼ばれるセットのサービス Elastic Searchの環境を立ち上げたときにサイドバーに表示されるメニューと使い方は以下

  • Dev tool コンソールでクエリ叩ける

  • Discover 登録データを見れる

参考になる記事

S2Daoのバッチ更新で手動作成SQLファイルを実行できるか調べてみた

ループ内更新による性能劣化を改善するために、S2Daoのバッチ更新で IO回数を減らしたかったので調査しました。

今回は、手動作成SQLファイルの実行を前提としています。

S2Daoのバッチ更新が使える条件

そもそも、手動作成SQLファイルの実行では、バッチ更新できませんでした。。

いろいろ制約があり、S2Daoを使う限り、ループ内SQL実行は避けられないみたいです。

参考

S2Dao_一括更新 http://s2dao.seasar.org/ja/s2dao.html#Batch

Domaなら、S2Daoで出来なかったことができる

@BatchUpdate の exclude 要素に指定されたプロパティを更新対象外が可能。 https://doma.readthedocs.io/en/2.11.0/query/batch-update/#sql

@BatchUpdateのsqlFileにtrueを設定することで、任意のSQLファイルにマッピングできます http://doma.seasar.org/reference/query/batch_update.html

Javaメモリ設定方法と、確認用コマンド

メモリ設定方法

以下の設定を元に考えてみます。

-XX:MaxMetaspaceSize=128m -Xms256m -Xmx256m -Xss1m -XX:NewSize=100m -XX:MaxNewSize=100m

|オプション|説明|設定値目安| |:-|:-|:-|

|MetaSpace|libの読み込み、JSPコンパイル結果つむ|64~256程度| |Xms|ヒープ領域の最小サイズ(初期サイズ)|XmsとXmxは同じ数値を指定| |Xmx|ヒープ領域の最大サイズ|XmsとXmxは同じ数値を指定| |Xss|Java APIで生成するスレッドのスタックサイズ| |NewSize|ヒープ領域内のNew領域のサイズ指定。XmsのNew領域版| |MaxNewSize|ヒープ領域内のNew領域のサイズ指定。XmxのNew領域版|

Xms Xmx

物凄い大きなMapやListを保持する可能性が無い限りは同じ数値を指定するのが望ましい。

Xmxのサイズを大きい状態だと、FGC発生時に最大でXmxのサイズまで拡張されていく。
メモリの拡張確保分だけ、FGCの停止時間が長くなってしまうため、同じサイズにするのが良いとされている。

Old領域

New領域に指定されなかった残りのヒープ領域はOld領域に割かれます。(Xms - NewSize = Old領域)
New領域は最初にインスタンスが格納される領域で、一杯になるとYGCとかマイナーGCなどのGCが走る。
何回かのYGCを生き残ったインスタンスがOldに移動される。

NewとOldは1:2が基本で、Oldが大きい方が安全です。ほぼOld領域に行かないと分かっている場合は、
1:1運用がFGCが発生し難いので効率がよい。

メモリ設定等を確認するうえで良く使うコマンド

JVMのオプションをjps -vで確認し、
jstat で1秒毎のメモリ状況確認する。

jps -v

[user@hostname bin]$ jps -v
11111 Jps -Dapplication.home=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.151-1.b12.35.amzn1.x86_64 -Xms8m
22222 Bootstrap -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=64m -Xms80m -Xmx80m -Xss1m -XX:NewSize=40m -XX:MaxNewSize=40m -XX:SurvivorRatio=12 -XX:TargetSurvivorRatio=95 -XX:MaxMetaspaceSize=32m -Dfile.encoding=UTF-8

jps -vはその環境で動作しているJVMの一覧(オプション付き)とプロセスIDを表示可能。
tomcatが動作しているのかの確認にも使用可能。

jstat -gcutil -h{ヘッダ間隔] [プロセスID] [実行間隔]
jstat -gccapacity  [プロセスID] [実行間隔]

[user@hostname bin]$ jstat -gcutil -h20 23436 1000
 S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
 0.00  16.46   8.74  45.33  97.03  93.16    865    2.958     1    0.036    2.994
 0.00  16.46  11.03  45.33  97.03  93.16    865    2.958     1    0.036    2.994
 0.00  16.46  12.72  45.33  97.03  93.16    865    2.958     1    0.036    2.994

Oracle 実行計画、統計情報の概要

SQLが実行されるまでの流れ

まずは、SQL実行の全体像について確認する。

f:id:tamata78:20210128173916p:plain

  • サーバープロセスがSQLを受け取る
  • SQLチェック:構文チェック、指定テーブルや列の確認、アクセス権の確認
  • 実行計画の確認:共有プール内に該当SQLの実行計画があるかどうか。あればそのまま検索。なければ、オプティマイザの処理へ
  • オプティマイザで評価:統計情報を参照しつつ、実行計画をいくつか作成、比較して最適な実行計画を選択
  • 選択した実行計画を共有プールへ格納する
  • 実行計画を元にSQLを実行する

画像引用:https://qiita.com/zd6ir7/items/abbb1e92701992d32b88

実行計画

SQLを実行するための計画。
フルスキャンする、インデックスを使うなどの無数の選択肢から最短ルートの実行方法を計画したもの
統計情報を元に作成されるため、統計情報が適切な情報になっていることが重要

統計情報

  • 構成: 表統計、列統計、索引統計、システム統計
  • 取得タイミング:自動、手動、SQL実行時

ECサイト経営で使う用語

ECサイトで使われるKPIにおけるよく使われる用語を書いておきます。

GMV(グロスマーチャンダイズ・ボリューム)

  • 概要:流通取引総額。購入される商品やサービスの販売総額を表す
  • 補足:企業の収益総額ではない。手数料ビジネスの企業であれば、商品取引時の手数料や広告料などGMVの一部が収益となる
  • 用途:市場規模の測定など

ARPU(Average Revenue Per User)

  • ユーザー1人当たりの売上金額を表す指標

logrotateでログをローテ―トする

アプリのlog4jなどでしかログローテートできないと思っていましたが、 linuxのコマンドでlogrotateなるものがあることを知ったので、まとめてみます。

環境

Red Hat 4.4.7-4

設定ファイル構成

├─ logrotate.conf # メインの設定ファイル
├─ logrotate.d # 各サービスごとの設定ファイル
│   ├─ dracut
│   ├─ httpd
│   ├─ tomcat
│   ├─ mysql
│   ├─ syslog
│   ├─ yum

logrotate.d以下に、各サービス毎の設定ファイルを配置するのが一般的

設定例

/var/log/app/*.log {
   copytruncate #ログファイルをコピーし、内容を削除
   rotate 7 #7世代分古いログを残す
   start 1 
   daily # 日次ローテーション
   nomail 
   compress # ローテーションログをgzipで圧縮
   missingok # ログファイルなしでもエラーを出さずに処理続行
   postrotate # ログローテーション後に以下を実行
       /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true # syslog再起動
   endscript
}

ローテートの動作確認

# テスト実行して結果を参照
$ logrotate -dv /etc/logrotate.conf

# 実際の実行
$ /usr/sbin/logrotate /etc/logrotate.conf

# 最終ローテ―ト日時の確認
$ cat /var/lib/logrotate.status
logrotate state -- version 2
"/var/log/yum.log" 2015-1-1
"/var/log/httpd/80-access_log" 2021-1-19
"/var/log/httpd/80-error_log" 2021-1-1

【関連情報】syslog

  • 概要:linux上の動きをログ出力する仕組み。syslogデーモンでログが記録される
  • パス:/var/log
  • デフォルトログ:cron、 messages、maillog、secureなど。
  • 設定ファイル:/etc/syslog.conf

Logbackのログローテート設定

Spring Boot内のLogbackを用いて、ログローテートを行う際の設定について以下にまとめます。

ログローテする場合のLogback設定

以下前提で設定しました。

  • ローテ―トタイミング:日別
  • 最大保存ファイル数:3
  • 保存形式:gz (ローテ―ト時に圧縮)
<configuration>

    <springProfile name="develop">
        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
        <property name="logDir" value="/work/logs/batch/" />
        <property name="fileName" value="TestBatch.log" />
        <property name="format" value="%d{yyyy/MM/dd HH:mm:ss.SSS} [%t] %-5le %lo{36} - %msg %n" />

        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>${logDir}${fileName}</file>
          <encoder>
            <pattern>${format}</pattern>
          </encoder>
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}${fileName}.%d{yyyy-MM-dd}.gz</fileNamePattern> <!-- 拡張子をgz、zipなどにすると自動で圧縮される-->
            <maxHistory>3</maxHistory> <!-- 最大保存ファイル数 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart> <!-- 最大ファイル数超過時にファイルを削除するか否かの設定 -->
          </rollingPolicy>
        </appender>

        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>${format}</pattern>
            </encoder>
        </appender>

        <logger name="jp.co.test.batch" level="debug"></logger>

        <root level="debug">
            <appender-ref ref="FILE" />
            <appender-ref ref="STDOUT" />
        </root>
    </springProfile>

%d{}変換指示子内の日時パターン文字列

java.text.SimpleDateFormatの規約に従う。
https://docs.oracle.com/javase/jp/7/api/java/text/SimpleDateFormat.html
→日付/時刻パターン

ログローテ単位毎の書き方

logFile.%d{yyyy-MM-dd}.log
%d{}のフォーマットを書き換えることで、ローテーションタイミングが決まる。

日次:logFile.%d.log  → 2006-11-24
月次ローテ:logFile.%d{yyyy-MM}.log → 2006-11 ※ "%d{yyyy/MM}/logFile.log"とすると、月次ディレクトリが作成される
週次ローテ:logFile.%d{yyyy-ww}.log
毎時ローテ:logFile.%d{yyyy-MM-dd_HH}.log
毎分ローテ:logFile.%d{yyyy-MM-dd_HH-mm}.log

ファイルサイズでローテ―トしたい場合

triggeringPolicyをappender RollingFileAppender以下に追加する。

<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
  <!-- ファイルサイズによってローテートする -->
  <maxFileSize>20MB</maxFileSize>
</triggeringPolicy>

おまけ Windowsでシステム日付を変更する

Windowsで日付を変更してローテ―トのテストをしたい場合は、以下のコマンドを利用して見てください。 コマンドプロンプトを管理者で開いて、dateコマンドで日付変更

f:id:tamata78:20210118181322p:plain

参考URL