その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

テクノブレーン被害者アドベントカレンダー Day 19

この記事はテクノブレーン被害者アドベントカレンダーの19日目として書かれています。このアドベントカレンダーは今まさに作りましたから、参加者は自分しかいません。他に被害者がいたら続きを書いておいてください。
この記事は特定の企業に対する苦情および批判が含まれます。お前だ、テクノブレーン

こんなことが横行していては、「リクルーティング」という職業の価値が著しく毀損されてしまうし、ソフトウェアエンジニアリング産業自体がスポイルされていってしまう。

明確に、俺は強く怒っている。お前たちは「駄目」だ。

TL;DR

  • テクノブレーンは本当に悪質なリクルーティング企業なので使ってはなりません。
  • テクノブレーンから電話が来ましたか? 奴らはカモフラージュしてきますが相手をしてはいけません。
  • テクノブレーンを貴方の所属する企業が採用目的で利用していますか? こんな邪悪な企業を使っているようでは自身の会社も邪悪だと思われますよ。少なくとも俺は軽蔑します。使ってはなりません。

本題

こういったことが、自分の把握している限り今年に入ってから5件以上発生しています。

だいたい、以下のパターンです。

  • なんらかの有名企業の名前が付いた子会社っぽい社名を名乗ってきて、「用事があるから」として所属企業の代表番号に電話をかけてきて、折り返しの電話を要求してきます。
    • この際、詳細な要件を追及すると一切話してきませんから、その時点で怪しいです。
    • 上記の場合は「トヨタITソリューションズ」という社名を名乗っていますが、そんな会社は実在しません。トヨタはもっとしっかりした、素晴しい企業です、GR 86とか作ってる最高の企業なんですよ。連中は単に「トヨタ」という接頭辞を付けて「それらしい」会社名をでっちあげて、電話の折り返しの確率を上げているだけだと考えられます。
  • 携帯電話の番号で電話をかけてきます。タチが悪いですね。
  • 仮に折り返しの電話をかける際、非通知設定、Skype、Twilioなどから電話をかけると取ってくれません。悲しいですね。俺は闘う準備ができているのだぞ。


と、こんな感じです。こんな電話は決して取り継がないでください。
もちろん、折り返しの電話は皆さん絶対にかけないでください! しかしあえて電話をかけてみると……*1

相手「もしもし」 (ここで決して自身が何者かは名乗らない)
僕「もしもし、電話を頂いた id:moznion (本当は本名を名乗っている) ですけど」
相手「はい」
僕「どういったご要件ですか、トヨタITソリューションズさんとのことでしたが、あなたは誰ですか」
相手「わたくし、テクノブレーンの……」
僕「アッ、ハイ、もう大丈夫でs」
(ここでブツッと電話が切れる)

という感じで、こちらが真摯に拒否しようとしているのにも関わらず途中で電話をブチ切りしてきます。無礼にも程がありますね。偽名だろうが、ナカガワ、貴様だ。

まとめ

テクノブレーン及び類似の業者に告ぐ。

  • 代表電話で取り継いでくれるメンバーも、俺も、お前のように暇ではない。クソみたいな電話をかけてくるんじゃねえ。
  • これ以上、同じ手口でリクルーティングをしかけてくるようであれば相応の対応を検討するのでそのつもりで。
    • ご提案:


人類全員、誰もテクノブレーン及び類似の業者を、求職、転職、その他あらゆる目的で利用してはなりません。本当にゴミカス。
リクルーティングというのは企業組織を形作るための営みなのであって、重要な経営行為の1つであるように思います。そんな重大なイベントについて、このような詐欺的な行いが横行していては絶対にならない。

以上です。テクノブレーンさん側から何か申し開きがある場合はコメント欄からどうぞ。

*1:無いと思うけど、なにか仕事で関係あったっけ……と思って念のためかけたというのがあります、そういったメンタリティをハックしてきてるんだろうなあ

SORACOM ArcをESP32のArduinoで動かすsoracom-arc-esp32-arduinoのご紹介

こんにちは、株式会社ソラコムでソフトウェア等のエンジニアをやっているmoznionです。
普段ブログには書かない所属を宣言するのはなぜか。それはこれが株式会社ソラコム Advent Calendar 2021 17日目の記事だからです。というわけで記事が書かれます。

前日のアドベントカレンダー16日目は @0x6b さんによる soratun を改造して AWS Lambda から簡単に SORACOM Arc を使ってみました でした。そして本記事もSORACOM Arcの記事になります。連チャンしていて景気が良いですね。


表題の通り、SORACOM ArcをESP32のArduinoで動かすためのライブラリであるsoracom-arc-esp32-arduinoをご紹介します。
SORACOM Arcというサービスはザックリ説明すると「WireGuardを使ってデバイスとSORACOMプラットフォームとの間にトンネルを張り、そのトンネル越しに通信することでSORACOMのサービスを使えるようにするサービス」です。つまり従来は必須であって携帯電話回線を使わなくてもSORACOMサービスが使えるという接続サービスになります。

github.com

なお本ライブラリはArduinoLibrary Registryにも登録してありますので、ArduinoのLibrary Managerから利用が可能です。

f:id:moznion:20211217095811p:plain


使い方は至って簡単で、このライブラリ (SoracomArcESP32.h) をincludeしてちょっとしたコードを書いてやることですぐにSORACOM Arcを利用することができます。

#include <SoracomArcESP32.h>
#include <WiFi.h>
#include <HTTPClient.h>

#define BAUDRATE 115200

static SoracomArc soracomArc;

void setup() {
  Serial.begin(BAUDRATE);
  while (!Serial)
    ;

  WiFi.begin("__YOUR_AP_SSID__", "__YOUR_AP_PASSWORD__");
  while (!WiFi.isConnected()) {
    delay(1000);
  }

  configTime(0, 0, "ntp.nict.jp", "time.google.com", "pool.ntp.org");

  std::string soracomAPIAuthKeyID = "keyId-__YOUR_SORACOM_API_KEY_ID__";
  std::string soracomAPIAuthKey = "secret-__YOUR_SORACOM_API_AUTH_KEY__";
  std::string caCert = "-----BEGIN CERTIFICATE-----\n"
                       "...\n"
                       "__CA_CERTIFICATION_STRING__\n"
                       "...\n"
                       "-----END CERTIFICATE-----\n";

  SoracomAPI soracomAPIClient(soracomAPIAuthKeyID, soracomAPIAuthKey, caCert);

  std::string simID = "__YOUR_SIM_ID__";
  while (!soracomArc.activate(soracomAPIClient, simID)) {
    delay(5000);
  }

  log_i("setup finished");
}

void loop() {
  HTTPClient http;

  http.begin("http://metadata.soracom.io/");
  http.setConnectTimeout(1000);
  http.setTimeout(1000);
  int statusCode = http.GET();

  String payload = http.getString();
  log_i("http status: %d", statusCode);
  log_i("%s", payload.c_str());

  http.end();

  delay(30000);
}

このコードは以下のシークエンスに従って動作します。

1. Wi-Fiに接続する
2. NTPを用いてシステムの時刻合わせをする
3. SORACOM APIのauthKeyIdとauthKeyを組み合わせて認証を行ない、apiKeyとapiTokenを取得する
4. 上記のapiKey/apiTokenを用いて使用するArcのSIMについてセッションの再生成を行う。この再生成時にWireGuardのcredentialsを動的に発行する
5. 生成したArcのセッション情報を引いてくる
6. WireGuardのcredentials情報とセッション情報に基いてWireGuardトンネルを張る
7. Arcが有効に。上記の例では30秒ごとにメタデータサービスにアクセスし、SIMの情報を取得している

という感じです。コード中のCA certについては https://api.soracom.io に対するものを良い感じに埋め込んでください。


内部的には@cinimlさんのWireGuard-ESP32-Arduinoを使ってWireGuardの接続を行なっています。このライブラリの技術詳細についてはSORACOM Advent Calendar 2021 7日目の@cinimlさんによるWireGuard for ESP32の実装的なところという記事でつまびらかにされているので、併せて是非ご参照ください。


もちろん、WireGuardの設定情報をそのまま利用して利用することも可能です。その例はこちらにございます: https://github.com/soracom-labs/soracom-arc-esp32-arduino/tree/main/examples/boot-static-wg-credentials


既知の課題としては、SORACOM APIを使う方法にせよWireGuardの設定をそのまま使う方法にせよセンシティヴな情報をボード上で取り扱う必要が出てくるため、それらをセキュアな領域に置くあるいは何らかセキュアな方法で操作する必要があります。細心の注意を払っていただければと思います。


というわけでsoracom-arc-esp32-arduinoのご紹介でした。詳しい使い方についてはREADMEexamplesをご参照ください。
ESP32 ArduinoでSORACOM Arcが動作するとプロトタイピング等で活躍してくれそうで夢が広がりそうです。ぜひご利用ください!

おまけ

PlatformIOを使ってライブラリ開発してたんですけど、これ死ぬほど便利な一方ハマりどころがちらほらあるのでそういった落し穴について記録しておきます。

platform = espressif32を指定していると古いバージョンが降ってくる

これ新しいバージョンを明示しないと古いのを使い続けてしまいます。結構ドラスティックにプログラミングインターフェイスが変わってたりするんで、バージョン間違えると確実にハマります。

github.com

ここから欲しいバージョンを探してきて、 platform = espressif32@3.4.0 みたいな感じで指定してあげると良いです。

C++17使いたいんだけど……

という時はこのように platformio.ini に記述してやると良いです。

build_flags =
  -std=gnu++17
build_unflags =
  -std=gnu++11

残念ながら gnu++20 (C++20) はまだ対応していない様子。std::format()、とにかくバキバキに使いたんだが……

Go Genericsを使ってgo-optionalを書いた / Go Generics感想

Go Genericsがどんなもんか試してみたかったので、これを使ってOptionの実装を書いてみました。

github.com

基本的な使い方としてはSynopsisを読んでもらえばわかると思いますが、ユーティリティとしては

  • IsSome()
  • IsNone()
  • Take()
  • TakeOr()
  • TakeOrElse()
  • Filter()
  • Map()
  • MapOr()
  • Zip()
  • ZipWith()
  • Unzip()
  • UnzipWith()

あたりを取り揃えております。examplesも併せてご覧いただくとおおよその使い方の雰囲気が掴めると思います。
利用のためにはまだunstableな最新版 (go1.18) を使う必要があるので、gotipとかを使って新しい処理系を引っぱってくる必要があります。

で、GoのGenericsを使ってみた感想としてdefault valueとかconstraintとかどうするんだよと悪戦苦闘していたのですが、id:codehexさんの書かれた記事にほぼほぼ書かれていることを一通り終わった後に気付きました。

zenn.dev

で、概ね同じような所感を持ったのですが、それだけだと芸が無いので僕個人としてのGenericsを使ったときの感想について記しておきます。

メソッドに型パラメータが持てない

例えばこのようなメソッドについて考えてみましょう。

type Something struct {
}

func (s *Something) Echo[V any](v V) V {
	return v
}

なんとなく構文的にはvalidなように見えますが、これをコンパイルすると

./main.go:6:25: methods cannot have type parameters
./main.go:6:26: invalid AST: method must have no type parameters

というエラーが吐かれます。メソッド (つまりレシーバがある) 時にはそのメソッドに対して型パラメータが付けられないということのようですね。
例えば以下のようにメソッドにすることをやめるとコンパイルは通るようになります。

func Echo[V any](s *Something, v V) V {
	return v
}

こういった実挙動のため、go-optionalでは Map() 等の関数実行時の型パラメータに依存するユーティリティについてはメソッドではなく関数として実装しています。
ref: https://github.com/moznion/go-optional/blob/b0ada9baa88672d2f0fc11a1487842da170b5f92/option.go#L83

なんとかジェネリクスのconstraintをtype assertionで分岐させられないか

例えば、go-optionalでは「Option[T]の値が、Tの或る値を持っているか」を確認するメソッドである Contains(v T) を実装しようと思ったのですが

type Option[T any] struct {
	value T
	exists *struct{}
}

func (o Option[T]) Contains(v T) bool {
	if o.IsNone() {
		return false
	}
	return v == o.value
}

というふうに素朴に実装すると ./option.go:20: invalid operation: cannot compare v == o.value (operator == not defined on T) という風なコンパイルエラーが出てきます。そりゃそうだ。

これについては上のcodehexさんの記事にもありましたが、現状あるconstraintを満足させるためには別の型 (型パラメータ) を付けて実装する必要があります。
たとえばこれだと通る。

type ComparableOption[T comparable] struct {
	value T
	exists *struct{}
}

func (o ComparableOption[T]) Contains(v T) bool {
	if o.IsNone() {
		return false
	}
	return v == o.value
}

あくまで例えばの話ですが、anyについてtype assertionのようにconstraintのチェックができれば便利だよな〜と思い、例えば以下のように……

// o is a value of Option[T any]
v, ok := o.(Option[T comparable])

とはいえまーこれあんま筋良くない気がするな、コンパイル時のtype erasureとかもあるだろうし……

型パラメータにタプルを持たせられない

例えばこういう書き方はできません。

func Zip[T, U any](opt1 Option[T], opt2 Option[U]) Option[(T, U)] {
	...
}

まあそういうもんですよ、と言われたらその通りなのでgo-optionalでは Pair という構造体を用意して Option[Pair[T, U]] を返却するようにしました。

ref: https://github.com/moznion/go-optional/blob/b0ada9baa88672d2f0fc11a1487842da170b5f92/option.go#L108

pkg.go.dev がgodocを読み込んでくれないっぽい?

https://pkg.go.dev/github.com/moznion/go-optional

stableなruntime versionではまだ未定義の構文が多く含まれているからか、godocが解釈できないっぽい? pkg.go.dev に一向にドキュメントが出てこなくて地味に困っています……

[追記] バイナリのフットプリントについて

Genericsを使ったときと使わないときでビルドされたバイナリのフットプリントに差が出るのだろうか、という素朴な疑問があったので雑に比較。
環境は go version devel go1.18-4083a6f Wed Nov 17 14:10:29 2021 +0000 darwin/amd64

genericsなし:

package main

import "fmt"

type SomethingStr struct {
        v string
}

type SomethingInt struct {
        v int
}

func main() {
        s := SomethingStr{
                v: "foo",
        }
        i := SomethingInt{
                v: 123,
        }

        fmt.Println(s)
        fmt.Println(i)
}

genericsあり:

package main

import "fmt"

type Something[T any] struct {
        v T
}

func main() {
        s := Something[string] {
                v: "foo",
        }
        i := Something[int] {
                v: 123,
        }

        fmt.Println(s)
        fmt.Println(i)
}

結果:

-rwxr-xr-x   1 user  user  1846368 11 18 11:11 generics*
-rwxr-xr-x   1 user  user  1846368 11 18 11:27 no_generics*

1バイトも変わらぬ結果に。面白いですね。とはいえこれは最適化戦略やコードに依存する気がするのでもっと複雑なコードを書くと変わってくる可能性がある気が……あんま信用しないでください。



だいたいそんな感じでした。手習い的にGoのGenericsを使ってみましたが、けっこうシンプルかつ現時点でも普通に使えるな、という肌触りです。
ここは型の推論が効くだろ (他の言語だと効きそうに見える)、と思って型パラメータの記述をサボると「型パラメータを明示しろや」とコンパイラに怒られたりもするわけですが、まあそこはgoらしさ・シンプルさを優先したということなのでしょう。機能と実装複雑性・コンパイルタイムのトレードオフという見方をしました。

MySQLのJSON Data Typeの値に対し、明示的なキャスト無しに `BETWEEN`, `IN()`, `GREATEST()`, `LEAST()` を使ってはならない

表題の通り、MySQLJSON Data Typeの値に対しては、明示的なキャスト無しBETWEEN, IN(), GREATEST() そして LEAST() を使ってはいけません。

本記事はこれに係る話題で、id:sugyan さんに Slack で相談を受けて「僕もそれハマったことあるな」と調べたところ以下のドキュメントに辿りつきました。

dev.mysql.com

これはMySQL 8.0のJSON Data Typeに関するドキュメントですが、このドキュメントの Comparison and Ordering of JSON Values というセクションに

The following comparison operators and functions are not yet supported with JSON values:

  • BETWEEN
  • IN()
  • GREATEST()
  • LEAST()

A workaround for the comparison operators and functions just listed is to cast JSON values to a native MySQL numeric or string data type so they have a consistent non-JSON scalar type.

と明記されています。これらの演算子・関数については「未対応」ということみたいですね。


これがどういうことか実例を見てみましょう。

前提として、JSON_EXTRACT() を使って取り出した値はその値自体がJSON Data Typeとして扱われます。

mysql> SELECT JSON_TYPE(JSON_EXTRACT('{"a":101,"b":99}', '$.a'));
+----------------------------------------------------+
| JSON_TYPE(JSON_EXTRACT('{"a":101,"b":99}', '$.a')) |
+----------------------------------------------------+
| INTEGER                                            |
+----------------------------------------------------+
1 row in set (0.00 sec)

なおこの反例として、仮にこの値をキャストしてみると JSON_TYPE() はエラーを返却します。

mysql> SELECT JSON_TYPE(CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.b') AS SIGNED INTEGER));
ERROR 3146 (22032): Invalid data type for JSON data in argument 1 to function json_type; a JSON string or JSON type is required.

さて、このJSON Data Typeの値について単純な比較演算子を使って数値比較をしてみましょう。比較演算子についてドキュメントには

JSON values can be compared using the =, <, <=, >, >=, <>, !=, and <=> operators.

とあるので正常に動きそうです。

mysql> SELECT JSON_EXTRACT('{"a":101,"b":99}', '$.a') > JSON_EXTRACT('{"a":101,"b":99}', '$.b');
+-----------------------------------------------------------------------------------+
| JSON_EXTRACT('{"a":101,"b":99}', '$.a') > JSON_EXTRACT('{"a":101,"b":99}', '$.b') |
+-----------------------------------------------------------------------------------+
|                                                                                 1 |
+-----------------------------------------------------------------------------------+
 1 row in set (0.00 sec)

このSQLJSON Data TypeがINTEGERの値である 10199 について比較したもの、すなわち 101 > 99 ですが、1が返却されているので正しく動作していそうです。

一方で同じ値に対し GREATEST() を利用するとどうなるでしょうか。

mysql> SELECT GREATEST(JSON_EXTRACT('{"a":101,"b":99}', '$.a'), JSON_EXTRACT('{"a":101,"b":99}', '$.b'));
+--------------------------------------------------------------------------------------------+
| GREATEST(JSON_EXTRACT('{"a":101,"b":99}', '$.a'), JSON_EXTRACT('{"a":101,"b":99}', '$.b')) |
+--------------------------------------------------------------------------------------------+
| 99                                                                                         |
+--------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

おやおや、期待した値は 101 なのですが 99 が返却されています。
これは辞書順なのでしょうか……? というところから暗黙的なキャストやその他もろもろを疑っていたのですが、結論としては冒頭の通り「未対応」なので使ってはいけないということのようです。

というわけで、提案されているワークアラウンドを実行してみましょう。JSON INTEGERをSIGNED INTEGERに明示的にキャストして試してみます。

mysql> SELECT GREATEST(CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.a') AS SIGNED INTEGER), CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.b') AS SIGNED INTEGER));
+--------------------------------------------------------------------------------------------------------------------------------------------+
| GREATEST(CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.a') AS SIGNED INTEGER), CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.b') AS SIGNED INTEGER)) |
+--------------------------------------------------------------------------------------------------------------------------------------------+
|                                                                                                                                        101 |
+--------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

はい、期待通りの結果になりましたね。良かった良かった。

そしてこの挙動について、実はMySQL側も警告メッセージを残していることがわかります。

mysql> SHOW WARNINGS;
+---------+------+----------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                            |
+---------+------+----------------------------------------------------------------------------------------------------+
| Warning | 1235 | This version of MySQL doesn't yet support 'comparison of JSON in the LEAST and GREATEST operators' |
+---------+------+----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

--show-warnings を有効にしておくとクエリの実行時に気付くこともできますが、この手のクエリを実行した時に失敗させる方法は見付かりませんでした。strictモードとかを色々いじってはみたのですが。なにか良い方法はあるんでしょうか?

結論

MySQLJSON Data Typeを使う時は気を付けましょう。

macOSでDocker Desktopをアンインストールしてdocker-cli + docker-machineで動かすようにする

www.docker.com

Docker Desktopがここ最近活発に開発されているというか、かなり見た目がオシャレになってきてて「ヤル気あるな〜」と思って眺めていたのですが、なるほど有料化するということなのですね。

Docker Desktop remains free for personal use, education, non-commercial open source projects, and small businesses (fewer than 250 employees AND less than $10M USD in annual revenue).
Commercial use of Docker Desktop in larger enterprises (more than 250 employees OR more than $10 million USD in annual revenue) requires a Docker Pro, Team or Business subscription for as little as $5 per user per month.

実際、こういうところに注力して利益を得ていくというのは良いことだと思います! 頑張れdocker!

それはそうとしてDocker Desktopの機能をあまり使ってこず、実際のところcliでdockerが操作できればよい、つまりdocker engineが動けば良いという感じだったので、docker-machineを使ってローカルでdockerデーモンのためのVMを動かし、ホストからそのVMcliを繋いでdockerを使うという方法をやっていくこととします。どことなく懐しいですね。podman使うとかでも良かったんですけど、ひとまずdocker cliでやります。

まずDocker Desktopをアンインストールする。

f:id:moznion:20210901111820p:plain

"Troubleshoot" メニューを開きます。この虫マークは "Troubleshoot" というメニューだったのですね。バグレポートボタンだと思ってた……

f:id:moznion:20210901111903p:plain

アンインストールしましょう。これで完了です。

そしてdocker cliとdocker-machineを入れる。

$ brew install docker docker-machine

で、docker-machineを使ってローカルにVMを立ててそこでdockerデーモンを動かすこととする。このとき、ローカルマシンにあらかじめVirtualBoxがインストールされている必要があります。
やりかたとしては右のページに従うと良い: Get started with Docker Machine and a local VM | Docker Documentation
おおざっぱに書くと以下のようなかんじ。

$ docker-machine create --driver virtualbox default
$ echo 'eval "$(docker-machine env default)"' >> .bash_profile

そして docker ps などと打ってみてちゃんとレスポンスが返ってくると成功です。


Docker Desktopで動かしていた時と比較するとなにか制限等があるかもしれませんがそれは遭遇した時に考える、あるいは遭遇したらLinux Desktopに乗り換えるなどを検討していきたいと思います。実際Linux Desktopへ移行していきたい……

[追記]

良くみてみるとdocker-machineの開発が停滞しているように見えるので、そこは懸念ポイントと言えそうですね……
github.com
まあなんかあったら手でVM運用していきましょう。俺たちにはそれができるはずだ。

[追記]

Gitlabがforkしたdocker-machineがあるとのこと。

IntelliJ IDEAのsbt pluginがPrivateなGitHubのmaven repositoryに上げたライブラリを解決してくれないとき

表題の件で、sbtの依存解決コンポーネントが突然HTTP Status 400を返却してきてなにをやっても無駄、一生解決してくれない、みたいなことが原因不明ながら周期的に起きていて、そういうときにどうすれば良いかというと、

$ GITHUB_REGISTRY_TOKEN="YOUR-TOKEN" open /Applications/IntelliJ\ IDEA.app/

みたいな感じでトークンを環境変数で与えながらIDEAを起動してやるととりあえず動く……が、まったく本望ではない。
実際IDEAの環境変数を適切に設定すれば動くでしょ、とは思いつつも環境変数どこで設定すりゃ良いんだよ、つーかそもそもsbt pluginでは環境変数を設定することができず本当につらい、mavenやgradleのpluginではできるのだが……という気持ちでいっぱいです。


忙しいときにこういうの踏むと本当に大変ですね。以上です。

Elasticsearchの"index.mapping.total_fields.limit"を監視する話

Elasticsearchには index.mapping.total_fields.limit という設定があり、これは何かというと「1つのindexあたりが保存できるフィールドの上限数」を表現しており、この上限に触れると Limit of total fields [1000] in "your_index" index has been exceededのようなエラーが発生してindexができなくなります。

この上限への対症療法としては先人たちが示している通り様々あるのでそちらに譲り *1、本記事ではこの index.mapping.total_fields.limit を監視する方法について考えていきましょう。上にも書きましたが、この上限にヒットするとそのindexに対するindexingが完全にストップするのでマズいんですよ……
(もちろん、Elasticsearchのパフォーマンスを考えると1つのindexに大量のフィールドを生やすのは良くないし、そもそも理性的な使いかたをしていれば起きないことだとは思うのですが……まあ実際にElasticsearchを運用していると色々なことがありますね)


というわけで、johtaniさんに色々教えてもらったことについて以下に記します。いつもありがとうざいます。

なるほど、コードを読んでみると確かにシンプルなリミットチェックを行っているだけっぽい。

stackoverflow.com

ハハア、なるほど。APIを叩いてそのindexのフィールド数を回収することで現在のステータスを回収しよう、ということですね。


というわけで以下のようなスクリプトを一定周期で流してメトリクスとして回収し、可視化およびアラートを設定することで現状なんとかしております、というお話でした。

for idx in $(curl -Ss "${ES_ENDPOINT}/_cat/indices?format=json" | jq -r .[].index); do
  num_of_fields=$(curl -Ss "${ES_ENDPOINT}/${idx}/_field_caps?fields=*" | jq '.fields | length')
  echo "$idx: $num_of_fields"
  store_metric "$idx" "$num_of_fields"
done