R の dplyr みたいなデータ操作ができる kotlin ライブラリである krangl でデータの unpivot をしてみる。
val df = dataFrameOf(
"product", "error", "warn"
)(
"CMS", 400, 343,
"Admin", 534, 834,
)
df.gather("product", "n", listOf("error", "warn"))
.print()
krangl はドキュメントがあんま検索に引っかからないが、dplyr でできることはだいたいできるので、krangl でどうしたらいいんだろう。。ってなったときは dplyr でどうしたらいいんだろう?という気持ちでググって見るのが良さそう。
ref. https://holgerbrandl.github.io/kotlin4ds_kotlin_night_frankfurt//emerging_kotlin_ds_ecosystem.html#1
モニター3枚ほど並べていたのだが配線がぐちゃぐちゃすぎてうんざりしたので、ウルトラワイドモニター一枚にしてみた。
かなりポートがたくさんついているので、これに接続するだけでほとんどの接続が集約されていて、快適です。USB ハブ機能があるのが便利。デスクが広く使える。
色々つないでたんですが、いまは↑のあたりをつないでいます
↓のように、非常に大きい実行ファイルがあったとする。
-rwxr-xr-x 1 tokuhirom tokuhirom 30M Jun 23 00:37 /tmp/thrift-linux-x86_64*
こういう場合には file コマンドを実行すると良い。
> file /thrift-linux-x86_64
thrift-linux-x86_64: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=f12655e32dd035a15ec1b40b181c8bcc76c55e4e, with debug_info, not stripped
ここで気になるのは not stripped
というところである。これは、シンボルテーブルがついていることを表している。シンボルテーブルは、デバッガでデバッグするときには便利だが、そういう用途が想定されない場合には不要である。
> strip thrift-linux-x86_64
> ls -lah thrift-linux-x86_64
-rwxr-xr-x 1 tokuhirom tokuhirom 3.7M Jun 23 00:42 thrift-linux-x86_64*
strip することで、30MB → 3.7MB と 8割程度の容量が削減される。
ref.
昇降式デスクというと、Flexispot が有名なのだが、Amazon で買えて一人でも古いデスクの回収もやってくれて、新しいデスクの組み立ても代行してくれるものという条件で探したらこれになった。
そこまで天板にこだわりないので、板を自分で選ぶとかめんどくさいなっていう感じ。
Android は Launcher を切り替えて、全く違うルックアンドフィールを体験できるのが強みの一つだと思う。
色々な Launcher を試してきたが、昨日見つけた AIO Launcher ってやつが最高便利なので紹介したい。
イメージとしては、iOS の左側の画面みたいな widget list だけのランチャーというのが近いです。その中にアプリリストウィジェットを埋めて使う感じ。
Notification が home の中にそのまま表示されるというのが新鮮。Notification いちいち開くの、めんどくさかったんですよね。
ウィジェットをホームのウィンドウごとにうまいこと並べる、みたいなのも必要なくなって、スーッと縦にスワイプしていけば、一通りのウィジェットを閲覧できて、今日の分のブリーフィング終わり。って感じ。
あと、ちょっとわかりにくいんだけど、AIO Launcher 自体の設定はフローティングな Search ボタンを長押しすればOK.
ウィジェットは Android widget ももちろん使えるんだけど(多分、有料版 feature だけど)、独自のウィジェット機構があって、lua で拡張できるっぽい。まだ試してないけど。
なお、これ多分日本語には対応してないです。
あと、Galaxy Z fold 3 みたいなフォルダブルの端末で画面を閉じたり開いたりしても画面が崩れないのもいいです。
コレはほんとにオススメです。
サーバー側がエラーを返しているが、その理由を返していないときにこのエラーになる。
内部コンポーネント同士の通信の場合、サーバー側での問題を propagate してしまうことも可能。
armeria-grpc なら -Dcom.linecorp.armeria.verboseResponses=true
すればよい。
https://armeria.dev/docs/server-grpc/#exception-propagation
Groovy でデータを処理する DSL
https://docs.groovy-lang.org/docs/next/html/documentation/#_ginq_a_k_a_groovy_integrated_query
def numbers = [0, 1, 2]
assert [0, 1, 2] == GQ {
from n in numbers
select n
}.toList()
のように書ける。
yabai という mac で動く tiling window manager を活用している。これと、同じ作者の skhd というショートカットマネージャーを組み合わせることで、ウィンドウの操作をほぼ全てキーボードで実施できるので鬼便利。
僕は ratpoison とかを長年使ってた経験あるので、この手の window manager は結構好き。
.yabairc を以下のように設定している。
# global settings
yabai -m config mouse_follows_focus off
yabai -m config focus_follows_mouse off
yabai -m config window_origin_display default
yabai -m config window_placement second_child
yabai -m config window_topmost off
yabai -m config window_shadow on
yabai -m config window_opacity off
yabai -m config window_opacity_duration 0.0
yabai -m config active_window_opacity 1.0
yabai -m config normal_window_opacity 0.60
yabai -m config window_border on
yabai -m config window_border_width 6
yabai -m config active_window_border_color 0xff775759
yabai -m config normal_window_border_color 0xff555555
yabai -m config insert_feedback_color 0xffd75f5f
yabai -m config split_ratio 0.50
yabai -m config auto_balance off
yabai -m config mouse_modifier fn
yabai -m config mouse_action1 move
yabai -m config mouse_action2 resize
yabai -m config mouse_drop_action swap
# general space settings
yabai -m config layout bsp
yabai -m config top_padding 0
yabai -m config bottom_padding 0
yabai -m config left_padding 0
yabai -m config right_padding 0
yabai -m config window_gap 0
yabai -m rule --add app="^System Preferences$" manage=off
yabai -m rule --add app="^Skitch$" manage=off
yabai -m rule --add app="^Zoom.*$" manage=off
echo "yabai configuration loaded.."
.skhdrc を以下のようにしている。
# focus window
alt - j : ~/dotfiles/bin/yabai-focus.pl
alt - k : ~/dotfiles/bin/yabai-focus.pl -r
# 画面を大きく3つのfloating window で占めて表示させる。
# 一番左に LINE アプリを表示。LINE アプリで本文が表示されるギリギリの横幅がこれ。
alt - 1: yabai -m window --toggle float && yabai -m window --grid 1:10:0:0:3:1
alt - 2: yabai -m window --toggle float && yabai -m window --grid 1:10:3:0:4:1
alt - 3: yabai -m window --toggle float && yabai -m window --grid 1:10:7:0:3:1
# swap window(よく使う)
shift + alt - h : yabai -m window --swap west
shift + alt - j : yabai -m window --swap south
shift + alt - k : yabai -m window --swap north
shift + alt - l : yabai -m window --swap east
# move window
shift + cmd - h : yabai -m window --warp west
shift + cmd - j : yabai -m window --warp south
shift + cmd - k : yabai -m window --warp north
shift + cmd - l : yabai -m window --warp east
# move window
shift + ctrl - a : yabai -m window --move rel:-20:0
shift + ctrl - s : yabai -m window --move rel:0:20
shift + ctrl - w : yabai -m window --move rel:0:-20
shift + ctrl - d : yabai -m window --move rel:20:0
# increase window size(よく使う)
shift + alt - a : yabai -m window --resize left:-20:0
shift + alt - s : yabai -m window --resize bottom:0:20
shift + alt - w : yabai -m window --resize top:0:-20
shift + alt - d : yabai -m window --resize right:20:0
# decrease window size
shift + cmd - a : yabai -m window --resize left:20:0
shift + cmd - s : yabai -m window --resize bottom:0:-20
shift + cmd - w : yabai -m window --resize top:0:20
shift + cmd - d : yabai -m window --resize right:-20:0
# rotate tree
alt - r : yabai -m space --rotate 90
# mirror tree y-axis
alt - y : yabai -m space --mirror y-axis
# mirror tree x-axis
alt - x : yabai -m space --mirror x-axis
# toggle desktop offset
alt - a : yabai -m space --toggle padding && yabai -m space --toggle gap
# toggle window fullscreen zoom
alt - f : yabai -m window --toggle zoom-fullscreen
# toggle window native fullscreen
shift + alt - f : yabai -m window --toggle float
# toggle window split type
alt - e : yabai -m window --toggle split
alt - o : yabai -m space --focus 2
alt - i : yabai -m space --move 2
# https://iter01.com/667321.html
で、ウィンドウの切り替えに以下のスクリプトを利用。
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP qw/decode_json/;
my $reversed = 0;
if (@ARGV == 1 && $ARGV[0] eq '-r') {
$reversed = 1;
}
my $json = `yabai -m query --windows --space`;
my @dat = @{decode_json($json)};
my @sorted = sort {
$a->{'is-floating'} <=> $b->{'is-floating'}
|| $a->{'frame'}{x} <=> $b->{'frame'}{x}
|| $a->{'frame'}{y} <=> $b->{'frame'}{y}
|| $a->{'frame'}{w} <=> $b->{'frame'}{w}
|| $a->{'frame'}{h} <=> $b->{'frame'}{h}
|| $a->{'id'} <=> $b->{'id'}
} @dat;
my $prev_is_focused = 0;
my @targets = 0..@sorted-1;
if ($reversed) {
@targets = reverse @targets;
}
for my $i (@targets) {
if ($prev_is_focused != 0) {
focus($sorted[$i]->{id});
exit 0;
}
if ($sorted[$i]->{'has-focus'}) {
$prev_is_focused++;
}
}
focus($sorted[$reversed ? -1 : 0]->{id});
exit 0;
sub focus {
my $id = shift;
system("yabai -m window --focus $id");
}
僕は、一般的なタイリングウィンドウマネージャー的な使い方ではなく、ちょっと変わった使い方をしている。
つかっているのがウルトラワイドモニターなので、フツウに bsp にすると画面を有効に使えてない気がするので、ちょいとした工夫をしている。
alt+1,alt+2,alt+3 で、ウィンドウをほぼ3分割して表示するようにしている。中央はやや広め。で、この3つはfloating windowとする。
alt+j, alt+k でのウィンドウの切り替えをするのだが、floating window を左から順番に切り替えて、その後 bsp を左から順番に切り替えるという設定にすることにより、ウィンドウの切り替えが速くなった。
ExponentialBackoffTest > test() FAILED
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class java.util.Random.
Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.
Java : 17
JVM vendor name : Eclipse Adoptium
JVM vendor version : 17.0.2+8
JVM name : OpenJDK 64-Bit Server VM
JVM version : 17.0.2+8
JVM info : mixed mode
OS name : Mac OS X
OS version : 12.3
こういうエラーがでていた。
ネットを探すと --add-opens
をつけろという記事が多いのだが、実はmock(Random.class, withSettings().withoutAnnotations())
とすれば良いようだ。
https://stackoverflow.com/questions/70993863/mockito-can-not-mock-random-in-java-17/70994194#70994194
問題が起きていた pyroscope-java については、PR を出して修正済み。
https://github.com/pyroscope-io/pyroscope-java/pull/26
spring-session-data-redis のデフォルト実装である RedisIndexedSessionRepository を利用すると以下の問題があります。
spring-security と併用した場合に TTL を設定していないキーが発生して、メモリがあふれる
spring-security を利用していなくてもメモリ仕様量が多い。
毎分0秒ごとにすべてのアプリケーション・サーバーからセッションデータ削除処理が走って、セルフ DDoS 状態になる(サーバー台数が多いときに顕著化する)。
これに対して、大規模サービス用のよりシンプルな実装として RedisSessionRepository が提供されている。これを用いれば、既存実装の問題点から開放されることができる。
検証
RedisIndexedSessionRepository
↓RedisIndexedSessionRepository で、ログインするまでに実行される redis のコードは以下のようになっている。一人がログインするだけなのに、めちゃくちゃ実行コマンドが多い。
1646916911.858574 [0 127.0.0.1:59916] "HMSET" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xc2\xe7" "maxInactiveInterval" "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b" "creationTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xc2\xe7" "sessionAttr:SPRING_SECURITY_SAVED_REQUEST" "\xac\xed\x00\x05sr\x00Aorg.springframework.security.web.savedrequest.DefaultSavedRequest\x1e@HD\xf96d\x94\x02\x00\x0eI\x00\nserverPortL\x00\x0bcontextPatht\x00\x12Ljava/lang/String;L\x00\acookiest\x00\x15Ljava/util/ArrayList;L\x00\aheaderst\x00\x0fLjava/util/Map;L\x00\alocalesq\x00~\x00\x02L\x00\x06methodq\x00~\x00\x01L\x00\nparametersq\x00~\x00\x03L\x00\bpathInfoq\x00~\x00\x01L\x00\x0bqueryStringq\x00~\x00\x01L\x00\nrequestURIq\x00~\x00\x01L\x00\nrequestURLq\x00~\x00\x01L\x00\x06schemeq\x00~\x00\x01L\x00\nserverNameq\x00~\x00\x01L\x00\x0bservletPathq\x00~\x00\x01xp\x00\x00\x1f\x90t\x00\x00sr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xsr\x00\x11java.util.TreeMap\x0c\xc1\xf6>-%j\xe6\x03\x00\x01L\x00\ncomparatort\x00\x16Ljava/util/Comparator;xpsr\x00*java.lang.String$CaseInsensitiveComparatorw\x03\\}\\P\xe5\xce\x02\x00\x00xpw\x04\x00\x00\x00\x10t\x00\x06acceptsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x87text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9xt\x00\x0faccept-encodingsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x11gzip, deflate, brxt\x00\x0faccept-languagesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00#ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7xt\x00\rcache-controlsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\tmax-age=0xt\x00\nconnectionsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\nkeep-alivext\x00\x04hostsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x0elocalhost:8080xt\x00\areferersq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x1bhttp://localhost:8080/loginxt\x00\tsec-ch-uasq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00@\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"xt\x00\x10sec-ch-ua-mobilesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x02?0xt\x00\x12sec-ch-ua-platformsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\a\"macOS\"xt\x00\x0esec-fetch-destsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\bdocumentxt\x00\x0esec-fetch-modesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\bnavigatext\x00\x0esec-fetch-sitesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x0bsame-originxt\x00\x0esec-fetch-usersq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x02?1xt\x00\x19upgrade-insecure-requestssq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x011xt\x00\nuser-agentsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00yMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36xxsq\x00~\x00\x06\x00\x00\x00\x04w\x04\x00\x00\x00\x04sr\x00\x10java.util.Locale~\xf8\x11`\x9c0\xf9\xec\x03\x00\x06I\x00\bhashcodeL\x00\acountryq\x00~\x00\x01L\x00\nextensionsq\x00~\x00\x01L\x00\blanguageq\x00~\x00\x01L\x00\x06scriptq\x00~\x00\x01L\x00\avariantq\x00~\x00\x01xp\xff\xff\xff\xfft\x00\x02JPq\x00~\x00\x05t\x00\x02jaq\x00~\x00\x05q\x00~\x00\x05xsq\x00~\x00>\xff\xff\xff\xffq\x00~\x00\x05q\x00~\x00\x05q\x00~\x00Aq\x00~\x00\x05q\x00~\x00\x05xsq\x00~\x00>\xff\xff\xff\xfft\x00\x02USq\x00~\x00\x05t\x00\x02enq\x00~\x00\x05q\x00~\x00\x05xsq\x00~\x00>\xff\xff\xff\xffq\x00~\x00\x05q\x00~\x00\x05q\x00~\x00Eq\x00~\x00\x05q\x00~\x00\x05xxt\x00\x03GETsq\x00~\x00\bpw\x04\x00\x00\x00\x00xppt\x00\x01/t\x00\x16http://localhost:8080/t\x00\x04httpt\x00\tlocalhostt\x00\x01/"
1646916911.860858 [0 127.0.0.1:59916] "SADD" "spring:session:expirations:1646918760000" "\xac\xed\x00\x05t\x00,expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916911.862692 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:expirations:1646918760000" "2100000"
1646916911.864704 [0 127.0.0.1:59916] "APPEND" "spring:session:sessions:expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2" ""
1646916911.865652 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "1800000"
1646916911.866575 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "2100000"
1646916911.868053 [0 127.0.0.1:59916] "PUBLISH" "spring:session:event:0:created:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "\xac\xed\x00\x05sr\x00\x11java.util.HashMap\x05\a\xda\xc1\xc3\x16`\xd1\x03\x00\x02F\x00\nloadFactorI\x00\tthresholdxp?@\x00\x00\x00\x00\x00\x04w\b\x00\x00\x00\x04\x00\x00\x00\x00x"
1646916911.882439 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916911.887132 [0 127.0.0.1:59916] "HMSET" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xc3\x0c" "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN" "\xac\xed\x00\x05sr\x006org.springframework.security.web.csrf.DefaultCsrfTokenZ\xef\xb7\xc8/\xa2\xfb\xd5\x02\x00\x03L\x00\nheaderNamet\x00\x12Ljava/lang/String;L\x00\rparameterNameq\x00~\x00\x01L\x00\x05tokenq\x00~\x00\x01xpt\x00\x0cX-CSRF-TOKENt\x00\x05_csrft\x00$31641e92-2d3d-486b-82e4-efcad96ee8fe"
1646916911.887987 [0 127.0.0.1:59916] "SADD" "spring:session:expirations:1646918760000" "\xac\xed\x00\x05t\x00,expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916911.888732 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:expirations:1646918760000" "2100000"
1646916911.889398 [0 127.0.0.1:59916] "APPEND" "spring:session:sessions:expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2" ""
1646916911.890051 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "1800000"
1646916911.890638 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "2100000"
1646916911.891258 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916911.892703 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916914.624004 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916914.825021 [0 127.0.0.1:59916] "RENAME" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.825777 [0 127.0.0.1:59916] "RENAME" "spring:session:sessions:expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.829586 [0 127.0.0.1:59916] "HMSET" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xcd\xc4" "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION" "" "sessionAttr:SPRING_SECURITY_CONTEXT" "\xac\xed\x00\x05sr\x00=org.springframework.security.core.context.SecurityContextImpl\x00\x00\x00\x00\x00\x00\x020\x02\x00\x01L\x00\x0eauthenticationt\x002Lorg/springframework/security/core/Authentication;xpsr\x00Oorg.springframework.security.authentication.UsernamePasswordAuthenticationToken\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\x0bcredentialst\x00\x12Ljava/lang/Object;L\x00\tprincipalq\x00~\x00\x04xr\x00Gorg.springframework.security.authentication.AbstractAuthenticationToken\xd3\xaa(~nGd\x0e\x02\x00\x03Z\x00\rauthenticatedL\x00\x0bauthoritiest\x00\x16Ljava/util/Collection;L\x00\adetailsq\x00~\x00\x04xp\x01sr\x00&java.util.Collections$UnmodifiableList\xfc\x0f%1\xb5\xec\x8e\x10\x02\x00\x01L\x00\x04listt\x00\x10Ljava/util/List;xr\x00,java.util.Collections$UnmodifiableCollection\x19B\x00\x80\xcb^\xf7\x1e\x02\x00\x01L\x00\x01cq\x00~\x00\x06xpsr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xq\x00~\x00\rsr\x00Horg.springframework.security.web.authentication.WebAuthenticationDetails\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\rremoteAddresst\x00\x12Ljava/lang/String;L\x00\tsessionIdq\x00~\x00\x0fxpt\x00\x0f0:0:0:0:0:0:0:1t\x00$c332c966-2d8b-4cb9-a940-fcced7ba25d2psr\x002org.springframework.security.core.userdetails.User\x00\x00\x00\x00\x00\x00\x020\x02\x00\aZ\x00\x11accountNonExpiredZ\x00\x10accountNonLockedZ\x00\x15credentialsNonExpiredZ\x00\aenabledL\x00\x0bauthoritiest\x00\x0fLjava/util/Set;L\x00\bpasswordq\x00~\x00\x0fL\x00\busernameq\x00~\x00\x0fxp\x01\x01\x01\x01sr\x00%java.util.Collections$UnmodifiableSet\x80\x1d\x92\xd1\x8f\x9b\x80U\x02\x00\x00xq\x00~\x00\nsr\x00\x11java.util.TreeSet\xdd\x98P\x93\x95\xed\x87[\x03\x00\x00xpsr\x00Forg.springframework.security.core.userdetails.User$AuthorityComparator\x00\x00\x00\x00\x00\x00\x020\x02\x00\x00xpw\x04\x00\x00\x00\x00xpt\x00\x04user" "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN" ""
1646916914.831427 [0 127.0.0.1:59916] "SADD" "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user" "\xac\xed\x00\x05t\x00$44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.832225 [0 127.0.0.1:59916] "SADD" "spring:session:expirations:1646918760000" "\xac\xed\x00\x05t\x00,expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.832900 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:expirations:1646918760000" "2100000"
1646916914.833575 [0 127.0.0.1:59916] "APPEND" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" ""
1646916914.834209 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "1800000"
1646916914.834850 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "2100000"
1646916914.835507 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916914.836309 [0 127.0.0.1:59916] "HMSET" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "sessionAttr:SPRING_SECURITY_CONTEXT" "\xac\xed\x00\x05sr\x00=org.springframework.security.core.context.SecurityContextImpl\x00\x00\x00\x00\x00\x00\x020\x02\x00\x01L\x00\x0eauthenticationt\x002Lorg/springframework/security/core/Authentication;xpsr\x00Oorg.springframework.security.authentication.UsernamePasswordAuthenticationToken\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\x0bcredentialst\x00\x12Ljava/lang/Object;L\x00\tprincipalq\x00~\x00\x04xr\x00Gorg.springframework.security.authentication.AbstractAuthenticationToken\xd3\xaa(~nGd\x0e\x02\x00\x03Z\x00\rauthenticatedL\x00\x0bauthoritiest\x00\x16Ljava/util/Collection;L\x00\adetailsq\x00~\x00\x04xp\x01sr\x00&java.util.Collections$UnmodifiableList\xfc\x0f%1\xb5\xec\x8e\x10\x02\x00\x01L\x00\x04listt\x00\x10Ljava/util/List;xr\x00,java.util.Collections$UnmodifiableCollection\x19B\x00\x80\xcb^\xf7\x1e\x02\x00\x01L\x00\x01cq\x00~\x00\x06xpsr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xq\x00~\x00\rsr\x00Horg.springframework.security.web.authentication.WebAuthenticationDetails\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\rremoteAddresst\x00\x12Ljava/lang/String;L\x00\tsessionIdq\x00~\x00\x0fxpt\x00\x0f0:0:0:0:0:0:0:1t\x00$c332c966-2d8b-4cb9-a940-fcced7ba25d2psr\x002org.springframework.security.core.userdetails.User\x00\x00\x00\x00\x00\x00\x020\x02\x00\aZ\x00\x11accountNonExpiredZ\x00\x10accountNonLockedZ\x00\x15credentialsNonExpiredZ\x00\aenabledL\x00\x0bauthoritiest\x00\x0fLjava/util/Set;L\x00\bpasswordq\x00~\x00\x0fL\x00\busernameq\x00~\x00\x0fxp\x01\x01\x01\x01sr\x00%java.util.Collections$UnmodifiableSet\x80\x1d\x92\xd1\x8f\x9b\x80U\x02\x00\x00xq\x00~\x00\nsr\x00\x11java.util.TreeSet\xdd\x98P\x93\x95\xed\x87[\x03\x00\x00xpsr\x00Forg.springframework.security.core.userdetails.User$AuthorityComparator\x00\x00\x00\x00\x00\x00\x020\x02\x00\x00xpw\x04\x00\x00\x00\x00xpt\x00\x04user"
1646916914.837401 [0 127.0.0.1:59916] "SREM" "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user" "\xac\xed\x00\x05t\x00$44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.838195 [0 127.0.0.1:59916] "SADD" "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user" "\xac\xed\x00\x05t\x00$44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.838894 [0 127.0.0.1:59916] "SADD" "spring:session:expirations:1646918760000" "\xac\xed\x00\x05t\x00,expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.839500 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:expirations:1646918760000" "2100000"
1646916914.840189 [0 127.0.0.1:59916] "APPEND" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" ""
1646916914.840861 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "1800000"
1646916914.841486 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "2100000"
1646916914.842081 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
1646916914.847443 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.856865 [0 127.0.0.1:59916] "HMSET" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xce\xa0" "sessionAttr:SPRING_SECURITY_SAVED_REQUEST" ""
1646916914.860546 [0 127.0.0.1:59916] "SADD" "spring:session:expirations:1646918760000" "\xac\xed\x00\x05t\x00,expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.861292 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:expirations:1646918760000" "2100000"
1646916914.861985 [0 127.0.0.1:59916] "APPEND" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" ""
1646916914.862693 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "1800000"
1646916914.864418 [0 127.0.0.1:59916] "PEXPIRE" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48" "2100000"
1646916914.864982 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.866710 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
1646916914.887045 [0 127.0.0.1:59916] "HGETALL" "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
↓ご存知のとおり、 TTL が設定されていないキーが一つある し、無駄が多そう。
このキーは、spring-security を有効化していると、設定されるようになる ようだ。
127.0.0.1:6379> eval "local t = {} local i = 1 for _,v in ipairs(redis.call('KEYS', '*')) do if redis.call('TTL',v) == -1 then t[i] = v i = i + 1 end end return t" 0
1) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user"
127.0.0.1:6379> keys *
1) "spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
2) "spring:session:expirations:1646918760000"
3) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user"
4) "spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
127.0.0.1:6379> hgetall spring:session:sessions:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48
1) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
2) ""
3) "sessionAttr:SPRING_SECURITY_CONTEXT"
4) "\xac\xed\x00\x05sr\x00=org.springframework.security.core.context.SecurityContextImpl\x00\x00\x00\x00\x00\x00\x020\x02\x00\x01L\x00\x0eauthenticationt\x002Lorg/springframework/security/core/Authentication;xpsr\x00Oorg.springframework.security.authentication.UsernamePasswordAuthenticationToken\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\x0bcredentialst\x00\x12Ljava/lang/Object;L\x00\tprincipalq\x00~\x00\x04xr\x00Gorg.springframework.security.authentication.AbstractAuthenticationToken\xd3\xaa(~nGd\x0e\x02\x00\x03Z\x00\rauthenticatedL\x00\x0bauthoritiest\x00\x16Ljava/util/Collection;L\x00\adetailsq\x00~\x00\x04xp\x01sr\x00&java.util.Collections$UnmodifiableList\xfc\x0f%1\xb5\xec\x8e\x10\x02\x00\x01L\x00\x04listt\x00\x10Ljava/util/List;xr\x00,java.util.Collections$UnmodifiableCollection\x19B\x00\x80\xcb^\xf7\x1e\x02\x00\x01L\x00\x01cq\x00~\x00\x06xpsr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xq\x00~\x00\rsr\x00Horg.springframework.security.web.authentication.WebAuthenticationDetails\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\rremoteAddresst\x00\x12Ljava/lang/String;L\x00\tsessionIdq\x00~\x00\x0fxpt\x00\x0f0:0:0:0:0:0:0:1t\x00$c332c966-2d8b-4cb9-a940-fcced7ba25d2psr\x002org.springframework.security.core.userdetails.User\x00\x00\x00\x00\x00\x00\x020\x02\x00\aZ\x00\x11accountNonExpiredZ\x00\x10accountNonLockedZ\x00\x15credentialsNonExpiredZ\x00\aenabledL\x00\x0bauthoritiest\x00\x0fLjava/util/Set;L\x00\bpasswordq\x00~\x00\x0fL\x00\busernameq\x00~\x00\x0fxp\x01\x01\x01\x01sr\x00%java.util.Collections$UnmodifiableSet\x80\x1d\x92\xd1\x8f\x9b\x80U\x02\x00\x00xq\x00~\x00\nsr\x00\x11java.util.TreeSet\xdd\x98P\x93\x95\xed\x87[\x03\x00\x00xpsr\x00Forg.springframework.security.core.userdetails.User$AuthorityComparator\x00\x00\x00\x00\x00\x00\x020\x02\x00\x00xpw\x04\x00\x00\x00\x00xpt\x00\x04user"
5) "sessionAttr:SPRING_SECURITY_SAVED_REQUEST"
6) ""
7) "creationTime"
8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xc2\xe7"
9) "maxInactiveInterval"
10) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"
11) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
12) ""
13) "lastAccessedTime"
14) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe5\xce\xa0"
127.0.0.1:6379> smembers spring:session:expirations:1646918760000
1) "\xac\xed\x00\x05t\x00,expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
2) "\xac\xed\x00\x05t\x00,expires:c332c966-2d8b-4cb9-a940-fcced7ba25d2"
127.0.0.1:6379> smembers spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user
1) "\xac\xed\x00\x05t\x00$44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48"
127.0.0.1:6379> get spring:session:sessions:expires:44b8a9c4-db68-4bbf-be7e-6f9dd5a01d48
""
ここから、1分間ごとに spring:session:expirations:1646918760000 をなめて消す処理が走る。しかし、ここには以下のような問題がある。
ロック処理などはないので、spring-session をロードしているすべてのサーバーで同じ処理が走って無駄であって、エコではない。
該当時間に全部のサーバーが落ちていれば、削除処理は動かない。
たとえば再起動しているタイミングがあると、タイミングによってリークするかもしれない。
ほとんどのデータは TTL が入っているので大丈夫。だが、TTL が入ってないキーがあるのでそこが問題だ。
中途半端に TTL 設定してるのがよくわからない。謎。
特定ユーザーのログインセッションを一括で消す機能があって便利だが、これもまぁ別に RedisSessionRepository をベースに自前で用意したほうが良いかもしれない。
RedisSessionRepository
RedisSessionRepository はよりシンプルな新しいクラスである。https://github.com/spring-projects/spring-session/issues/1278 の PR で導入されていて、複雑すぎる RedisIndexedSessionRepository に対する代替案として提示されているものである。今後はこっちをデフォルトにする計画もあるっぽいです。 https://github.com/spring-projects/spring-session/issues/1711
(SimpleRedisOperationsSessionRepositoryという名前から変更された)
↓RedisSessionRepository での実行結果。そこそこ多いが、だいぶマシである。
1646916589.099128 [0 127.0.0.1:59338] "HMSET" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe0\xd6!" "maxInactiveInterval" "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b" "sessionAttr:SPRING_SECURITY_SAVED_REQUEST" "\xac\xed\x00\x05sr\x00Aorg.springframework.security.web.savedrequest.DefaultSavedRequest\x1e@HD\xf96d\x94\x02\x00\x0eI\x00\nserverPortL\x00\x0bcontextPatht\x00\x12Ljava/lang/String;L\x00\acookiest\x00\x15Ljava/util/ArrayList;L\x00\aheaderst\x00\x0fLjava/util/Map;L\x00\alocalesq\x00~\x00\x02L\x00\x06methodq\x00~\x00\x01L\x00\nparametersq\x00~\x00\x03L\x00\bpathInfoq\x00~\x00\x01L\x00\x0bqueryStringq\x00~\x00\x01L\x00\nrequestURIq\x00~\x00\x01L\x00\nrequestURLq\x00~\x00\x01L\x00\x06schemeq\x00~\x00\x01L\x00\nserverNameq\x00~\x00\x01L\x00\x0bservletPathq\x00~\x00\x01xp\x00\x00\x1f\x90t\x00\x00sr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xsr\x00\x11java.util.TreeMap\x0c\xc1\xf6>-%j\xe6\x03\x00\x01L\x00\ncomparatort\x00\x16Ljava/util/Comparator;xpsr\x00*java.lang.String$CaseInsensitiveComparatorw\x03\\}\\P\xe5\xce\x02\x00\x00xpw\x04\x00\x00\x00\x10t\x00\x06acceptsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x87text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9xt\x00\x0faccept-encodingsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x11gzip, deflate, brxt\x00\x0faccept-languagesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00#ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7xt\x00\rcache-controlsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\tmax-age=0xt\x00\nconnectionsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\nkeep-alivext\x00\x04hostsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x0elocalhost:8080xt\x00\areferersq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x1bhttp://localhost:8080/loginxt\x00\tsec-ch-uasq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00@\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"xt\x00\x10sec-ch-ua-mobilesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x02?0xt\x00\x12sec-ch-ua-platformsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\a\"macOS\"xt\x00\x0esec-fetch-destsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\bdocumentxt\x00\x0esec-fetch-modesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\bnavigatext\x00\x0esec-fetch-sitesq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x0bsame-originxt\x00\x0esec-fetch-usersq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x02?1xt\x00\x19upgrade-insecure-requestssq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00\x011xt\x00\nuser-agentsq\x00~\x00\x06\x00\x00\x00\x01w\x04\x00\x00\x00\x01t\x00yMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36xxsq\x00~\x00\x06\x00\x00\x00\x04w\x04\x00\x00\x00\x04sr\x00\x10java.util.Locale~\xf8\x11`\x9c0\xf9\xec\x03\x00\x06I\x00\bhashcodeL\x00\acountryq\x00~\x00\x01L\x00\nextensionsq\x00~\x00\x01L\x00\blanguageq\x00~\x00\x01L\x00\x06scriptq\x00~\x00\x01L\x00\avariantq\x00~\x00\x01xp\xff\xff\xff\xfft\x00\x02JPq\x00~\x00\x05t\x00\x02jaq\x00~\x00\x05q\x00~\x00\x05xsq\x00~\x00>\xff\xff\xff\xffq\x00~\x00\x05q\x00~\x00\x05q\x00~\x00Aq\x00~\x00\x05q\x00~\x00\x05xsq\x00~\x00>\xff\xff\xff\xfft\x00\x02USq\x00~\x00\x05t\x00\x02enq\x00~\x00\x05q\x00~\x00\x05xsq\x00~\x00>\xff\xff\xff\xffq\x00~\x00\x05q\x00~\x00\x05q\x00~\x00Eq\x00~\x00\x05q\x00~\x00\x05xxt\x00\x03GETsq\x00~\x00\bpw\x04\x00\x00\x00\x00xppt\x00\x01/t\x00\x16http://localhost:8080/t\x00\x04httpt\x00\tlocalhostt\x00\x01/" "creationTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe0\xd6!"
1646916589.102109 [0 127.0.0.1:59338] "PEXPIREAT" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90" "1646918389089"
1646916589.105447 [0 127.0.0.1:59338] "EXISTS" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916589.116953 [0 127.0.0.1:59338] "HGETALL" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916589.122376 [0 127.0.0.1:59338] "EXISTS" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916589.125151 [0 127.0.0.1:59338] "HMSET" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe0\xd6@" "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN" "\xac\xed\x00\x05sr\x006org.springframework.security.web.csrf.DefaultCsrfTokenZ\xef\xb7\xc8/\xa2\xfb\xd5\x02\x00\x03L\x00\nheaderNamet\x00\x12Ljava/lang/String;L\x00\rparameterNameq\x00~\x00\x01L\x00\x05tokenq\x00~\x00\x01xpt\x00\x0cX-CSRF-TOKENt\x00\x05_csrft\x00$49938ae3-2c5a-4343-a983-3b941ec5fe34"
1646916589.127423 [0 127.0.0.1:59338] "PEXPIREAT" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90" "1646918389120"
1646916589.129688 [0 127.0.0.1:59338] "HGETALL" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916589.133727 [0 127.0.0.1:59338] "EXISTS" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916589.135493 [0 127.0.0.1:59338] "HGETALL" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916592.188724 [0 127.0.0.1:59338] "HGETALL" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916592.312255 [0 127.0.0.1:59338] "EXISTS" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90"
1646916592.313174 [0 127.0.0.1:59338] "RENAME" "spring:session:sessions:82b6e568-278f-41bf-8ac5-ca7861460d90" "spring:session:sessions:a7133f51-f57f-4e05-80ca-ddfbbf2286d0"
1646916592.314158 [0 127.0.0.1:59338] "HMSET" "spring:session:sessions:a7133f51-f57f-4e05-80ca-ddfbbf2286d0" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe0\xe2>" "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION" "" "sessionAttr:SPRING_SECURITY_CONTEXT" "\xac\xed\x00\x05sr\x00=org.springframework.security.core.context.SecurityContextImpl\x00\x00\x00\x00\x00\x00\x020\x02\x00\x01L\x00\x0eauthenticationt\x002Lorg/springframework/security/core/Authentication;xpsr\x00Oorg.springframework.security.authentication.UsernamePasswordAuthenticationToken\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\x0bcredentialst\x00\x12Ljava/lang/Object;L\x00\tprincipalq\x00~\x00\x04xr\x00Gorg.springframework.security.authentication.AbstractAuthenticationToken\xd3\xaa(~nGd\x0e\x02\x00\x03Z\x00\rauthenticatedL\x00\x0bauthoritiest\x00\x16Ljava/util/Collection;L\x00\adetailsq\x00~\x00\x04xp\x01sr\x00&java.util.Collections$UnmodifiableList\xfc\x0f%1\xb5\xec\x8e\x10\x02\x00\x01L\x00\x04listt\x00\x10Ljava/util/List;xr\x00,java.util.Collections$UnmodifiableCollection\x19B\x00\x80\xcb^\xf7\x1e\x02\x00\x01L\x00\x01cq\x00~\x00\x06xpsr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xq\x00~\x00\rsr\x00Horg.springframework.security.web.authentication.WebAuthenticationDetails\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\rremoteAddresst\x00\x12Ljava/lang/String;L\x00\tsessionIdq\x00~\x00\x0fxpt\x00\x0f0:0:0:0:0:0:0:1t\x00$82b6e568-278f-41bf-8ac5-ca7861460d90psr\x002org.springframework.security.core.userdetails.User\x00\x00\x00\x00\x00\x00\x020\x02\x00\aZ\x00\x11accountNonExpiredZ\x00\x10accountNonLockedZ\x00\x15credentialsNonExpiredZ\x00\aenabledL\x00\x0bauthoritiest\x00\x0fLjava/util/Set;L\x00\bpasswordq\x00~\x00\x0fL\x00\busernameq\x00~\x00\x0fxp\x01\x01\x01\x01sr\x00%java.util.Collections$UnmodifiableSet\x80\x1d\x92\xd1\x8f\x9b\x80U\x02\x00\x00xq\x00~\x00\nsr\x00\x11java.util.TreeSet\xdd\x98P\x93\x95\xed\x87[\x03\x00\x00xpsr\x00Forg.springframework.security.core.userdetails.User$AuthorityComparator\x00\x00\x00\x00\x00\x00\x020\x02\x00\x00xpw\x04\x00\x00\x00\x00xpt\x00\x04user" "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN" ""
↓ ** TTL が設定されていないキーは存在しないし、1セッションにつき1つのハッシュのみがセットされていて、大きくデータ量を削減することができる。**
(デフォルトで30分の TTL っぽい)
127.0.0.1:6379> eval "local t = {} local i = 1 for _,v in ipairs(redis.call('KEYS', '*')) do if redis.call('TTL',v) == -1 then t[i] = v i = i + 1 end end return t" 0
(empty array)
127.0.0.1:6379> keys *
1) "spring:session:sessions:a7133f51-f57f-4e05-80ca-ddfbbf2286d0"
127.0.0.1:6379> type spring:session:sessions:a7133f51-f57f-4e05-80ca-ddfbbf2286d0
hash
127.0.0.1:6379> hgetall spring:session:sessions:a7133f51-f57f-4e05-80ca-ddfbbf2286d0
1) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
2) ""
3) "sessionAttr:SPRING_SECURITY_CONTEXT"
4) "\xac\xed\x00\x05sr\x00=org.springframework.security.core.context.SecurityContextImpl\x00\x00\x00\x00\x00\x00\x020\x02\x00\x01L\x00\x0eauthenticationt\x002Lorg/springframework/security/core/Authentication;xpsr\x00Oorg.springframework.security.authentication.UsernamePasswordAuthenticationToken\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\x0bcredentialst\x00\x12Ljava/lang/Object;L\x00\tprincipalq\x00~\x00\x04xr\x00Gorg.springframework.security.authentication.AbstractAuthenticationToken\xd3\xaa(~nGd\x0e\x02\x00\x03Z\x00\rauthenticatedL\x00\x0bauthoritiest\x00\x16Ljava/util/Collection;L\x00\adetailsq\x00~\x00\x04xp\x01sr\x00&java.util.Collections$UnmodifiableList\xfc\x0f%1\xb5\xec\x8e\x10\x02\x00\x01L\x00\x04listt\x00\x10Ljava/util/List;xr\x00,java.util.Collections$UnmodifiableCollection\x19B\x00\x80\xcb^\xf7\x1e\x02\x00\x01L\x00\x01cq\x00~\x00\x06xpsr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x00w\x04\x00\x00\x00\x00xq\x00~\x00\rsr\x00Horg.springframework.security.web.authentication.WebAuthenticationDetails\x00\x00\x00\x00\x00\x00\x020\x02\x00\x02L\x00\rremoteAddresst\x00\x12Ljava/lang/String;L\x00\tsessionIdq\x00~\x00\x0fxpt\x00\x0f0:0:0:0:0:0:0:1t\x00$82b6e568-278f-41bf-8ac5-ca7861460d90psr\x002org.springframework.security.core.userdetails.User\x00\x00\x00\x00\x00\x00\x020\x02\x00\aZ\x00\x11accountNonExpiredZ\x00\x10accountNonLockedZ\x00\x15credentialsNonExpiredZ\x00\aenabledL\x00\x0bauthoritiest\x00\x0fLjava/util/Set;L\x00\bpasswordq\x00~\x00\x0fL\x00\busernameq\x00~\x00\x0fxp\x01\x01\x01\x01sr\x00%java.util.Collections$UnmodifiableSet\x80\x1d\x92\xd1\x8f\x9b\x80U\x02\x00\x00xq\x00~\x00\nsr\x00\x11java.util.TreeSet\xdd\x98P\x93\x95\xed\x87[\x03\x00\x00xpsr\x00Forg.springframework.security.core.userdetails.User$AuthorityComparator\x00\x00\x00\x00\x00\x00\x020\x02\x00\x00xpw\x04\x00\x00\x00\x00xpt\x00\x04user"
5) "sessionAttr:SPRING_SECURITY_SAVED_REQUEST"
6) ""
7) "creationTime"
8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe0\xd6!"
9) "maxInactiveInterval"
10) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"
11) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
12) ""
13) "lastAccessedTime"
14) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x7fs\xe0\xe2\xc6"
127.0.0.1:6379> ttl spring:session:sessions:a7133f51-f57f-4e05-80ca-ddfbbf2286d0
(integer) 1764
現在は、RedisSessionRepository は、デフォルトの実装ではないこともあってドキュメントが少ない。
https://spring.io/blog/2019/06/18/spring-session-corn-m2-and-spring-session-bean-sr6-released
https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-redis-simple
このあたりのドキュメントしかないのでこのへんを参照のこと。
↓のように設定すれば、RedisSessionRepository を利用できる。
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.data.redis.RedisSessionRepository;
@EnableSpringHttpSession
@Configuration(proxyBeanMethods = false)
public class MySessionConfiguration {
@Bean
public RedisOperations<String, Object> sessionRedisOperations(RedisConnectionFactory redisConnectionFactory) {
org.springframework.data.redis.core.RedisTemplate<String, Object> redisTemplate = new org.springframework.data.redis.core.RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public RedisSessionRepository redisSessionRepository(RedisOperations<String, Object> sessionRedisOperations) {
return new RedisSessionRepository(sessionRedisOperations);
}
}
See also
https://kagamihoge.hatenablog.com/entry/2018/05/19/163524
import jdk.jfr.consumer.RecordedClass
import jdk.jfr.consumer.RecordedEvent
import jdk.jfr.consumer.RecordingStream
import java.nio.ByteBuffer
import java.time.Duration
import kotlin.system.exitProcess
fun main(args: Array<String>) {
// OOME を発行させるスレッド
Thread {
Thread.sleep(1000)
val bbList = mutableListOf<ByteBuffer>()
while (true) {
try {
val bb = ByteBuffer.allocateDirect(Int.MAX_VALUE)
bbList.add(bb)
} catch (e: Error) {
println("Caught $e")
}
}
}.start()
// OOME があったら harakiri する。
RecordingStream().use { rs ->
rs.enable("jdk.JavaExceptionThrow").withPeriod(Duration.ofSeconds(1))
rs.onEvent("jdk.JavaExceptionThrow") { event: RecordedEvent ->
val message = event.getString("message")
val thrownClass = event.getValue<RecordedClass>("thrownClass")
if (thrownClass.name == "java.lang.OutOfMemoryError" && message.startsWith("Cannot reserve")) {
println("Caught OutOfMemoryError! $event")
exitProcess(4)
}
}
println("Starting RecordingStream")
rs.start()
}
}
import java.util.Locale;
public class Foo {
public static void main(String[] args) {
System.out.println(new Locale("id", "ID").toString());
}
}
Java 17 だと id_ID
となるけど、Java 11 だと in_ID
となる。
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html#legacy_language_codes
https://github.com/openjdk/jdk/commit/a4c46e1e4f4f2f05c8002b2af683a390fc46b424
Clock を mockito で @Spy しようとした場合などに、最近では制限が厳しくていじりづらくなっているが、、
Java 17 からは Clock の interface が切り出されて、InstantSource という名前になっている。Java 17 以後の場合は、実装コード内では InstantSource を利用するのが基本となっていくだろう。
https://bugs.openjdk.java.net/browse/JDK-8266846
> Could not resolve all files for configuration ':protobufToolsLocator_protoc'.
> Could not find protoc-3.15.0-osx-aarch_64.exe (com.google.protobuf:protoc:3.15.0).
Searched in the following locations:
https://中略/com/google/protobuf/protoc/3.15.0/protoc-3.15.0-osx-aarch_64.exe
というようなエラーが出るときがある。
この対応は 3.17.3 で入っている。
https://github.com/protocolbuffers/protobuf/pull/8557
https://search.maven.org/artifact/com.google.protobuf/protoc/3.17.3/pom
一旦、x86_64 用のバイナリをダウンロードさせるようになったようだ。
https://github.com/Kotlin/kotlinx-kover
kover は kotlin 用のカバレッジ収集ツール。jacoco でも収集は可能だが、以下のような違いがある。
inline functions に対応
multiplatform 対応
いわゆる放置ゲーなのだが、userjs による自動化をしつつ、なんだかんだ furthest ring に到達するまでやった。challenge も全部やったし、まぁやりきったかなという感じ。
Achievement も 21 個集めきった。
CI のサイクルが違うので、分けておいたほうが開発しやすいようだ。
アプリケーションコードをいじるたびにベースイメージのビルドをしていると時間がかかる。
ここまでできれば、あとは fluentd で storage に格納して、flamegraph 等を描画すれば良いだけである。
import jdk.jfr.consumer.RecordedEvent
import jdk.jfr.consumer.RecordingStream
import org.komamitsu.fluency.Fluency
import org.slf4j.LoggerFactory
import java.time.Duration
import java.time.ZoneId
class JfmonClient(
private val fluency: Fluency,
private val tag: String,
private val siteId: String,
private val instanceId: String
) : AutoCloseable {
private var rs: RecordingStream = RecordingStream()
private val logger = LoggerFactory.getLogger(JfmonClient::class.java)
fun start() {
this.rs.start()
}
fun startAsync() {
this.rs.startAsync()
}
override fun close() {
logger.info("Closing jfmon-client")
this.rs.close()
this.fluency.flush()
this.fluency.close()
}
fun flush() {
logger.info("Flushing jfmon-client")
this.fluency.flush()
}
/**
* jdk.SocketRead: [jdk.jfr.events.SocketReadEvent]
*/
fun enable(name: String, period: Duration?, threshold: Duration? = null, stackTrace: Boolean? = null) {
logger.info("Enabling {}(period={}, threshold={}, stackTrace={})", name, period, threshold, stackTrace)
val settings = rs.enable(name)
if (period != null) {
settings.withPeriod(period)
}
if (threshold != null) {
settings.withThreshold(threshold)
}
if (stackTrace != null) {
if (stackTrace) {
settings.withStackTrace()
} else {
settings.withoutStackTrace()
}
}
rs.onEvent(name, this::emitEvent)
}
private fun emitEvent(event: RecordedEvent) {
val data = buildMap(event)
if (logger.isDebugEnabled) {
logger.debug(
"{}: {} {}",
event.eventType.name,
event.startTime.atZone(ZoneId.of("Asia/Tokyo")),
data
)
}
fluency.emit(tag, data)
}
private fun buildMap(event: RecordedEvent): Map<String, Any?> {
return mapOf(
"siteId" to siteId,
"instanceId" to instanceId,
"type" to event.eventType.name,
"data" to buildData(event)
)
}
private fun buildData(event: RecordedEvent): Map<String, Any?> {
return event.fields.associate { dv ->
val value: Any? = when (dv.typeName) {
"boolean", "long", "int", "java.lang.String" -> event.getValue<Any>(dv.name)
"java.lang.Thread" -> event.getThread(dv.name).javaName
"jdk.types.StackTrace" -> {
val stackTrace = event.stackTrace
stackTrace?.frames?.map { frame ->
// 型が違うものを一つの配列に入れると、Elasticsearch が怒る。
listOf(
frame.method?.type?.name,
frame.method?.name?.toString(),
frame.lineNumber.toString(),
)
}
}
else -> null
}
dv.name to value
}
}
}
JFR では Stacktrace を取得する処理があるが、デフォルトのスタック取得深度は 64 である。
現代のウェブアプリケーションではフレームワーク側で64スタックぐらい余裕で消費しているので、これは増加させる必要がある。あまり大きく設定すると、メモリを圧迫する可能性があるので、できるだけ小さく、かつ各アプリケーションで必要なぶんなスタックの深さを指定するのが良い。
datadog の agent では、256 を設定させている。
https://docs.datadoghq.com/ja/tracing/profiler/enabling/java/
JFR の設定ファイルの中で、stackdepth を指定できる方法がないかと調べたが見当たらない。
2016 年の報告では、G1GCのときに stachdepth を深くしたら遅くなったという報告があるが、今だったらカイゼンされてたりしないのかなぁ。と思ったりする。
https://blogs.oracle.com/poonam/g1-and-flight-recorders-xx:flightrecorderoptionsstackdepth-option