カテゴリー: テクノロジー

Apacheの前にNginxを置いた際にApache側で行う設定例

サーバーは一度設定するとなかなか次に設定することがなく、設定方法を忘れてしまいます。
今回はApacheが走っているサーバーの前段としてNginxを用意した場合にするであろう設定例です。
※ファイルの場所はUbuntuの例です。他のディストリビューションの場合はパスが変わってくるので適宜読み替えてください。

Apacheの設定

ポートを80から8080へ変更します。※例ではついでにIPv4のみにしています。
/etc/apache2/ports.conf

Listen 80
Listen 127.0.0.1:8080

Apacheのアクセスログに残すIPがリモートのIPになるようremoteipモジュールを設定します。
※何も設定しないと全てNginxの走っているサーバーのIPになってしまいます。

このモジュールの設定ファイルを作成します。
/etc/apache2/mods-available/remoteip.conf

RemoteIPInternalProxy 127.0.0.1
RemoteIPHeader        X-Forwarded-For
RemoteIPProxiesHeader X-Forwarded-By

モジュールと設定を有効化します。

$ sudo a2enmod remoteip

一般的にログフォーマットの設定では%hのほうがデフォルトで使われているのでこれを%aへ変更します。log_config

LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined

ここまでの設定を有効化します。

$ sudo apache2ctl restart

これでApache側でリモートのIPが取得できるようになります。

お疲れさまでした。


Laravelのエラーログに情報を追加する方法

ログの拡張

開発中であればエラーはそのまま表示されるので解決は比較的容易ですが、運用中に発生した場合はエラーログから調査するので少し時間がかかります。

エラーログにはデフォルトで発生時ログインしているuserIdが一緒に出力されエラーが発生したファイル名や行番号も出力されるので基本的にはそちらからエラーの調査は行えますが、特定のレコード時だけ発生したなどになるとそれだけでは時間がかかる時があります。

なので出力されるログの内容を拡張できないかフレームワークのソースを調べたところ

Illuminate/Foundation/Exceptions/Handler.php
にcontextと目的の関数があり

contextの関数をオーバーライドすればいい様子

以下実装例

app/Exceptions/Handler.php
/**
 * ログに記載する追加情報
 */
protected function context()
{
$context = parent::context(); try { return array_merge($context, [ // 一緒に記録したい情報 'uri' => env('REQUEST_URI'), ]); } catch (Throwable $e) { return $context; } }

以上!


SolrのUAX29URLEmailTokenizerを使う

Solrにはさまざまなトークナイザーが用意されており、その中に UAX29URLEmailTokenizer があります。
このトークナイザーは基本的には StandardTokenizer と同じ挙動なのですが、以下の要素をトークンとして切り出してくれるという特徴があります。

  • ドメイン名
  • URL(http(s)://, file://, ftp://)
  • Eメールアドレス
  • IPアドレス(IPv4とIPv6)

UAX29URLEmailTokenizer を使うフィールドタイプとフィールドを定義して動作を確認してみます。

<fieldType name="text_url" class="solr.TextField" positionIncrementGap="100">
  <analyzer>
    <tokenizer class="solr.UAX29URLEmailTokenizerFactory"/>
  </analyzer>
</fieldType>                                                                                                            
<field name="url_test" type="text_url" stored="true"/>

この設定で「http://www.example.com/example/です。」をトークナイズした結果は以下の通りです。

日本語の文書を扱う場合、通常の文章の部分が StandardTokenizer 相当でトークナイズされるといろいろ使い勝手が悪いので、たとえば形態素解析のトークナイザーと組み合わせることを考えます。

<fieldType name="url" class="solr.TextField" positionIncrementGap="100">
  <analyzer type="index">
    <tokenizer class="solr.UAX29URLEmailTokenizerFactory"/>
    <filter class="solr.TypeTokenFilterFactory" types="url_whitelist.txt" useWhitelist="true"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.KeywordTokenizerFactory"/>
  </analyzer>
</fieldType>

<field name="body" type="text_ja" stored="true"/>
<field name="url" type="url" stored="true"/>
<copyField source="body" dest="url"/>

UAX29URLEmailTokenizer の後ろに TypeTokenFilter を追加することで、必要なトークンタイプだけを残すことができます。TypeTokenFilter のホワイトリストとして url_whitelist.txt に以下の1行だけを記述して、URLだけを残すようにします。

<URL>

クエリ用のアナライザを別に定義したのは、URLのフィールドの検索では余計なトークナイズをさせずに文字列マッチ相当の動作にしたいからです。
urlフィールドをurlフィールドタイプとして定義し、text_jaフィールドタイプであるbodyフィールドのコピーフィールドにしています。

以下の文書を投入します。

[
    {"id":"1",
     "body":"昨日 http://blog.splout.co.jp/ をみた。"
    },
    {"id":"2",
     "body":"明後日は http://splout.co.jp/ をみる。"
    }
]

各フィールドにどういうトークンが納められたのかを確認します。

$ curl -s 'http://localhost:8983/solr/urlTokenizer/terms?terms.fl=url&terms.limit=-1&omitHeader=true&wt=json'
{
  "terms":{
    "url":[
      "http://blog.splout.co.jp/",1,
      "http://splout.co.jp/",1]}}

urlフィールドにはURLだけが納められています。

$ curl -s 'http://localhost:8983/solr/urlTokenizer/terms?terms.fl=body&terms.limit=-1&omitHeader=true&wt=json'
{
  "terms":{
    "body":[
      "co",2,
      "http",2,
      "jp",2,
      "splout",2,
      "みる",2,
      "blog",1,
      "明後日",1,
      "昨日",1]}}

bodyフィールドには通常の形態素解析結果が納められています。

この状態で検索してみます。まずは body フィールドを “http://splout.co.jp/” で。

$ curl -s 'http://localhost:8983/solr/urlTokenizer/select?q=body%3Ahttp%5C%3A%5C%2F%5C%2Fsplout.co.jp%5C%2F'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":0,
    "params":{
      "q":"body:http\\:\\/\\/splout.co.jp\\/"}},
  "response":{"numFound":2,"start":0,"docs":[
      {
        "id":"2",
        "body":"明後日は http://splout.co.jp/ をみる。",
        "url":"明後日は http://splout.co.jp/ をみる。",
        "_version_":1656715432355168256},
      {
        "id":"1",
        "body":"昨日 http://blog.splout.co.jp/ をみた。",
        "url":"昨日 http://blog.splout.co.jp/ をみた。",
        "_version_":1656715432353071104}]
  }}

“http”, “splout”, “co”, “jp” がバラバラにトークンとして扱われるので、”http://blog.splout.co.jp/” も “http://splout.co.jp/” も両方がヒットしています。
次に url フィールドを同じく “http://splout.co.jp/” で。

$ curl -s 'http://localhost:8983/solr/urlTokenizer/select?q=url%3Ahttp%5C%3A%5C%2F%5C%2Fsplout.co.jp%5C%2F'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":0,
    "params":{
      "q":"url:http\\:\\/\\/splout.co.jp\\/"}},
  "response":{"numFound":1,"start":0,"docs":[
      {
        "id":"2",
        "body":"明後日は http://splout.co.jp/ をみる。",
        "url":"明後日は http://splout.co.jp/ をみる。",
        "_version_":1656715432355168256}]
  }}

“http://splout.co.jp/” の方だけがヒットします。

urlフィールドの検索ではワイルドカードを使うこともできます。”http://*.splout.co.jp/” を指定して splout.co.jp のサブドメインだけを検索してみます。

$ curl -s 'http://localhost:8983/solr/urlTokenizer/select?q=url%3Ahttp%5C%3A%5C%2F%5C%2F*.splout.co.jp%5C%2F'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":1,
    "params":{
      "q":"url:http\\:\\/\\/*.splout.co.jp\\/"}},
  "response":{"numFound":1,"start":0,"docs":[
      {
        "id":"1",
        "body":"昨日 http://blog.splout.co.jp/ をみた。",
        "url":"昨日 http://blog.splout.co.jp/ をみた。",
        "_version_":1656715432353071104}]
  }}

文章に含まれるURLやメールアドレスの扱いは案外面倒なものです。
UAX29URLEmailTokenizer をうまく使えば、自前でURLやメールアドレスを抽出してstringフィールドに格納するよりもバグも少なく取り扱うことができるでしょう。


PHP Laravel bladeとvuejsと。

いよいよ花粉症の季節がやってきますね。
> 花粉さん、今年は注射治療しますがお手柔らかにお願いしますね。
マエダです。

さてこのタイトルなんなのかとご説明させていただきますと、みんな大好きLaravelのBladeテンプレートにControllerから渡された変数を利用して、vuejsの変数名を生成して、vuejsの変数を表示したい!

僕の日本語がヘンくてわかりませんね。(変)

Bladeテンプレートの変数の表示

こちら利用したことある方はおなじみの記述方法ですね。

<span>
{{ $name }}
</span>

Vue.jsの変数の表示(Blade内)

Laravel利用者はおなじみだと思いますが、BladeとVue.jsの波括弧とで構文エラーとなるのを先頭に「@」を付けることで回避する記述方法を利用します。

<span>
@{{ name }}
</span>

Vue.jsの変数をPHP変数で変更して表示

上記を踏まえまして、これってどうかなーってBladeテンプレートに書くと・・・

<span>
@{{ users.{{ $user_id }}.name }}
</span>

「[Vue warn]: Error compiling template」

波括弧の閉じるがどっちやねん!って怒られてしまいますね。。。

そこでの回避方法として、

<span>
@php echo("{{ users.{$user_id}.name }}") @endphp
</span>

急に波括弧を使わない!という選択。( ー`дー´)キリッ

そもそもそんなPHPとJavaScriptどっちつかずの設計にするなよとかのツッコミは受け付けません。
こうやったらどうですか?という優しいツッコミだけお願いします。

以上、vuejsに触れてみようのコーナーでした。

\\優しくツッコミをいれてくれる仲間を募集中です!!//
vuejsビギナーの僕がよくわからない利用方法を実験しているときに優しくツッコミいれてくれるそこのあなた。
以下ページよりご応募お待ちしております。
https://splout.co.jp/recruit/


SolrCloudのエイリアス機能(その2)

はじめに

SolrCloudのエイリアス機能には大きく分けて Standard Aliases と Routed Aliases があります。基本的な機能はどちらも共通で、Routed Aliases には特定のフィールドの値に応じて特定のコレクションを対象として指定できるという特徴があります。

この記事では、Routed Aliases の中の Time Routed Aliases を採り上げます。

Time Routed Aliases の作成

Time Routed Aliases を作るときの基本的なリクエストは以下のようなものです。

$ curl -s 'http://localhost:8983/solr/admin/collections?action=CREATEALIAS&name=timedata&router.start=NOW/DAY&router.field=date&router.name=time&router.interval=%2B1DAY&router.maxFutureMs=3600000&router.autoDeleteAge=/DAY-90DAYS&create-collection.collection.configName=aliasConfigSet&create-collection.numShards=1'

指定されているパラメータの説明

  • action=CREATEALIAS
  • router.name=time
    • Time Routed Aliases の場合は “time” を指定
  • name=timedata
    • エイリアス名
    • コレクション名は、この文字列をプレフィックスとして”timedata_2019-12-25″のようになる
  • router.field=date
    • 振り分けの基準にするフィールド名
  • router.start=NOW/DAY
    • 起点の時刻
    • この時刻を含むコレクションが最初のコレクションとして生成される。それより古いデータはエラーになる。
  • router.interval=+1DAY
    • 1つのコレクションが受け持つ時間の範囲
  • router.maxFutureMs=3600000
    • 現在時刻よりもどのくらい先の時刻までのデータを受け入れるか(ミリ秒)
    • 3600000が指定された場合、1時間先のデータまでは受け入れる
  • router.autoDeleteAge=/DAY-90DAYS
    • ここで指定された期間を過ぎたコレクションは自動的に削除される
  • create-collection.collection.configName=aliasConfigSet
    • Time Routed Aliases に属するコレクションが共通して持つコンフィグセットの指定
  • create-collection.numShards=1
    • Routed Aliases では CREATEALIAS の際にコレクションを作成するので、CREATE アクションでコレクションを作成する際に指定できるパラメータをcreate-collection.* という形で指定できる。

たとえば2019年12月25日に上に示したリクエストを実行したとすると、以下のような運用になります。

  • エイリアス名 timedata
  • コレクション名 timedata_2019-12-25
  • 現在時刻から1時間先のデータまで受け付る
  • 2019年12月26日のデータを受け付けると timedata_2019-12-26 が作られる
  • 90日よりも古くなったコレクションは削除

コレクションの自動追加

上述の通り、最新のコレクションが timedata_2019-12-25 であるときに12月26日のデータを追加しようとすると、新しく timedata_2019-12-26 が作られます。 新しいコレクションの作成には数秒を要し、その間 timedata_2019-12-26 への更新処理はブロックされます。

秒あたりのデータ数が多く、このブロック発生が許容できない場合には、CREATEALIAS アクションのオプションとして router.preemptiveCreateMath を指定できます。ここで例えば60分を指定しておくと、12月25日の23:00のデータを処理した時点で先に timedata_2019-12-26 を非同期に作成しておくことができます。

おわりに

Time Routed Aliases は以下の特徴を持つ Routed Aliases です。

  • エイリアス配下のコレクションそれぞれが一定の時間範囲のデータを担当する
    • (例)12月10日用のコレクション、11日用のコレクション、12日用のコレクション…
  • 時刻のフィールドによる振り分け
  • 更新を許容する時間範囲の設定
  • 現在時刻に応じて自動的に新しいコレクションを追加
  • 現在時刻に応じて古いコレクションを自動的に削除

時間的に連続するログデータやセンサの計測データなどを扱うのに向いています。