yak shaving life

遠回りこそが最短の道

Spring Data RedisでZPOPMIN(MAX)を使う

RedisにはSorted Setというデータ型があって、ユーザランキングみたいなものを作るときに便利だったりする。このSorted Setに対する操作はZADDとかZRANKのようにZから始まるものとなっている。

Sorted Setから一番スコアが低い(高い)ものを破壊的に取り出すZPOPMIN(ZPOPMAX)というコマンドがあり、Spring Data Redisでこれらを使いたかったのだがやり方がわからず、調べてもなかなか出てこないので苦労した。ので、解決までの道のりをメモ。やり方だけ知りたい人は最後の方のサンプルコードを見てください。検証に使っているSpring Data Redisのバージョンは2.5.6で、Javaは11。Redis ConnectorはLettuce。

まず、サポートされているコマンドは以下のように利用できる。

ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("ranking", "user1", 100);
zSetOperations.add("ranking", "user2", 200);
Set elements = zSetOperations.range("ranking", 0L, 1L);

System.out.println(elements);
// 出力 => [user1, user2]

zSetOperations.addZADDzSetOperations.rangeZRANGEコマンドを実行してます。簡単ですね。

ところがZSetOperationsクラスにはpopMinとかpopMaxみたいなメソッドがありません。こいつは困った。色々調べたけどよくわからない。そうこうしているうちに以下のissueに辿り着いた。

github.com

要は次くらいのバージョンで対応してZPOPMIN, BZPOPMIN, ZPOPMAX, BZPOPMAX, ZMSCOREが使えるようになるとのこと。へー。でも僕は!今やりたいんです!どうすりゃいいの!と思っていたら興味深いコメントが。

When using Lettuce, you need to provide a CommandOutput to capture the Redis response. For [B]ZPOP[MIN|MAX] that's new ScoredValueOutput<>(ByteArrayCodec.INSTANCE) (via execute(String command, @Nullable CommandOutput commandOutputTypeHint, byte[]... args)).

Spring Data Redis doesn't have yet command output hints for these commands.

なるほど、要はRedisTemplateではできないからLettuceのexecuteコマンドでやれってことね。ふむふむ。というわけでリファレンスを見る。

docs.spring.io

なるほどわからん…。数時間溶かしてなんとかできたのが以下。各行に何やってるかコメント書いてます。

 // ここはさっきとほぼ同じコード
ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("ranking", "user1", 100);
zSetOperations.add("ranking", "user2", 200);
Set elementsBefore = zSetOperations.range("ranking", 0L, 1L);
System.out.println("Ranking before ZPOPMIN : " + elementsBefore);
// => Ranking before ZPOPMIN : [user1, user2]

// LettuceConnectionを取得。キャストするのがミソ。キャストせずRedisConnectionのままだとexecuteが通らない
LettuceConnection conn = (LettuceConnection) redisTemplate.getRequiredConnectionFactory().getConnection();

// 実行。第2引数がミソ。戻り値はObject型
Object resultObject = conn.execute("ZPOPMIN", new ScoredValueOutput<>(ByteArrayCodec.INSTANCE), "ranking".getBytes());

// executeの戻り値はScoredValue型にキャストできる。ここの型を変えれば他のコマンドにも対応できそう
ScoredValue scoredValue = (ScoredValue) resultObject;

// getValueでvalueが取れるが、戻り値はObject型
Object valueObject = scoredValue.getValue();

// Stringを入れてるならStringに変換できる。直接はキャストできないのでbyte配列にしてから
String value = new String((byte[]) valueObject);
        
// scoreはdoubleで入ってるのでキャストとかは不要
double score = scoredValue.getScore();
        
// valueとscoreが取れてめでたしめでたし
System.out.println("Popped element : value = " + value + ", score = " + score);
// => Popped element : value = user1, score = 100.0

// POPしたのでSorted Setの中身が一つ減っていることを確認
Set elementsAfter = zSetOperations.range("ranking", 0L, 1L);
System.out.println("Ranking after ZPOPMIN : " + elementsAfter);
// => Ranking after ZPOPMIN : [user2]

conn.close();

このサンプルコードがググってさっと見つかればこんなに苦労することもなかったのになあ。。 ということで一応デモアプリをGithubにあげておいた。誰かの役に立てばいいけど。

github.com

追記:connectionをcloseしてなかったのでconn.close()を追加。

東京都現代美術館で GENKYO 横尾忠則 などを観た

この前 The Artists にも行ったのだが、GENKYO はもう規模が桁違いだった。とにかく横尾忠則作品がものすごい数展示されていた。活動期間がとても長い方なので、年代ごとに作風が全然違う。それがうまくカテゴリ分けされていて素晴らしい展示だった。

横尾忠則作品といえば、若い頃のアングラっぽい派手な色と女性が特徴的なポスターだったり、書き込み量が異常に多くて宗教や西洋美術のモチーフが大量に詰め込まれたような難解かつ複雑な絵画なんかは観たことがあったけれども、それ以外にもY字路シリーズとか「タマ、帰っておいで」とかまあ色んな作品があった。量だけはかなりたくさん観た気がするので、少しずつ横尾作品が分かるようになっていきたい。

その後はマーク・マンダースの特別展示を見たり、コレクション展を見たり。マーク・マンダースは初めてじっくり観たけど、どういうコンセプトなのか不思議だ。立体は表現方法が色々ありすぎてカオスで面白い。本人のインタビューとか解説をもうちょっと見たいなあ。コレクション展の方でも色々あって面白かったけど、平田実の写真展示でハイレッドセンターの「首都圏清掃整理促進運動」があり、あっ、本田パピヨン先生の漫画*1で見たやつだ!という進研ゼミ的体験をしたりして楽しかった。

1日でかなり多くの作品が観れて良かった。都現美いいとこみんなで行こう。

iPadは教科書代わりになるのか

息子が学校の教科書をPDF化してiPadだけ持って登下校しているという話を見た。

togetter.com

本当か嘘かみたいなところはどうでもよくて、はてブコメントが面白い。特に否定派のコメントはある意味参考になる。人は変化を嫌う生き物なんだなーというのがよくわかるというか。リツコさん風に言うとホメオスタシスというやつだろうか。全自動洗濯機が発売された当初、「洗濯は愛情を込めて手でするもの、機械がやるなんてあり得ない」と言われなかなか受け入れられなかったという話を聞いたことがあるが、どの時代も同じという感じがするなあ。

iPadがあれば(紙の)教科書はいらないのかというとどうだろう。個人的な経験からすると紙の方が便利そうに感じるけれども、初めからiPadしかなければその環境で十分に育っていくのが人間だという気もする。教育は結果が出るのに時間がかかるし効果測定が難しそうなのでなんとも言い難い。まあでもクソ重い教科書の持ち運びは意味ないからやめた方がいいでしょうね。

それはさておき、少し前に買ったキングジムのフリーノのことを思い出した。自分ではあまり使っていないのだが、奥さんが試験勉強にガッツリ活用していて面白いなと思った。PDFの問題集を端末に入れて、そこに書き込んでいくという感じ。いくらでもコピーできるし書き直しもできるし検索もできるというので紙より便利そう。いつでも持ち運べるし。もしかしたら、教育用にはiPadみたいな汎用タブレットより電子ペーパーのデジタルノート端末の方が向いているかもしれない。あーでも動画とか見れないのはちょっときついか…iPadで動画や資料を見て、フリーノでノートを取る。みたいな感じが一番いいのかもしれないけど、そこまでリッチな環境を全学生に提供するにはまだまだかかりそうだ。教育のIT化も難しそうだ。いつか携わってみたいところではあるけどまあ茨の道だろうな…。

HomebrewでJmeterをインストールしたらちゃんと動かなかった

諸事情によりJmeterGUIを動かしたかったのでMacでのインストール方法を調べると、Homebrewにあるとのことだったのでインストールした。が、ちゃんと動かなかったのでやったことをメモ。

環境

macOS 10.15.13(Catalina), JavaはOpenJDK 11.0.4, MacBook Pro Early 2015。M1 Macの新しいやつほしい。

事象

brew install jmeterJmeterをインストールして、jmeterコマンドでGUIを実行。WARNING: package sun.awt.X11 not in java.desktopとかいうのが出てるからなんとなく嫌な予感がしたが、一部の機能が動かない。具体的には File -> Open を押しても何も起こらない。(何もできねえ…)

ターミナルを見るとこんなエラーが出力されている。

Uncaught Exception java.lang.IllegalAccessError: class com.github.weisj.darklaf.ui.filechooser.DarkFilePaneUIBridge$DetailsTableModel (in unnamed module @0x4c40b76e) cannot access class sun.awt.shell.ShellFolder (in module java.desktop) because module java.desktop does not export sun.awt.shell to unnamed module @0x4c40b76e in thread Thread[AWT-EventQueue-0,6,main]. See log file for details.

なるほどわからん。ログファイルを見ろと言われているので、カレントディレクトリに生成されているjmeter.logを見る。

2021-10-26 01:27:40,693 ERROR o.a.j.JMeter: Uncaught exception in thread Thread[AWT-EventQueue-0,6,main]
java.lang.NoClassDefFoundError: Could not initialize class org.apache.jmeter.gui.util.FileDialoger
        at org.apache.jmeter.gui.action.Load.doActionAfterCheck(Load.java:75) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.AbstractActionWithNoRunningTest.doAction(AbstractActionWithNoRunningTest.java:44) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.performAction(ActionRouter.java:87) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.lambda$actionPerformed$0(ActionRouter.java:69) ~[ApacheJMeter_core.jar:5.4.1]
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318) ~[?:?]
        at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:722) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:716) ~[?:?]
        at java.security.AccessController.doPrivileged(AccessController.java:399) ~[?:?]
        at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) ~[?:?]
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:741) ~[?:?]
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) [?:?]
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:90) [?:?]
2021-10-26 01:27:40,842 ERROR o.a.j.JMeter: Uncaught exception in thread Thread[AWT-EventQueue-0,6,main]
java.lang.NoClassDefFoundError: Could not initialize class org.apache.jmeter.gui.util.FileDialoger
        at org.apache.jmeter.gui.action.Load.doActionAfterCheck(Load.java:75) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.AbstractActionWithNoRunningTest.doAction(AbstractActionWithNoRunningTest.java:44) ~[
ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.performAction(ActionRouter.java:87) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.lambda$actionPerformed$0(ActionRouter.java:69) ~[ApacheJMeter_core.jar:5.4.1]
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318) ~[?:?]
        at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:722) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:716) ~[?:?]
        at java.security.AccessController.doPrivileged(AccessController.java:399) ~[?:?]
        at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) ~[?:?]
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:741) ~[?:?]
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) [?:?]
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:90) [?:?]

なるほどわからん(2分ぶり2回目)。とりあえずorg.apache.jmeter.gui.util.FileDialogerがクラスパスにないんだろうけどどうすればいいやら。

解決策

エラーメッセージ等でしばらくググって色々試したけどどうも解決できず。Javaのバージョン下げろとか上げろとか色々書いてあったので、jenvを入れて8とか17とかも試してみたけど変わらず。

こいつはちょっとヤクすぎる(yak shavingすぎる)なと感じ始めたので、別のアプローチを試すことに。[Jmeter公式]に行くとバイナリをダウンロードできるので、これをダウンロードしてみることに。その間にhomebrew版は消しておく。

大体以下のようなコマンドを打った。

$ brew uninstall jmeter
Uninstalling /usr/local/Cellar/jmeter/5.4.1... (2,645 files, 124.4MB)

# 一応Checksumを計算してみる。ちゃんと合ってるっぽい。
$ shasum -a 512 apache-jmeter-5.4.1.tgz
bfc0faa84769b58c1fd498417b3a5c65749f52226bd6e3533f08ca7ea4a3798bb8d2cbd7091b443dd6837f3cbea5565c3c18e6497b40bec95616bf44dfdf590d  apache-jmeter-5.4.1.tgz

$ tar xzvf apache-jmeter-5.4.1.tgz
(出力略)

$ cd apache-jmeter-5.4.1/bin/

$ sh jmeter.sh
================================================================================
Don't use GUI mode for load testing !, only for Test creation and Test debugging.
For load testing, use CLI Mode (was NON GUI):
   jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]
& increase Java Heap to meet your test requirements:
   Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in the jmeter batch file
Check : https://jmeter.apache.org/usermanual/best-practices.html
================================================================================

お、Homebrew版で出ていたWARNINGが出てない!これは期待!

ということで、実際に立ち上がったGUIではFile -> Openも動きました。エラー出力もなし。めでたしめでたし。

Homebrew版のエラーを解消する方法は結局分からなかったけど、まあ動いたからよしとしよう。今日もyakをshavingしたので人生を生きているなあ。

世界史を変えた植物 を読んだ

なぜ読もうと思ったか

家族に勧められて。

どのような本か

Amazonの紹介文が面白いのでそのまま引用します。

私たちが知っている歴史の裏側で、植物が暗躍していたら……

古代の文明が植物によって生みだされたとしたら、どうだろう。 近代社会を生みだした産業革命の原動力となったものが、ある植物であったとしたらどうだろう。 世界一の大国であるアメリカの隆盛の陰に、ある植物があったとしたらどうだろう。 アメリカの南北戦争やイギリスと清とのアヘン戦争の陰に、植物の陰があったとしたらどうだろう。 人類の歴史は、植物の歴史でもある。 本書では、そんな植物から見た世界の歴史をのぞき見てみたいと思う。 (「はじめに」より)

書評というか感想

すごい面白かった。

一章につき一つの植物という章立てになっていて、歴史上重要な出来事と植物の関係性について解説がある。植物についての豆知識的な面白さもあるし、植物や農業が人類の発展においていかに重要なファクターなのかが丁寧に説明されていて勉強になる。

人間が植物を育てているのか、はたまた我々は植物に育てさせられているのか…?という若干オカルトめいた語り口もコミカルで面白い。

植物/農業が富を生み、国を作ったという視点は今までなかったけど言われてみれば確かにという感じ。肥沃な土地より痩せた土地で農業が発展するとか、農業と奴隷と戦争の関係、唐辛子と胡椒はなぜ両方ペッパーというのか、なぜヨーロッパで紅茶が流行ったのか、コーヒーを広めたのはイスラム教徒、ドイツ料理にジャガイモが多い理由、トウモロコシは宇宙から来た(?)等々、とにかく興味深い話題のオンパレードだった。

読みやすく、面白く、勉強にもなる非常にオススメな本です。

著者の稲垣栄洋氏は農学部の教授らしい。この人の他の書籍も読んでみたい。

KAWS TOKYO FIRST と 横尾忠則 The Artists に行った

順番的には先に21_21に行って横尾忠則 The Artistsを観てからKAWS TOKYO FIRSTの方へ行った。六本木はええところですなあ。

横尾忠則氏についてはあまりよく知らないのだけれども、若い頃のアングラ演劇ポスターみたいな印象が強い。毒々しい色の裸体の女みたいな。その程度しか知らずに観に行ったのだが、なかなか良かった。著名なアーティスト、写真家、デザイナーなどの肖像画がずらっと並んでいるのだけど、全て違った画風で書かれていて面白かった。北野武宮島達夫とかちょくちょく日本人も居た。ここにいる人たち全員わかったら相当アートに詳しいと言えるだろうな〜と思いながら眺めていた。僕は10分の1も分からなかった。無知。

KAWS TOKYO FIRSTは大変賑わっていて、長い入場列ができていた。当日券買えるかわかりませんとまで言われた。買えたけど。こちらは作品数も多いし大きな作品もあって分かりやすく凄かった。KAWSについてもあまりよく知らなかったのだけど、彼のキャラクターにはCompanion、BFF、Chumという名前があるようだ。BFFて。Best Friend Foreverて。個人的にはBackend For Frontendを真っ先に想像するので職業病だなと思った。

KAWSは大衆的キャラクターを多用しているので子供も喜んではしゃぎ回っていた。珍しく写真撮るときのポーズとかもしてくれたしキャラクターは偉大だなあ。アートと一般人(僕みたいな)の接点を増やしてくれるという意味でも良い。しかし、こういうのって権利関係とかどうなってるんだろう。分からんことだらけで楽しいなあ。

Pixel 6 祭りに乗るか否か

Pixel 3 を使っている。Pixel 3 は本当に素晴らしいデバイスで、未だにほとんど不満がない。いや、Google Storeで買った後すぐに値下げしやがったことについてだけはハラワタ煮えくりかえってますけどね。許すまじ。2万ですよ2万。いきなり2万値下げっておかしくない?値下げの直前に買ったんですけど?なんで先に買ったユーザになんの保証もないじゃコラ。せめてなんかクーポンくれるとかさあ、なんか…あるやん?何にも無しってのはちょっと…辛いやん?

というのはさておき、デバイス的には本当に素晴らしくて、ちょっとメモリ容量が少ないのとカメラのメモリ消費が異常なのでカメラを開くと全てのアプリがkillされてしまう*1けどまあ大して困っていない。動作の軽快さ、カメラの画質、背面指紋センサ、本体のサイズ感、質感、見た目。どこをとっても個人的には歴代ベストに近い端末だと思っている。

いるんですが、電池の消耗だけはどうしようもなく一日外にいると厳しい感じになってきたのと、電源ボタンがイカれ始めていてたまに押しっぱなし状態になってしまい勝手に再起動を繰り返す困ったちゃんなのでそろそろ買い替え時期かなと。Pixel 5aが出たときにこれは買いかなーと思っていたんだけど、割とすぐPixel 6の噂が出始めたのでどうすっかなーというところ。

Pixel 6はかつてないくらいマーケティングが激しくて盛り上がっているように見える。リーク記事とかも多すぎて故意にリークしてるとしか思えないレベル。しかしGoogle Tensorとやらがどのくらい凄いのか分からない、というか不安の方が大きい。フラッグシップ端末を大枚叩いて買ってまで人柱になりたくないなあ。あとはPixel 3 値下げの件もあり、発売してすぐ素直に手が出せないというのもある。あれは…本当にトラウマなんですよ…。

というわけで、このビッグウェーブに乗ってPixel 6買っちまうか、でも正直5aで十分な気もするんだよなーというので悩んでいる。5aはワイヤレス充電できないこと以外は良さそうだけどなあ。まあ6が発売するまでは様子見かなー。あと身近な人がiPhoneユーザばかりなのでPixel話ができないのが悲しい。日本Pixelユーザ会(JPUG)みたいなものが必要とされている…。いや、されてないか。とりあえずPixel 6、要チェックなのでAndroidユーザの皆さんは楽しくお祭りに参加しましょう。ワイワイ。

*1:rebuild.fmでもそのような話があった気がする