be-hase blog

うぇーい!うぇいーーーい!!

Redis Cluster での Lua script に関して

Redis Cluster の Slot に関して

Redis Cluster は slot(shard) が 16384個あり、それぞれの node がその slot を複数保持している。 ある key がどの slot に割当られるのかは、以下の計算で行われる。

slot_num = CRC16(key) mod 16384

また、key に {...} を使用することで slot の計算に使われる部分を指定することができる。

  • {userId10000}:score
  • {userId10000}:friends

は、同じ slot に含まれる。

Lua script with Redis Cluster

Redis Clusterでも、 KEYS が全て同じ slot なら Lua script が使える 。

例えば、こういうのは大丈夫。

> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 {key1}hoge {key1}bar first second
1) "{key1}hoge"
2) "{key1}bar"
3) "first"
4) "second"

が、 keys に異なるslotが含まれると redis cluster は処理することができない。

> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 {key1}hoge {key2}bar first second
(error) CROSSSLOT Keys in request don't hash to the same slot

Spring boot の configuration 周りで意識したいクラス命名規則

xxxConfigだったりxxxSettingだったりxxxConfigurationだったりxxxPropertiesだったり、人によってバラバラなのでなかなか困ります。

本家でどうしているのかを見てみると下のような感じです。このようにしたほうがわかりやすいですぞ〜。

@Configurationをつけるクラス

xxxConfiguration。 annotationの名前と対応しているのでわかりやすい!

@ConfigurationPropertiesをつけるクラス

xxxProperties。 同じくannotationの名前と対応しているのでわかりやすい!

Spring の UriComponentsBuilder のちょっと罠っぽいところ

UriComponentsBuilder は以下のように、URIの構築を簡単にできるやつです。 Spring とか使っている人だと使ったことあると思います。

String result = UriComponentsBuilder
        .fromUriString("https://example.com/")
        .queryParam("hoge", "あ")
        .toUriString();

assertThat(result).isEqualTo("https://example.com/?hoge=%E3%81%82");

最近使っていて気づいたのですが、UriComponentsBuilder.fromUriString(str) or UriComponentsBuilder.fromHttpUrl(str) に渡す URI string に query がついている場合、その部分も encode されるという挙動をします。 つまり、予め encodeしていると2重に encode されてしまうわけです。

こんな感じです。

String result = UriComponentsBuilder
        .fromHttpUrl("https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage") // "/path/to/some/page"
        .queryParam("hoge", "あ")
        .toUriString();

assertThat(result).isNotEqualTo(
        "https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage&hoge=%E3%81%82");
assertThat(result).isEqualTo(
        "https://example.com/?nextPath=%252Fpath%252Fto%252Fsome%252Fpage&hoge=%E3%81%82");

んー。個人的には、queryParam で指定した部分だけのほうが直感的だと思いますが...。

参考までに、Apache HttpClient にある URIBuilder だと query 部分が2重にencodeされるということはありません。

String result = new URIBuilder("https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage")
        .addParameter("hoge", "あ")
        .build().toString();
assertThat(result).isEqualTo(
        "https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage&hoge=%E3%81%82");

IntelliJ IDEAにおいて、remote maven repo の snapshot module がうまく更新されないとき

たしかに local repoには最新版落ちてるのになんぞーって時。

File -> Invalidate Caches / Restart を試す。

SpringのModelに共通な値(デフォルト値)を入れたいとき

開発環境依存の定数とかといったような共通な値をmodelに入れたいときがある。
(static fileのURLとか)

実は@ControllerAdvice@ModelAttributeが使用できるみたい。
(@ExceptionHandlerとか@InitBinderでしか使ったことなかった。)

@ControllerAdvice
public class ControllerAdvice {

    @ModelAttribute
    public void addDefaults(Model model) {
        model.addAttribute("hoge", "hoge");
    }
}

今まで、Interceptor使って埋め込んだり、FreeMarkerConfigurerにsetFreemarkerVariablesとかしていたけど、こっちのほうがスッキリする気はする。

Redis Lua script(EVAL)はSLOWLOGにはどう出るのか

先日、RedisのINCRBYFLOATコマンドがSLOWLOGにはSETコマンドとして出て来るといったような話がありました。 https://github.com/antirez/redis/issues/3841

EVALコマンドでLua scriptを実行したときのSLOWLOGは、EVALとして出て来るのか。それともLua script内のRedisコマンド単位で出てくるのか気になるね〜みたいな話がでたので検証してみる次第です。

結果から言うと、(Redis 3.2.1 versionでは)EVALコマンドとして出て来るので問題ないです。

検証する

なんか適当にLua内でsleepさせてみれば良いわけであります。

しかし、RedisのLuaでは使用できるlibに限りがあるので、os.sleepとかは使えません。
どうしたもんかと考えた結果、SET, PEXPIRE, PTTLを以下のように使って20ms sleepさせてみました。
(Redisはデフォだと10msこえるとSLOWLOGに出てくる)

local tempKey = "temp-key"
local cycles

redis.call("SET", tempKey, "1")
redis.call("PEXPIRE", tempKey, 20)

for i = 0, ARGV[1] do
    local pttl = redis.call("PTTL", tempKey)
    cycles = i;
    if pttl == 0 then
        break;
    end
end

準備できたので、実行してみるです。

EVALのとき

$ redis-cli EVAL "$(cat slow.lua)" 0 100000
(integer) 29409
$ redis-cli SLOWLOG GET 1

1) 1) (integer) 4
   2) (integer) 1488512354
   3) (integer) 19775
   4) 1) "EVAL"
      2) "local tempKey = \"temp-key\"\nlocal cycles\n\nredis.call(\"SET\", tempKey, \"1\")\nredis.call(\"PEXPIRE\", tempKey, 20)\n\nfor i = 0, ARGV[1] ... (127 more bytes)"
      3) "0"
      4) "100000"

お、ちゃんとEVAL単位で出てきます。

EVALSHAのとき

毎回Lua scriptをRedisに送るのは帯域の無駄なので、予めRedisにscriptを読ませておく方法があります。
そして、そのscriptのsha1を使ってこんな風に実行します。

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

もし、SLOWLOGでこのコマンドで出てきても、いったいどのLua scriptなのかパッと見でわからんという問題があります。

というわけで、一応試します。

$ redis-cli SCRIPT LOAD "$(cat slow.lua)"
9dabe8fa13e49ee8c41467e697cd852e553ca36c

$ redis-cli EVALSHA 9dabe8fa13e49ee8c41467e697cd852e553ca36c 0 100000
(integer) 35989
$ redis-cli SLOWLOG GET 10
1) 1) (integer) 6
   2) (integer) 1488512445
   3) (integer) 19238
   4) 1) "EVAL"
      2) "local tempKey = \"temp-key\"\nlocal cycles\n\nredis.call(\"SET\", tempKey, \"1\")\nredis.call(\"PEXPIRE\", tempKey, 20)\n\nfor i = 0, ARGV[1] ... (127 more bytes)"
      3) "0"
      4) "100000"

お、ちゃんとEVALとして出て来るので安心です。

まとめ

というわけで安心です。

Vue 2.0, vue-router, vuex で はてブ リーダーぽいやつ作った

最近サーバー側ばっかやっていて全然フロントエンド側触ってなくてやばい !っという理由と、 そういえば去年の秋頃に Vue 2.0 が出ていたし触ってみるか!っという理由で Vue 2.0 を触ってみた。

ただ触るだけじゃつまらないので、( SPA で)はてブ リーダーっぽいやつを作りました。

https://github.com/be-hase/vue-hatena-bookmark

Live demo

Vue 公式が HackerNews リーダー的なものを sample として公開していたので、それじゃはてブ版でも作るかという軽いノリです。

“人気"と"新着"しかないぶん、わりとサクサク動くのでなかなか便利です。

使ってるもの

package.jsonに書いてあるとおりですが、

  • Vue 2.0
  • vue-router
  • vuex
  • vuetify
    • Vue の Material Design componentsみたいなやつ
  • axios

で作っています。

RSSYQL

記事情報はRSSから取得しています。

さらにYQL(Yahoo Query Language) を使用しています。

YQLを使うと、RSSのようなweb上のリソースをSQLのように取得することができます。

例えば、はてブのテクノロジーの新着を見たいなら、

select * from feed where url='http://b.hatena.ne.jp/entrylist/it.rss'

みたいなYQLを書きます。

axiosを使用して取得するなら、こんな感じになります。

axios.get('https://query.yahooapis.com/v1/public/yql', {
    params: {
      q: `select * from feed where url='http://b.hatena.ne.jp/entrylist/it.rss'`,
      format: 'json',
    },
  });

format=json を指定するとJSON で response を受け取れます。

ちなみにYQL, 特に認証しなくても使えるがcall数の制限はあります。 https://developer.yahoo.com/yql/guide/usage_info_limits.html

YQL, CORSできるので今回の例だと特にserver側で動的に書くことはなくて、static file置くだけで動きます。

所感

フロントエンドのフレームワーク、今までBackbone → Reactって触ってきましたが、一番お手軽感あって学習コストも低くて便利だなっていう感じです。
( React経由しているので学習コスト低く感じただけもしれないっていうのはありますが… )

ちょっと管理画面とかで複雑なことやりたいときとかに使ってみようかなという感じです。