ブログサイトのレコメンド備忘録

ブログサイトのレコメンドについて考える機会があったのでそのメモ。頭にあることをダンプしたかっただけなので、実用性あるのかとか、どう実装するのかとかはあまり考えていない。

概要

自社で運用するブログサイトの直帰率が高いことが気になった。つまり、オーガニック検索(Google検索とか、Yahoo!検索とか)からサイトのある記事に流入してくる。記事を読んでいただけた後に他の記事は読まれずに元のページに戻ったり、ページを閉じられたりしている。どうせならもっと他の記事も読んでもらってサイトのファンになってほしい。そして有用な情報があることを理解いただけれえば、サイトをブックマークしていただけたり、RSS フィードを購読していただけたりすればもっとファンになってくれるのではないかと思う。その取っ掛かりとして他の記事をレコメンドして直帰率を下げ、回遊率を上げたい。

パターン1:最新記事のレコメンド

概要

一番簡単な実装。記事の終わりとか、サイドバーとかに当サイトのいくつかの最新記事へのリンクを表示する。最新記事は読者が欲しい最新の情報がある可能性が高く、読者は気になってクリックしてくれる可能性がある。

実装方法

データストアから執筆日時、更新日時などでソートして Top ◯件の記事データを取得する。

デメリット

  • 最新記事が読者が興味のある記事とは限らない。興味のない記事をレコメンドしても意味がない。
  • 古い記事をレコメンドできない。

パターン2:カテゴリベースの最新記事のレコメンド

概要

興味のない記事をレコメンドすることを対応した実装。同一カテゴリの最新記事へのリンクを表示する。

実装方法

データストアからカテゴリを表すフィールドを閲覧している記事と同一のカテゴリでフィルタして記事データを取得する。

デメリット

  • カテゴリとなるデータのメンテナンスが重要
    • 執筆者がカテゴリを付与することを意識する
    • カテゴリの粒度によって本来読者に読ませたい記事を読ませれない
  • 古い記事をレコメンドできないことは解決されない

パターン3:類似記事のレコメンド

概要

古い記事をレコメンドできないことを対応し、カテゴリデータのメンテナンスが不要な実装。記事レベルで他記事と類似度を算出し、類似度が高い記事を表示する。

実装方法

Elasticsearch の more like this クエリなどを利用する。

デメリット

  • 新しいデータソースが出てきた
  • 閲覧している記事から別のカテゴリに広がらない

パターン4:読者の閲覧履歴に基づく類似記事のレコメンド

概要

閲覧している記事だけでなく、読者がサイトで閲覧した履歴から総合的に類似記事を表示する。

実装方法

読者に Cookie を払い出してトラッキングする。アクセスログなどに Cookie を出力して読者が他にどの記事を読んだのか情報として残す。過去に同一読者が読んだ記事全体からパターン3同様に類似記事を取得する。他にも同サイトでの検索履歴から全文検索のスコアリングで取得するのも良さそう。

デメリット

  • 読んだ記事が全て同列で扱われる
    • 興味ある記事、興味ない記事がある気がする

パターン5:読者の検索履歴に基づく類似記事のレコメンド

概要

閲覧した記事が必ずしも興味のあることとは限らない。検索したキーワードは興味のあることである可能性が高い。読者がサイトで検索した履歴から関連記事を表示する。

実装方法

読者に Cookie を払い出してトラッキングする。アクセスログなどに Cookie を出力して読者が他にどの記事を読んだのか情報として残す。過去に同一読者が読んだ記事全体からパターン3同様に類似記事を取得する。他にも同サイトでの検索履歴から全文検索のスコアリングで取得するのも良さそう。

デメリット

  • サイト内で検索したことがある読者に限られる

パターン6:読者と類似する読者の閲覧履歴に基づくレコメンド

概要

Amazon などでもある「よく一緒に購入されている商品」。読者の閲覧履歴と似ている履歴を持つ読者を探し、類似読者が読んでいて、対象読者が読んでいない記事を表示する。

実装方法

上記と同様である程度できそう

デメリット

  • 実装のハードルが高そう

The Definitive Guide から学ぶ Elasticsearch(You Know, for Search…)

前回の続き。このペースだと終わらないのでペースアップ。

sfujimoto.hatenablog.com

You Know, for Search…

ここでは Elasticsearch の概要を学びます。Elasticsearch は Apache Lucene を内部で利用した検索エンジンです。Apache LuceneJava 実装の OSS 検索ライブラリです。Elasticsearch だけでなく、Apache Solr でも利用されています。

Apache Lucene 自体でも以下のような機能を持ちます。

しかし、Apache Lucene 自体では以下のような機能は持ちません。

そこで Elasticsearch で Apache Lucene をラップすることで以下を可能としています。

  • RESTful API による操作
    • コマンドベースの操作(Linux であれば、curlコマンドなどで簡単操作 )
    • 各種言語の SDK が提供されていて、Java 言語に限らずその他言語でのデータ操作
  • クラスタリングによるデータのレプリケーションや水平スケールの拡張性

Installing and Running Elasticsearch

ここでは Elasticsearch のインストール・起動について学びます。Elasticsearch の動作要件は最新バージョンの Java がインストールされていることだけです。Java さえインストールされていれば、LinuxWindowsMac どこでも動作します。インストールも非常に簡単です。OS に応じたパッケージをダウンロードし、起動プログラムを実行するだけでインストールが完了します。

Sense という Elasticsearch の操作を簡単に行うことができる Kibana のプラグインのインストール方法が合わせて紹介されていますが、Kibana 5.0.0 以降から Console という名称で標準搭載しています。これから Elasticsearch を触られる方は気にしなくていいです。ただ Console は非常に便利なのでコマンドベースで操作している方は Console を利用されることをオススメします。

ここでは Elasticsearch の操作について学びます。最初に Java Client について紹介されていますが、今は HTTP ベースのクライアントがリリースされていますので紹介されているのは気にしなくていいです。Transport Client に関しても今後は新機能は実装されなくなるのでこれから使い始める方は Java REST Client を覚えましょう。

ここからが本題です。Elasticsearch は RESTful API で操作します。Elasticsearch がデフォルト設定で LISTEN するポートは tpc/9200 です。操作内容は HTTPメソッド、URLパス、JSON 形式の HTTP リクエストボディで決まります。いくつかcurlコマンドベースの例です。

index という名前のインデックス作成
$ curl -XPUT http://localhost:9200/index
index という名前のインデックスの設定内容取得
$ curl -XGET http://localhost:9200/index
インデックス「index」のタイプ「type」に ID「1」でフィールド「name」の値が「elasticsearch」のデータを登録
$ curl -XPUT http://localhost:9200/index/type/1 -d '{
  "name": "elasticsearch"
}'
インデックス「index」を検索
$ curl -XGET http://localhost:9200/index/_search -d '{
  "query": {
    "match": {
      "name": "elasticsearch"
    }
  }
}'

Document Oriented

ここでは Elasticsearch が扱うデータについて学びます。Elasticsearch はドキュメントベース(JSON 形式)でデータを操作、管理します。ドキュメントベースは非常に扱い易いです。RDBMS ではシステム上データを扱う際にそのまま扱うことはほとんどできず、データを正規化して、分割して扱う必要がありました。Elasticsearch では配列やオブジェクトをそのまま扱うことができます。また列指向でデータをインデキシングするため、高パフォーマンスでの検索、ソート、フィルタリングを実現できます。

Finding Your Feet

ここから何節か跨いでサンプルデータを扱うことで Elasticsearch を学んでいきます。サンプルデータは従業員のデータです。HR では従業員のデータを様々な用途で検索します。

Indexing Employee Documents

ここでは Elasticsearch へのデータの登録について学びます。まずは Elasticsearch にサンプルデータを格納します。Elasticsearch にデータを格納することをインデキシングといいます。インデキシングの例は上記でサンプルのコマンドで紹介した通りです。インデックス、タイプ、ID、フィールドなどは下記をご参照ください。

Elasticsearch は 3種類のインデックスという単語が登場します。

  1. 論理的なデータ格納先となるインデックス Elasticsearch の論理構成は RDBMS の論理構成と対比すると理解が早まります。
RDBMSの用語 Elasticsearch
データベース インデックス
テーブル タイプ
カラム フィールド
主キー ID
  1. データ登録のインデックス これは RDBMS の INSERT のようなものです。どちらかというとインデキシングと言われることが多いです。

  2. 転置インデックス 全文検索の手法の一つです。転置インデックス全文検索と同じポジションなのが Grep全文検索(順次走査検索)です。

Retrieving a Document

ここでは Elasticsearch からのデータの取得について学びます。登録したデータを取得します。Elasticsearch は検索はもちろんですが、インデックス名、タイプ名、ID を指定することで一つのデータを取得することもできます。

Search Lite

ここでは Elasticsearch からのデータの検索について学びます。_search API により検索できます。

  • 何も指定しない場合、全件検索します。
  • クエリストリングにqを検索キーワードを指定することで、簡易な条件の検索ができます。
  • HTTP リクエストボディに JSON 形式の Query DSL を指定することで、複雑な検索、集計などを行うことができます。
    • ほとんどはこの方法で検索します。

Search with Query DSL

ここでは Query DSL を利用したデータの検索について学びます。Talking to Elasticsearch で記載したように JSON 形式で Query DSL を記述します。例えば下記のような検索 API

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}

インデックス「megacorp」、タイプ「employee」にあるデータからフィールド「last_name」が「Smith」のデータを検索します。

More-Complicated Searches

ここでは Query DSL についてもう少し学びます。先ほどは単純に名前の文字列を検索しました。Elasticsearch は文字列だけではありません。数値も扱うことができます。

GET /megacorp/employee/_search
{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}

先ほどの条件に加え、age(年齢)が 30以上のデータを取得しています。JSON が複雑になりましたが、一旦ここでは数値のフィルタリングもできるということを覚えてください。もちろん数値だけではなく、日付や時刻、珍しいところでは位置情報、IPアドレスなども扱うことができます。

Full-Text Search

ここでは転置インデックス全文検索について学びます。RDBMS では物足りなくなり、Elasticsearch を利用する理由の一つになります。RDBMS でも全文検索できます。RDBMS ではLIKE句を利用することで順次走査型全文検索できます。しかし、順次操作型全文検索ではLIKE句ではインデックスを張れないため、データ量が多くなるにつれて全文検索のパフォーマンスが劣化が早いです。また含まれるか、含まれないかの Yes/No しか判断できないため、検索にヒットしたデータがどのくらい検索者の希望するものに近いのか判断することができません。転置インデックス全文検索を利用することで全文検索のインデックス化によるパフォーマンス向上、ランキングによる検索データのスコアリングを行うことができます。詳細は別の章で説明することになるかと思いますのでここでは Elasticsearch だとこんなことができるのかと覚える程度で大丈夫です。

RDBMS でも転置インデックス全文検索できるものもあります)

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}

about フィールドに rock climbing というキーワードで検索します。

{
   ...
   "hits": {
      "total":      2,
      "max_score":  0.16273327,
      "hits": [
         {
            ...
            "_score":         0.16273327, 
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            ...
            "_score":         0.016878016, 
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}

Elasticsearch の match クエリはデフォルト「OR検索」のため、「rock」、「climbing」のいずれかが含むデータを返しますが、両方を含むデータの方がスコアリングにより「_score」の値が大きくなっています。

Phrase Search

ここではフレーズ検索について学びます。先ほどロッククライミングで検索したら、ロッククライミングと音楽のロックが検索ヒットしました。ただ音楽のロックは期待したことではありません。ここではノイズにしかなりません。rock climbing を連語と識別して検索方法にフレーズ検索があります。フレーズ検索は Query DSL で指定可能です。

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}

matchmatch_phraseになっただけですね。簡単です。

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         }
      ]
   }
}

今度は音楽のロックは返ってきませんでした

Highlighting Our Searches

ここでは Elasticsearch のハイライト機能について学びます。例えば、Google 検索であるキーワードで検索した時、大体全て見きれない多くの結果が返ってきます。あなたはその多くある結果から該当するページを探す時、どう探していますか?上から順に見る方もいるかもしれません、タイトルで判断する方もいるかもしれません。タイトルの下の3,4行の説明文を見ていませんか?説明文には検索キーワードを含む文が表示されています。文脈を読むことができ、検索キーワードから期待する結果に近いのかどうか大きな判断材料となります。Elasticsearch ではハイライト機能という標準機能でそのような結果を返すことができます。また検索キーワードは特定の HTML タグを付与することで強調表示することも可能です。

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}

先ほどのmatch_phraseクエリにハイライトの Query DSL を付与するだけです。

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            },
            "highlight": {
               "about": [
                  "I love to go <em>rock</em> <em>climbing</em>" 
               ]
            }
         }
      ]
   }
}

文が短いのであまりありがたみが分からない結果となりましたが、rock と climbing にタグが付与されました。

Analytics

ここでは集計機能(Aggregation)について学びます。RDBMSSQL では GROUP BY 句により、データ件数や数値データの合算などを集計することができます。Elasticsearch でも集計できます。Query DSL に Aggregation という機能があります。

GET /megacorp/employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": { "field": "interests.keyword" }
    }
  }
}

aggsは Aggregation 機能の宣言です。all_interestsは変数名、メソッド名みたいなものです。termsは Terms Aggregation(Term の集計)を利用する宣言です。fieldは Terms Aggregation の必須オプションで集計対象とするフィールドを指定します。今回はinterests.keywordフィールドを指定しています。

{
   ...
   "hits": { ... },
   "aggregations": {
      "all_interests": {
         ...
         "buckets": [
            {
               "key": "music",
               "doc_count": 2
            },
            {
               "key": "forestry",
               "doc_count": 1
            },
            {
               "key": "sports",
               "doc_count": 1
            }
         ]
      }
   }
}

フィールドinterests.keywordからキーワード毎のデータ件数を集計します。musicを含むデータが 2件、forestrysportsを含むデータが 1件づつあります。

検索結果を集計することもできます。

GET /megacorp/employee/_search
{
  "query": {
    "match": {
      "last_name": "smith"
    }
  },
  "aggs": {
    "all_interests": {
      "terms": {
        "field": "interests.keyword"
      }
    }
  }
}

集計結果を更に集計することもできます。

GET /megacorp/employee/_search
{
    "aggs" : {
        "all_interests" : {
            "terms" : {
                "field" : "interests.keyword"
            },
            "aggs" : {
                "avg_age" : {
                    "avg" : { "field" : "age" }
                }
            }
        }
    }
}

これは興味があること毎の従業員の平均年齢を集計しています。

Tutorial Conclusion

ここではサンプルデータを使った説明のまとめです。ここまでで紹介したことは Elasticsearch の機能のほんの一部です。他にも様々な検索、集計ができますが、ここまでで学んだ Query DSL が変わるだけで複雑にはなりません。

Distributed Nature

ここでは Elasticsearch の分散性について学びます。チュートリアルのデータ量では意識する必要ありませんでしたが、Elasticsearch は数百(または数千)のサーバーにスケールアウトし、ペタバイトのデータを処理できます。Elasticsearch は簡単に複数ノードによるクラスタを組むことができます。

The Definitive Guide から学ぶ Elasticsearch(Getting Started)

久しぶりのブログエントリです。年末年始休暇の時間を使って改めて Elasticsearch を学び直したいと思います。

概要

Elastic Stack はドキュメントが豊富です。Elastic Stack の各種プロダクトにドキュメントがあることはもちろん、プラグインやクライアントにもドキュメントが用意されていて更にうれしいのは各種バージョンでのドキュメントも残されています。つまり最新バージョンを使っていなくても使っているバージョンのドキュメントに切り替えて閲覧することができます。それらドキュメントの一つに The Definitive Guide というドキュメントがあります。過去に一回通しで読んだことがあるのですが、いまいち理解できずに読み進めたところがありますので改めてこのドキュメントを読み進めながら Elasticsearch の理解を深めたいと思います。このドキュメントはバージョン 2系で書かれていますが、Elasticsearch の基礎となる部分は変わっていないと思うので個人的には入門ドキュメントにピッタリだと思っています。ちなみに個人の見解も混じっているので間違っているところあったら是非ご指摘ください。

Getting Started

ここでは Elasticsearch の概要が説明されています。Elasticsearch は全文検索、Structured検索(そのまま構造化検索でいいのかな)、分析などの用途に利用できます。例えば、転置インデックスを扱う全文検索を行うことができ、JSON 形式のスキーマフリーにデータを扱うことができ、柔軟な検索、集計でデータの分析、予測などを行うことができます。

ユースケース

ここではいくつかの有名サービス、企業での Elasticsearch のユースケースを紹介されています。

  • Wikipedia
    あの大量の文書を Elasticsearch でストアしています。検索だけでなく、検索窓のサジェストや、検索結果のハイライトも Elasticsearch によって実現しています。

  • The Guardian
    イギリスの新聞社の The Guardian は読者の行動分析に Elasticsearch を利用しています。ログと SNS のデータから編集者に読者の反応をフィードバックしています。

  • Stack Overflow
    全文検索と位置情報と類似検索を使ってより関連する質問、回答の検索を実現しました。

  • GitHub
    GitHub にある 1,300億行のソースコードを検索します。(2系のドキュメントなので今は一体何行あるのか。。)

ここでは大企業、有名サービスの例として上記を上げていますが、スタートアップなどでの採用も多くなっています。

データベースだとダメ?

多くのシステムでは RDBMS にデータを蓄えています。RDBMS で蓄えているだけでデータを活用していないケースはよくあります。RDBMS には SQL がありますが、Elasticsearch と比較するとデータの集計、分析が得意ではありません。Elasticsearch にもデータを置くことで今まで難しかったデータ分析を行っていきましょう。

【sshec2】Github によるソースコード管理

前回のエントリで私が開発した OSS ツールの sshec2 をご紹介しました。

sfujimoto.hatenablog.com

今回から何回かに分けて、OSS ツールを開発する上で考慮した点を備忘録で記載していきます。

まずはソースコード管理。ソースコード管理(ソースコードリポジトリ)には Github を利用しました。

ソースコード管理

一人で開発する、誰とも共有する必要がない、一回作って終わりなプログラムであれば、ソースコード管理は必要ないでしょう。ただし逆に複数人で開発する必要がある、誰かと共有したい、継続的にプログラムを運用したい、といった用途のプログラムはソースコードリポジトリを利用すると非常に便利です。

  • 複数人で開発する
    例えば、一つのプログラムファイルを複数人で同時並行に修正していて、それをファイルサーバーで管理していた場合、何が起きるでしょうか?1人目が修正を終えてファイルサーバーに上書きアップロードしました。2人目も修正を終えてファイルサーバーに上書きアップロードしました。1人目の修正が上書きされています。もちろん、こういう環境では気をつけて、アップロードする前に Diff なりして、差分を確認することが一般的でしょうが、差分を忘れない保証はありません。

  • 誰かと共有したい
    今回は OSS ツールを開発しました。どうせなら多くの人に使っていただき、フィードバックを受け、より良いものにしたいと思っています。一般公開可能なソースコードリポジトリを利用することでプログラムを公開し、色んな人に見ていただくことが可能です。場合によってはより良いものに修正したプログラムを提案いただけるかもしれません。

  • 継続的にプログラムを運用したい
    プログラムを継続的に運用するのは簡単ではありません。バグが見つかれば改修する必要があります。新しい機能を追加したければ新しい機能を実装する必要があります。

ソースコード管理手法

ソースコード管理で良く話に上がるのは Git です。他にも SubversionMercurial という手法もありましたが、今は Git がデファクトスタンダードで利用できる人が多いと思うので特に比較せずに Git によるソースコード管理を選択しました。Git を利用することで上記を解決することができます。

  • 複数人で開発
    Git を利用すると、自身の更新内容を自身で自動で管理してくれます。それにより、ソースコードリポジトリにアップロードする時に自身の更新内容と、他の人が更新していないか、更新があった場合は内容を自動で比較し、バッティングしていること、どうバッティングしているのかを表示します。

  • 誰かと共有したい
    Git リポジトリSaaS があり、それらで公開することができます。

  • 継続的にプログラムを運用したい
    プログラムを運用する上で多くのツール、SaaS と連携することで圧倒的に楽にすることができます。Git リポジトリは多くの選択肢があり、ツールとの連携、他サービスとの連携も選択するポイントの一つとなります。

Git リポジトリのセキュリティレベル

次に Git を利用する上で Git のリポジトリのセキュリティレベルをどうするかです。Git リポジトリを利用する方法は多く存在します。Git リポジトリの比較の前に管理するプログラム・ソースコードのセキュリティレベルを考える必要があります。大きく分けて、3段階あるかと思います。アクセス制御のレベルが低い順に 3つ書きます。あくまでアクセス制御の話で Git リポジトリを運営する会社がどうのこうの言うつもりはありません。また閲覧権限と変更権限は別の問題ですので今回のスコープにはしません。命名はオレオレで分かりやすい名前を付けただけですのであしからず。

パブリックリポジトリ

ソースコードは誰でも閲覧できます。

プライベートリポジトリ

ソースコードは登録したユーザーだけが閲覧できます。

プライベートサーバー

決まったユーザーだけサーバにアクセスでき、閲覧できます。

パブリックリポジトリと、プライベートリポジトリ/サーバーの違いは分かりやすいかと思います。認証が必要化かどうかです。
次にプライベートリポジトリとプライベートサーバーの違いを簡単に説明します。どちらも認証されたユーザーのみがアクセスできます。プライベートリポジトリは認証情報(例えば、ユーザー名とパスワード)のみで閲覧可否を判断します。一方、プライベートサーバーは認証情報に加えて、ネットワークの疎通性が追加されます。例えば社内ネットワークからのみアクセスできる、VPN を通してのみアクセスできるなど。大きな違いは認証情報が漏れた時の影響です。プライベートリポジトリは認証情報が漏れた時に悪用されます。しかし、プライベートサーバーは認証情報が漏れてもそもそもリポジトリを持つサーバーにアクセスできないので影響はありません。

今回は OSS ツールとしてプログラムを公開したかったのでパブリックリポジトリを選択しました。

パブリックリポジトリ

パブリックリポジトリもいくつか選択肢があります。

有名どころは下記の 2つです。

決めては今後のブログエントリの話に影響するのですが、Github の優れたインテグレーションです。sshec2 の開発には多くの SaaS を利用しています。それらは全てソースコードリポジトリと連携することで自動でサービスの処理が動作します。その上で多くの SaaSGithub と連携しているということで Github を選択しました。

Github を使う

それでは簡単に Github の利用方法をご紹介します。

github.com

ユーザー登録

トップページにある入力欄(ユーザー名、メールアドレス、パスワード)を入力するだけでユーザー登録できます。Github は色々なサービスと連携するのでログインはユーザー名、パスワードだけでなく、多要素認証(ワンタイムパスワード)を追加することを推奨します。

リポジトリ作成

New Repository からリポジトリ名を入力するだけでリポジトリを作成できます。リポジトリを作成すると、初期コミットまでに必要なコマンドが表示されるのでそのまま実行するだけでリポジトリにプログラムを配置することができます。

ソースコードの修正

一人でソースコードを修正する場合、一つのブランチで運用すればよいです。が、複数人が修正する場合、各自が好き勝手にソースコードをコミットすると、どこかで不整合が発生します。しかもそれは多々あります。そこで別のブランチを切って、修正が完了したタイミングで本番ブランチにマージするといった運用がよくあるパターンです。Github ではプルリクエストという機能があり、プルリクエストを利用することで掲示板のやり取りのようにマージ前に修正内容をレビューすることができます。

まとめ

簡単にですが、ソースコードリポジトリについて調べたことを簡単にまとめました。

【sshec2】EC2 への SSH 接続の管理を楽にするツールを公開しました

こんにちは、久しぶりのブログエントリです。
会社でもブログ書いてて、最近はほとんどそちらに書いてました。今回は会社のブログに書くような内容じゃなかったのでこちらのブログにエントリします。

私はインフラ屋で仕事ではほとんどプログラムを書かない趣味プログラマーですが、周りの刺激を受けて OSS 開発したくなりました。

主な目的は、、

  • プログラムを書くこと
  • プログラムを運用すること
  • CI/CD を取り入れたナウい自動化の仕組みを導入・運用すること

です。

欲しい成果物があったわけではなく、上記目的を体験するために開発しました。

本日は成果物の報告ということでエントリします。

sshec2

ec2ssh を Google 検索すると、色々なツールが出てきます。cookpad の方や、海外の方のリポジトリが多く引っかかります。

私が中でも「これいいな」と思ったのは下記リポジトリ内にある ec2ssh です。

GitHub - tilfin/homebrew-aws: AWS commands easy to manipulate on terminal

コマンド実行することで EC2 インスタンスの一覧を取得し、対象のインスタンスを選択することで SSH ログインできるというものです。すごくいいです。ただ私の業務上、色々な環境に、色々な方法でログインするケースがあり、マッチしないケースが多々ありました。そこでより柔軟なオプションで利用できる sshec2 を開発しました。

機能

基本機能は上記 ec2ssh と同じで、EC2 インスタンスを一覧表示し、インスタンスを選択することで SSH ログインできます。

加えて、柔軟なオプションが多くあります。

  • SSH ログインユーザー名の指定
    • デフォルトは ec2-user
  • SSH ログインユーザーの秘密鍵の指定
    • デフォルトは ~/.ssh/ 配下の
  • 踏み台サーバを介した多段 SSH 接続
    • 踏み台サーバも個別のユーザー名、秘密鍵を指定可能
  • ソフトウェア VPN 接続対応(対象インスタンスへの自動ルーティング追加)
  • SCP モード(ローカル←→EC2 双方向対応)

詳細はドキュメントに記載していますので是非ご覧ください。

Github

github.com

ドキュメント

Welcome to sshec2’s documentation! — sshec2 0.1.0 documentation

今後は主に CI/CD のところで何をやってきたかをエントリしています。予定は下記のようなエントリを書いていく予定です。

Elasticsearch クラスタをマルチノードでインストールする

前回はElasticsearch を Linux上、Dockerコンテナでシングルノードでインストール起動しました。

前回のエントリは下記をご参照ください。

sfujimoto.hatenablog.com

今回も Linux上、Dockerコンテナそれぞれでのクラスタ構築を実施します。

Elasticsearch クラスタ on Linux

まずは Linux OS 上で Elasticsearch クラスタを構築します。ノード数は2台とします。

環境

Elasticsearch インストール

Elasticsearch のインストールはシングルノードと同じです。

Java 8、Elasticsearch をインストールします。

# echo "[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md" > /etc/yum.repos.d/elastic.repo

# rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

# yum install -y java-1.8.0 elasticsearch
:

# rpm -q elasticsearch java-1.8.0-openjdk
elasticsearch-5.1.1-1.noarch
java-1.8.0-openjdk-1.8.0.111-2.b15.el7_3.x86_64

2台ともにインストールします。

クラスタ設定

Elasticsearch の設定をします。クラスタを組むためには以下の設定が必要です。

項目 説明
discovery.zen.ping.unicast.hosts Elasticsearch のクラスタを構成する相手のホスト名、もしくはIPアドレスを指定します。
cluster.name Elasticsearch のクラスタ名を指定します。クラスタ構成とする場合、クラスタを構成する全てのノードで名前を合わせる必要があります。指定しない場合、デフォルトの```elasticsearch```が設定されます。
node.name Elasticsearch のノード名を指定します。指定は必須ではないですが、設定する場合はクラスタ内で異なるノード名を指定します。

今回は下記のような設定としています。

ノード1
# echo "network.host: 10.255.0.100, 127.0.0.1
cluster.name: my-cluster
node.name: node1
discovery.zen.ping.unicast.hosts: 10.255.0.101" > /etc/elasticsearch/elasticsearch.yml
ノード2
# echo "network.host: 10.255.0.101, 127.0.0.1
cluster.name: my-cluster
node.name: node2
discovery.zen.ping.unicast.hosts: 10.255.0.100" > /etc/elasticsearch/elasticsearch.yml

Elasticsearch 再起動

Elasticsearch サービスを再起動します。

# systemctl restart elasticsearch

動作確認

クラスタのノードを確認します。

# curl localhost:9200/_cat/nodes
10.255.0.101 3 97 1 0.12 0.08 0.08 mdi - node2
10.255.0.100 3 97 1 0.03 0.04 0.08 mdi * node1

2つのノードが表示され、クラスタが構成されていることがわかります。

Elasticsearch コンテナ クラスタ on Docker

続いて、Docker上で Elasticsearch クラスタを構築します。今回もノードは2台です。

環境

  • Docker ホスト : Docker for Mac 1.12
  • Elasticsearch : 5.1.1

Docker コンテナ起動

Elastic 社の Github リポジトリdocker-compose.ymlが提供されていますので利用します。

$ git clone https://github.com/elastic/elasticsearch-docker.git
:

$ tree elasticsearch-docker
elasticsearch-docker
├── LICENSE
├── Makefile
├── README.md
├── build
│   ├── elasticsearch
│   │   ├── Dockerfile
│   │   ├── bin
│   │   │   └── es-docker
│   │   ├── elasticsearch.yml
│   │   └── log4j2.properties
│   └── python
│       └── Dockerfile
├── docker-compose.hostports.yml
├── docker-compose.yml
└── tests
    ├── __pycache__
    │   └── test_xpack_basic_index_crud.cpython-27-PYTEST.pyc
    ├── es_acceptance.py
    ├── es_acceptance.pyc
    ├── test_basic_index_crud.py
    ├── test_xpack_basic_index_crud.py
    └── testdata.json

6 directories, 16 files

Makefilemakeコマンド)で簡単に利用できるのですが、このまま利用するとバージョンが 5.0.0-beta1 という若干古めのバージョンです。最新を利用したいので、Makefile に記載されているバージョンを 5.1.1 に書き換えます。

$ cd elasticsearch-docker
$ sed -i 's/ELASTIC_VERSION=5.0.1/ELASTIC_VERSION=5.1.1/g' Makefile

docker-compose でクラスタを起動します。

$ make run-es-cluster
docker pull docker.elastic.co/elasticsearch/elasticsearch-alpine-base:latest
latest: Pulling from elasticsearch/elasticsearch-alpine-base
:

数分で起動します。

動作確認

クラスタのノードを確認します。

$ curl -u elastic:changeme localhost:9200/_cat/nodes
172.18.0.2  7 87 4 0.47 0.43 0.27 mdi * P7MWTJw
172.18.0.3 11 87 4 0.47 0.43 0.27 mdi - QF4Caww

2つのノードが表示され、クラスタが構成されていることがわかります。

まとめ

いかがでしたでしょうか?

データストアのクラスタリングといえば、非常に設定が面倒なイメージがありますが、Elasticsearch は非常に簡単です。インストールだけでなく、設定も簡単なのは嬉しいですね。

Python で並行処理(マルチコア、マルチスレッド)を実装する

Python のマルチコア、マルチスレッドの実装を勉強する機会がありましたので、メモ書きします。

簡単に違いを説明すると、

  • マルチコア
    • 複数のプロセスを立ち上げることで複数のCPUコアを利用することができる
    • 得意なもの:CPUバウンドな並列処理も複数のCPUコアを利用することで効率化できる
    • 苦手なもの:並列処理間でプロセスが異なり、データの受け渡しが難しい
  • マルチスレッド
    • 一つのプロセスを立ち上げ、一つのCPUコアを時分割で利用することができる
    • 得意なもの:並列処理間も同じメモリ空間を利用することができる
    • 苦手なもの:CPUバウンドな並列処理はCPUがボトルネックになり、並列処理の効果が出ない

Python による並列処理

Python はマルチコア、マルチスレッドを実装するパッケージを標準で持っています。マルチコアにはmultiprocssingパッケージ、マルチスレッドにはconcurrentパッケージを利用します。マルチスレッドはthreadingパッケージもありますが、3.2系から追加されたconcurrentパッケージが簡単だったので、今回はconcurrentパッケージで説明します。

マルチコア

まずはマルチコアの実装方法です。

同時実行数制御

マルチコアの同時実行数制御はmultiprocessing.Poolクラスの引数でプロセス数を設定するだけです。

from multiprocessing import Pool
pool = Pool(4)

こちらは 4プロセスを起動します。

並列処理実行

Pool から別プロセスで処理を実行する場合、apply_asyncメソッドを利用します。

from multiprocessing import Pool
import time

def f():
    time.sleep(1)
    print(1)

pool = Pool(4)
pool.apply_async(f)
print(2)
time.sleep(2)

こちらは実行プロセスとは別のプロセスでf関数が実行されます。普通にf関数を呼び出すだけであれば、1 -> 2 の順に表示されますが、apply_asyncで別プロセスで処理を開始しているため、2 -> 1 の順に表示されます。

python ./multi_core_test.py
2
1

非同期処理に引数を渡す

apply_asyncメソッドの第二引数にタプル形式で渡すだけです。引数が一つでも、二つでもタプルに並べるだけです。

from multiprocessing import Pool
import time

def f(num):
    time.sleep(num)
    print(num)

pool = Pool(4)
pool.apply_async(f, (1, ))
print(2)
time.sleep(2)
python ./multi_core_test.py
2
1

非同期処理の終了を待機

先ほどのサンプルで最後に 2秒のスリープを入れています。これには理由があり、別プロセスは実行プロセスの子プロセスとして起動します。そのため、実行したプロセスは処理を終えると、別プロセスで処理が実行中であろうと、プロセスを終了します。ただ今回は 1秒のスリープと print による標準出力だけなので、スリープに必要な秒数が分かりますが、複雑な処理の場合、適切なスリープの設定は難しいでしょう。そこでmultiprocessingパッケージは並列処理と同期を取ることができます。

from multiprocessing import Pool
import time

def f():
    time.sleep(1)
    print(1)

pool = Pool(4)
result = pool.apply_async(f)
print(2)
result.wait()
print(3)

apply_asyncが返すApplyResultwaitメソッドにより、並列処理の実行の終了を待つことができます。

python ./multi_core_test.py
2
1
3

非同期処理の戻り値を受け取る

先ほどのサンプルでは戻り値を返していませんでしたが、今度はf関数で返した値を取得してみましょう。

from multiprocessing import Pool
import time

def f():
    time.sleep(1)
    print(1)
    return 4

pool = Pool(4)
result = pool.apply_async(f)
print(2)
print(result.get())
print(3)

apply_asyncが返すApplyResultgetメソッドにより、並列処理の実行の終了を待ち、戻り値を取得することができます。

python ./multi_core_test.py
2
1
4
3

マルチスレッド

まずはマルチスレッドの実装方法です。

同時実行数制御

マルチスレッドの同時実行数制御はconcurrent.futures.ThreadPoolExecutorクラスの引数でプロセス数を設定するだけです。

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(4)

こちらは 4プロセスを起動します。

並列処理実行

ThreadPoolExecutor から別スレッドで処理を実行する場合、submitメソッドを利用します。第一引数に並列実行したい関数を渡すだけです。

from concurrent.futures import ThreadPoolExecutor
import time

def f():
    time.sleep(1)
    print(1)

pool = ThreadPoolExecutor(4)
pool.submit(f)
print(2)
time.sleep(2)

こちらは別スレッドでf関数が実行されます。

python ./multi_thread_test.py
2
1

非同期処理に引数を渡す

submitメソッドの第二引数以降に引数となる値を渡すだけです。引数が一つの場合は第二引数に、引数が二つの場合は第二・第三引数に渡します。

from concurrent.futures import ThreadPoolExecutor
import time

def f(num):
    time.sleep(num)
    print(num)

pool = ThreadPoolExecutor(4)
pool.submit(f, 1)
print(2)
time.sleep(2)
python ./multi_thread_test.py
2
1

非同期処理の終了を待機

concurrent.futuresパッケージは並列処理と同期を取ることができます。as_completedを利用します。

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def f(num):
    time.sleep(num)
    print(num)

pool = ThreadPoolExecutor(4)
result = pool.submit(f, 1)
print(2)
as_completed([result]).next()
print(3)
python ./multi_thread_test.py
2
1
3

非同期処理の戻り値を受け取る

先ほどのサンプルでは戻り値を返していませんでしたが、今度はf関数で返した値を取得してみましょう。

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def f(num):
    time.sleep(num)
    print(num)
    return 4

pool = ThreadPoolExecutor(4)
result = pool.submit(f, 1)
print(2)
print(as_completed([result]).next().result())
print(3)

as_completedresultメソッドで戻り値を取得することができます。

python ./multi_thread_test.py
2
1
4
3

まとめ

簡単な紹介となりましたが、標準パッケージでも様々な機能を備えています。また公式ドキュメントに分かりやすく色々な機能を紹介されていますので是非そちらもご参考ください。

http://docs.python.jp/3/library/concurrent.futures.html#module-concurrent.futures http://docs.python.jp/3.5/library/multiprocessing.html