まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2016/11

MySQL の主キーとかに auto_increment 属性を付けておくと連番の ID を自動生成してくれるので便利です。

例えばテーブル定義時にこんな感じで指定しておくと、
> create table names( id int primary key auto_increment, name varchar(100) );

データ挿入時に id を指定せず(気にせずに)他の値だけを挿入すればよくなって、
> insert into names( name ) values( '鈴木' );
> insert into names( name ) values( '佐藤' );
> insert into names( name ) values( '田中' );
> insert into names( name ) values( '山田' );
  :

ユニークな ID が自動で割り当てられる、ということが実現できます:
> select * from names;
+----+------+
| id | name |
+----+------+
|  1 | 鈴木 |
|  2 | 佐藤 |
|  3 | 田中 |
|  4 | 山田 |
     :
+----+------+

このように便利な auto_increment 属性ですが、便利な反面でコントロールする必要が出た場合に少しコツが必要になります。例えば 10 レコード入った時点で最後のデータに間違いが判明して、削除してしまったとします:
> select * from names;
+----+------+
| id | name |
+----+------+
|  1 | 鈴木 |
|  2 | 佐藤 |
|  3 | 田中 |
|  4 | 山田 |
     :
|  9 | 山本 |
| 10 | 木村 | ←ミスしたので削除↓
+----+------+

> delete from names where id = 10;

> select * from names;
+----+------+
| id | name |
+----+------+
|  1 | 鈴木 |
|  2 | 佐藤 |
|  3 | 田中 |
|  4 | 山田 |
     :
|  9 | 山本 |
+----+------+

この時点でテーブルには 9 件のレコードが入っています。ここで次のデータをインサートすると auto_increment はリセットされずに id = 11 として 10 件目のデータが挿入されます:
> insert into names( name ) values( '武田' );

> select * from names;
+----+------+
| id | name |
+----+------+
|  1 | 鈴木 |
|  2 | 佐藤 |
|  3 | 田中 |
|  4 | 山田 |
     :
|  9 | 山本 |
| 11 | 武田 |
+----+------+

ID をどう考えるかにもよりますが、この挙動を(ユニークである、という点は確保できているので)これはこれで OK と考えるのであれば特に気にする必要はありません。ただ連番ではなくなってしまうので、auto_increment を自分の意図で(上記の例であれば 10 から再スタートするように)リセットしたくなることもあります。

その場合は、以下のコマンドを実行します:
> alter table names auto_increment = 10;

names テーブルの auto_increment 属性値を 10 に(強制的に)変更しました。この状態からインサートすると、次に入るレコードの ID は 10 になります:
> insert into names( name ) values( '武田' );

> select * from names;
+----+------+
| id | name |
+----+------+
|  1 | 鈴木 |
|  2 | 佐藤 |
|  3 | 田中 |
|  4 | 山田 |
     :
|  9 | 山本 |
| 10 | 武田 |
+----+------+

ID は本来内部的に利用するべきものなので抜け番号があったからまずいわけでもなく、また連番でないといけないような制約が入るのは本来あまりいいことではありません。でもまあ気持ち的にスッキリはしますかね。

ElasticSearch で英語以外のテキストを使った検索エンジンを実装しようとすると、多くの場合 ICU(International Components for Unicode) というプラグインを使うことになります。特に日本語を扱う場合は kumoroji プラグインとセットで使うことが多いようですが、日本語文字列を正規化してフィルタリングをかける場合のデファクトスタンダードになっている組み合わせだと思います。

この ICU プラグインを ElasticSearch に導入する方法は ICU プラグインのホームページに記載されています:
https://github.com/elastic/elasticsearch-analysis-icu


具体的な導入方法は上記ページ内に記述されているように、コマンドラインから
# bin/plugin -install elasticsearch/elasticsearch-analysis-icu/(ICU のバージョン番号)

と入力・・・ だと思っていました。ところがこの方法は ElasticSearch のバージョンが 1.x までの頃の話でした:
2016111801


現在、多くの人が使っている ElasticSearch のバージョンは 2.x 以上だと思っています(2016/Nov/18 時点の最新バージョンは 5.0)。では ElasticSearch 2.x で ICU を導入するにはどのようにすればいいのでしょうか?

答はこれでした。かなりシンプルになりました:
# bin/plugin -install analysis-icu

これ、公式のどこかに書いてないのかな・・・

スケーラブルな検索エンジンである ElasticSearch は Ver.2 以降の仕様でデフォルト設定では localhost からのアクセスのみが許可されるようになりました。要するにデフォルト設定のままでは外部からの検索リクエストを受け付けない、ということになります。

このままでも問題ない、という人もいると思いますが、検索サービスとして利用するケースを想定すると、このままでは外部に検索 API 機能を提供できないことになってしまいます。外部からのリクエストを受け付けるには elasticsearch.yml(CentOS などであれば /etc/elasticsearch/elasticsearch.yml)を編集して、以下の1行を追加します:
network.host: 0.0.0.0

この状態で ElasticSearch を再起動すると外部アクセスが可能になります。

ただし、この指定だと本当にどこからでも使えてしまいます。つまり外部から DELETE リクエストでインデックスごと消す、なんてことも出来てしまいます。それはそれで危険かもしれません。実際の運用においては特定のアドレス(範囲)からのアクセスだけ受け付けるようにする、などの工夫をすべきだと思います。

IBM Watson の API を使っていたら、ある日を境にこんなエラーが出るようになりました:
javax.net.ssl.SSLException: java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 64

2016110901


エラーが出ているのは URL を指定して REST API を実行する瞬間の箇所です。GET でも POST でも発生しました。

原因を調べるために Watson API Explorer を使って、同じ API を実行してみたところ・・・今度はエラーなく実行できてしまいました:
2016110902


なんだこれは!? というわけで、エラーメッセージをググって調べてみたところ、こんな StackOverflow にこんなスレッドを見つけました:
Receiving “javax.net.ssl.SSLException: java.lang.ArrayIndexOutOfBoundsException” while connecting to “https:” site


ここに書かれている内容が原因であるとすると、暗号化アルゴリズムである TLS のバージョンの変更が原因らしいとのこと。またいくつかの対処方法があるが、クライアント側だけで対応するには JDK のバージョンを 1.8 にしろ、とのことらしいです。

確かに今回エラーが出るようになったシステムでは JDK 1.6 が使われているので、このエラーが発生する条件は満たしています。また Watson API Explorer 側では JDK 1.8 か、Java 以外のアプリケーションサーバーが使われていると仮定すればエラーなしに成功することになります。なので、今回のような結果になっても説明は付くことになります。
2016110903



原因が分かったところで、問題は対処方法です。今回はとあるシステム内からこの API を実行したかったという背景があります。そのシステムに組み込まれている JDK のバージョンが 1.6 である以上、ここを勝手に変更するわけにはいきません。 一方で、クライアントが JDK 1.8 未満である以上はこのエラーを回避するにはサーバー側を変更する必要があります。API 提供されているサーバー設定を変更してもらえる保証もありません。そもそもセキュリティ強化のためのサーバー設定変更が背景にあるわけですが、仮にセキュリティに一旦目をつぶるとして、JDK 1.6 のままでどうにかする方法はないでしょうか?


その方法の1つとして考えられるのが、下図のように JDK 1.8(または他のプラットフォームでもよい)など Watson API を実行できる環境でプロクシー的なものを作って、JDK 1.8 未満のシステムからはこのプロクシーを経由してリクエストを実行し、レスポンスを得る、という仕組みです:
2016110904

こうすると JDK 1.8 未満のシステムからはプロクシーまでアクセスできればよく、またプロクシー側の設定等にも自分である程度手を加えることも可能になります。そして Watson からは JDK 1.8 からのリクエストとして API を実行して結果を返す、という仕組みを作ればうまくいくのではないか、と考えました。まあ2つの仕組みを作ってメンテナンスしなければならない、という点はありますが、技術的にはクリアできる目処が立ちそうです。

近いうちに実際に作ってみた仕組みをまた紹介する予定です。


このページのトップヘ