動かざることバグの如し

近づきたいよ 君の理想に

MariaDBでJSON型を使う

使いたい人生だった

Qittaのとある記事みてMySQL5.7から追加されたJSON型で遊んでみたくなった。さっそく手元のDBで試そうと思ったら

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'JSON

ファーーーーーーーーーーーってそういえば手元のDBMySQLじゃなくてMariaDBだったは(

で終わり、は流石にアレなのでMariaDBDynamic Columnsで遊んでみた

環境

試す

まずは適当なjson_testテーブルを作成(正確にはJSONではないがJSON型があると思って作ってしまったので

CREATE TABLE `json_test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `json` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

1つデータを追加してみる

INSERT INTO json_test VALUES (1, COLUMN_CREATE('name', 'taro', 'color', 'red'));

いけたのでもう1つ適当に

INSERT INTO json_test VALUES (2, COLUMN_CREATE('name', 'hanako', 'color', 'black'));

見てみよう

mysql> SELECT * from json_test
+----+-------------------------------------+
| id | json                                |
+----+-------------------------------------+
|  1 |           S namecolor!taro!red     |
|  2 |           s namecolor!hanako!black |
+----+-------------------------------------+

お、おう?

中身も表示させたい場合は COLUMN_JSONを使うといい感じ。JSONで取れるの便利

mysql> select id, COLUMN_JSON(json) from json_test;
+----+-----------------------------------+
| id | COLUMN_JSON(json)                 |
+----+-----------------------------------+
|  1 | {"name":"taro","color":"red"}     |
|  2 | {"name":"hanako","color":"black"} |
+----+-----------------------------------+

JSONの特定の要素のみ取得したい場合はCOLUMN_GET

mysql> select id, COLUMN_GET(json, 'name' AS char) AS name from json_test;
+----+--------+
| id | name   |
+----+--------+
|  1 | taro   |
|  2 | hanako |
+----+--------+

JSONに対してWHEREもかけることができる

SELECT id, COLUMN_JSON(json) FROM json_test WHERE COLUMN_GET(json, 'name' AS CHAR) = 'taro';
+----+-------------------------------+
| id | COLUMN_JSON(json)             |
+----+-------------------------------+
|  1 | {"name":"taro","color":"red"} |
+----+-------------------------------+

今までにないカラムを追加してみる

INSERT INTO json_test VALUES (3, COLUMN_CREATE('name', 'piyo', 'color', 'blue', 'age', 33));

見てみよう

mysql> select id, COLUMN_JSON(json) from json_test;
+----+-----------------------------------------+
| id | COLUMN_JSON(json)                       |
+----+-----------------------------------------+
|  1 | {"name":"taro","color":"red"}           |
|  2 | {"name":"hanako","color":"black"}       |
|  3 | {"age":33,"name":"piyo","color":"blue"} |
+----+-----------------------------------------+

値のないものはNULLになる

mysql> select id, COLUMN_GET(json, 'age' AS integer) AS name from json_test;
+----+------+
| id | name |
+----+------+
|  1 | NULL |
|  2 | NULL |
|  3 |   33 |
+----+------+

ElasticsearchのReindex機能を使ってスキーマを変更する

スキーマって言うとめっちゃコラコラされそうだが。(正確にはmapping

環境

ElasticsearchのReindex APIElasticsearch 2.3.0以降で実装された機能で、その名の通り既存のindexのドキュメントをコピーすることができる。

ここではindex「twitter」から「twitter_new」にコピーするとする。

やりかた

まずコピー先のindexを先に作成する。

curl -XPUT localhost:9200/twitter_new -d@tw.json

当然中身はまだ空

次にreindex用のjsonをreindex.jsonとして用意する。typeは指定しないとindex内の全ドキュメントをコピーするので注意。

{
  "source": {
    "index": "twitter",
    "type": "tweet"
  },
  "dest": {
    "index": "twitter_new",
    "type": "tweet"
  }
}

あとは作ったjsonを指定してreindex APIを叩くだけ

curl -XPOST dev41:9200/_reindex -d@reindex.json

以下のような返ってくればおk

{
  "took": 16808,
  "timed_out": false,
  "total": 152257,
  "updated": 0,
  "created": 152257,
  "deleted": 0,
  "batches": 153,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1,
  "throttled_until_millis": 0,
  "failures": []
}

Elasticsearchの検索でqueryとrangeを同時に付けるとUnknown key for a START_OBJECT

rubyからElasticsearch使おうとしたらエラーになったのでメモ

環境

  • Ubuntu 16.04
  • Elasticsearch 5
  • elasticsearch-ruby 5.0.4

失敗したコード

require 'elasticsearch'

client = Elasticsearch::Client.new({
  log: false,
  hosts: {
    host: 'localhost',
    port: 9200
  }
})

q = {
  query: {
    query_string: {
      default_field: 'subject',
      default_operator: 'AND',
      query: "ruby",
    },
  },
  filter: {
    range: {
      created_date: {
        gte: "2017-12-01",
        lte: "2017-12-25"
      }
    }
  }
}

data = client.search(index: 'myindex', type: 'movies', body: q)
puts data.to_json

一見イケそうだがエラーになる。てかElasticsearchバージョン2系はこれでいけた

/Users/hoge/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/elasticsearch-transport-5.0.4/lib/elasticsearch/transport/transport/base.rb:202:in `__raise_transport_error': [400] {"error":{"root_cause":[{"type":"parsing_exception","reason":"Unknown key for a START_OBJECT in [filter].","line":1,"col":104}],"type":"parsing_exception","reason":"Unknown key for a START_OBJECT in [filter].","line":1,"col":104},"status":400} (Elasticsearch::Transport::Transport::Errors::BadRequest)
from /Users/hoge/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/elasticsearch-transport-5.0.4/lib/elasticsearch/transport/transport/base.rb:319:in `perform_request'
from /Users/hoge/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/elasticsearch-transport-5.0.4/lib/elasticsearch/transport/transport/http/faraday.rb:20:in `perform_request'
from /Users/hoge/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/elasticsearch-transport-5.0.4/lib/elasticsearch/transport/client.rb:131:in `perform_request'
from /Users/hoge/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/elasticsearch-api-5.0.4/lib/elasticsearch/api/actions/search.rb:183:in `search'
from sugukesu.rb:29:in `<main>'

Unknown key for a START_OBJECT in [filter]ってなんだよとか思って色々調べていたら、queryとrangeを併用できないらしい

正しいのは以下

q = {
  query: {
    bool: {
      must: [
        {
          query_string: {
            default_field: 'subject',
            default_operator: 'AND',
            query: "ruby",
          },
        },
        {
          range: {
            created_date: {
              gte: "2017-12-01",
              lte: "2017-12-25"
            }
          }
        }
      ]
    } 
  }
}

UbuntuにElasticsearchをインストールして使えるようにするまで

環境

  • Ubuntu server 16.04
  • Elasticsearch 5.x

インストール

本家ドキュメント見ろって話ではあるが

そもそもElasticsearchにはJava 8の環境(JDK)が必要。UbuntuではOpen JDK(公式レポジトリで公開)とOracle JDK(PPA追加でインストール可)の2つがあり、今回は前者で入れる

apt install openjdk-8-jdk default-jdk
# JAVA_HOMEはupdate-alternatives --list javaで確認できる
echo JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 > /etc/profile.d/java.sh

これで再ログインするとjavaが使えるようになる

レポジトリの追加

wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-5.x.list
apt update
apt install elasticsearch

必須ではないが、日本語を含めた検索にはkuromojiと呼ばれるプラグインが必要になるのでこの時点でインストールしておく

/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji

初期設定

インストール時点ではまだElasticsearchは起動していない。/etc/elasticsearch/elasticsearch.ymlを弄って最低限以下を修正する。

  • cluster.name クラスタネーム 分かりやすい名前で
  • node.name: ノードの名前 ホスト名と合わせると楽かも
  • path.data: データの置く場所 /elasticsearchとか、どこでもいいが、パーミッションをelasticsearch:elasticsearchにする必要がある
  • path.logs: ログの場所 /var/log/elasticsearchとか パーミッションをelasticsearch:elasticsearchにする必要がある
  • network.host: 外部から参照する場合は0.0.0.0にする

後述するが、以下のオプションも追記しておくと吉

bootstrap.system_call_filter: false
http.cors.enabled: true
http.cors.allow-origin: "*"

起動

service elasticsearch start

node settings must not contain any index level settingsで起動できない

index系の設定をelasticsearch.ymlにかくと怒られる。Elasticsearch 2系の設定をそのまま流用しようとするとコケるので注意

java.lang.IllegalArgumentException: node settings must not contain any index level settings

node validation exceptionで起動できない

ログに以下のようなエラーが出て表示できない場合がある

[ERROR][o.e.b.Bootstrap          ] [es1] node validation exception
c
max number of threads [1024] for user [elasticsearch] is too low, increase to at least [2048]
system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk

その時は /etc/security/limits.confに以下を追記

elasticsearch soft nproc 65536
elasticsearch hard nproc 65536

でさっきの/etc/elasticsearch/elasticsearch.ymlにbootstrap.system_call_filter: falseを追記したらいけた

head

git clone git://github.com/mobz/elasticsearch-head.git
cd elasticsearch-head
npm install
npm run start

elasticsearch.ymlに以下を追記

http.cors.enabled: true
http.cors.allow-origin: *

初期化せずにHDDのパーティンションテーブルをGPTに変換する

いやー久々にハマった

状況

今まで2TBのHDDで運用してきたが、単価も安くなってきたので今回4TBのHDDを購入。例のクローン機能を使って無事にコピーまではできたが、いざディスクの拡張でコケる。

パーティションを 1.82 TiB から 3.64 TiB に拡大する  00:00:00    ( エラー )
        
以前の開始位置: 2048
以前の終了位置: 3907024064
以前の容量: 3907022017 (1.82 TiB)
要求された開始位置: 2048
要求された終了位置: 7814035455
要求された容量: 7814033408 (3.64 TiB)
libparted のメッセージ    ( 情報 )
        
partition length of 7814033408 sectors exceeds the msdos-partition-table-imposed maximum of 4294967295

sectors exceeds the msdos-partition-table-imposed maximum of 4294967295ってアーーーーーー

解決策

どうもこうもMS-DOSパーティンションテーブルは2TBが限界らしい。ってことで2TB以上対応してるのはGPTというパーティンションテーブルで、それに変換することにした。

最初HDDの中身も飛ぶのかと思ったがそんなことはなかった。オプションで-gをつけるだけ

       -g, --mbrtogpt
              Convert an MBR or BSD disklabel disk to a GPT disk. As a safety measure, use of this
              option is required on MBR or BSD disklabel disks if you intend to save your changes,
              in order to prevent accidentally damaging such disks.

恐る恐るやってみよう

# sgdisk -g /dev/sdc

***************************************************************
Found invalid GPT and valid MBR; converting MBR to GPT format
in memory. 
***************************************************************

The operation has completed successfully.

ビビるぐらい一瞬だった。

間違ってgdiskコマンドで-gするとMBRに書き換えるとかいう罠があるので注意

man gdiskより、

g Convert GPT into MBR and exit.

オプション統一しろよ(

教訓

身の丈にあった容量を買え