今期のバントホームラン枠 SHOW BY ROCK!! が毎週楽しみな mix3 です。
CentOS6/7 で VPN 構築
今までにも何回か VPN 構築に挑戦してその度に「上手くいかない」となって諦めるということを繰り返してたのですが、今回はどうにか成功したので忘れないうちにメモ。
openswan (for CentOS6) / libreswan (for CentOS7) (IPsec), xl2tpd(L2TP) でさくら VPS で VPN を構築した。
参考にしたサイト
- CLCL / CentOS6-L2TP-IPsec.md - Gist
- スクリプトで一発簡単!iPhone/Android/Mac/Windowsから接続可能なL2TP/IPSecなVPN環境を構築する - blog@sotm.jp
- CentOS7 PPTPを辞めてL2TP/IPSecに変更する - ともかくメモ
- ipsec - linuxでVPNサーバ - Qiita
- CentOS7 第4回 はじめての firewalld で作る Linuxルータ – CLARA ONLINE techblog
- etc...
設定は スクリプトで一発簡単!iPhone/Android/Mac/Windowsから接続可能なL2TP/IPSecなVPN環境を構築する - blog@sotm.jp のスクリプトをほぼほぼそのまま使わせてもらっている。
以下ハマッたポイント
VPNパススルー、またはIPsecパススルー
今までずっと玉砕してた理由が多分これで、ブロードバンドルータのほうで NAT を突破出来るように設定しなければならず、大抵「VPN パススルー」または「IPsec パススルー」という名前で設定が用意されているので設定を有効にしないといけなかった。
NAT超えが出来ないとサーバに接続が到達しないのでログすら出力されず途方にくれるしかったので、今回どうにかルータで止まってるということに気付けて良かった。
CentOS6 の openswan は yum で入れるとバージョンが低い
openswan のバージョンによっては MacOSX でうまく接続出来ない問題があるっぽい
なので、こんな感じで openswan を最新に更新してあげる必要があった。
$ wget https://download.openswan.org/openswan/openswan-latest.tar.gz -O /tmp/openswan-latest.tar.gz
$ mkdir /tmp/openswan-latest
$ tar xzf /tmp/openswan-latest.tar.gz -C /tmp/openswan-latest --strip=1
$ cd /tmp/openswan-latest
$ make programs
$ make install
CentOS7 の libreswan ではそういう問題はないっぽい。
conf の name は揃える必要がある
/etc/ppp/options.xl2tpd
name xl2tpd
/etc/xl2tpd/xl2tpd.conf
name = LinuxVPNserver
/etc/ipsec.d/default.secrets
"hoge001" "xl2tpd" "hoge##123" *
"hoge002" "xl2tpd" "hoge##456" *
これらの設定で LinuxVPNserver となってるところを xl2tpd にして名前を揃えないと接続がうまくいかなかった。(もしかしたら気のせいかもしれない)
CentoOS7 のカーネル設定
CentOS7 のカーネル設定は /etc/sysctl.d/*.conf にファイルを置いて設定するっぽいので CentOS7 PPTPを辞めてL2TP/IPSecに変更する - ともかくメモ に従って
/etc/sysctl.d/10-sysctl_ipsec.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.eth0.accept_redirects = 0
net.ipv4.conf.eth0.send_redirects = 0
net.ipv4.conf.lo.accept_redirects = 0
net.ipv4.conf.lo.send_redirects = 0
として設定反映したあと ipsec verify するとおそらくこんな感じのが出てくる
Checking rp_filter [ENABLED]
/proc/sys/net/ipv4/conf/default/rp_filter [ENABLED]
rp_filter is not fully aware of IPsec and should be disabled
rp_filter もちゃんと無効にしろ ということなので、
/etc/sysctl.d/10-sysctl_ipsec.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.eth0.accept_redirects = 0
net.ipv4.conf.eth0.send_redirects = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.conf.lo.accept_redirects = 0
net.ipv4.conf.lo.send_redirects = 0
net.ipv4.conf.lo.rp_filter = 0
として設定反映したあと ipsec verify するとなぜか変わらず ENABLED がでてくる。設定を確認するとなぜか反映されていない。
$ sysctl -a | grep -e net.ipv4.conf.*send_redirects -e net.ipv4.conf.*accept_redirects -e net.ipv4.conf.*rp_filter | grep -v arp
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.rp_filter = 1 <= ここだけおかしい
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.eth0.accept_redirects = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.conf.eth0.send_redirects = 0
net.ipv4.conf.lo.accept_redirects = 0
net.ipv4.conf.lo.rp_filter = 0
net.ipv4.conf.lo.send_redirects = 0
上書きされてるっぽい雰囲気は感じるので、どこで何が設定されるか調べられないかとググったところ sysctl --system で見れるっぽいことが分かったので、試したところこんな感じになっており /usr/lib/sysctl.d/50-default.conf で上書きされてることが判明。
* Applying /usr/lib/sysctl.d/00-system.conf ...
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0
kernel.shmmax = 4294967295
kernel.shmall = 268435456
* Applying /etc/sysctl.d/10-sysctl_ipsec.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.lo.accept_redirects = 0
net.ipv4.conf.lo.send_redirects = 0
net.ipv4.conf.eth0.accept_redirects = 0
net.ipv4.conf.eth0.send_redirects = 0
* Applying /usr/lib/sysctl.d/50-default.conf ...
kernel.sysrq = 16
kernel.core_uses_pid = 1
net.ipv4.conf.default.rp_filter = 1 <= ここで上書きされてる
net.ipv4.conf.default.accept_source_route = 0
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /etc/sysctl.conf ...
仕方ないので mv /etc/sysctl.d/10-sysctl_ipsec.conf /etc/sysctl.d/60-sysctl_ipsec.conf として上書きされるのを回避することでようやく ENABLED を除去出来た。
CentOS7 では iptables ではなく firewalld
CentOS7 では iptables ではなく firewalld でファイアーウォールを設定する。IPsec/L2TP で必要な設定は以下で良いかと思われる。
firewall-cmd --permanent --add-service=ipsec
firewall-cmd --permanent --add-port=1701/udp
firewall-cmd --permanent --add-port=4500/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload
CentOS7 では chkconfig, /etc/init.d/NAME COMMAND ではなく systemctl COMMAND NAME
CentOS7 ではサービスの管理方法が変更されているので自動起動やサービスのスタートが以下のようになっている。
systemctl enable ipsec
systemctl enable xl2tpd
systemctl restart ipsec
systemctl restart xl2tpd
完成品がこちら
CentOS7 の一発VPN構築スクリプト L2TP_IPSec_vpn_setup_for_centos7.sh
$ curl -L https://gist.githubusercontent.com/mix3/efbaf5cb47946bff6f56/raw/L2TP_IPSec_vpn_setup_for_centos7.sh | bash
or
$ wget https://gist.githubusercontent.com/mix3/efbaf5cb47946bff6f56/raw/L2TP_IPSec_vpn_setup_for_centos7.sh
$ vim L2TP_IPSec_vpn_setup_for_centos7.sh
$ bash L2TP_IPSec_vpn_setup_for_centos7.sh
とでもすると一発で構築VPNが構築出来ると思う。
共有鍵ではなく公開鍵
に出来たらいいなぁとか思ってるがそこまでは行けていない。
社内isuconで新卒にボッコボコにされたので、役立たずとして社内に居場所がなくなりそうです、mix3です。
@acidlemon 作 mirage を「使ってテスト環境を立てまくるぜー」みたいなことが割と当たり前になりつつあって今関わってるプロジェクトもいずれはそうしたいなぁということがありまして。(CentOS6だと不都合があるっぽくてCentOS7に移行するなりそこクリアしてからだけど)
いざ「環境作れよ、あくしろよ」となっても困らないように練習しておこうと思い立って、持て余してるさくらvpsの一つをdocker専用にしてみた。ついでにCoreOSなんかも使ってみた。
ISOのアップロードしてリモートコンソールを開く
ところまでは さくらのVPSにCoreOSをインストールしてDocker専用機にする - Qiita を参考にした
sshで繋ぐ
ところは CoreOSをさくらVPSサーバに入れようとして嵌ったのでメモ - Qiita を参考に /etc/systemd/network/static.network を作らずに以下を実行した
sudo ifconfig <network port> <ip address> netmask <netmask>
sudo route add default gw <default gateway IP>
$ sudo vi /etc/resolv.conf
nameserver <DNS1>
cloud-config
も CoreOSをさくらVPSサーバに入れようとして嵌ったのでメモ - Qiita を参考にした
#cloud-config
coreos:
units:
- name: static.network
content: |
[Match]
Name=eth0
[Network]
Address=<IPアドレス>/<サブネットマスク>
Gateway=<ゲートウェイ>
DNS=<DNS1>
DNS=<DNS2>
ssh_authorized_keys:
- ssh-rsa ABCDABCDABCDABCDABCDABCDABCDABCDABCD...
CoreOS のインストールと reboot
インストールは さくらのVPSにCoreOSをインストールしてDocker専用機にする - Qiita を参考に以下のようにした。 記事では -C alpha だけど -C stable にしておいた。
$ sudo coreos-install -d /dev/vda -c cloud-config -C stable
reboot は VPS の管理画面から。 sudo reboot とかすると再度 LiveCD で boot されてしまう。
ログイン
cloud-configの設定によって公開鍵が設定されてるはずなので、それでログイン出来るようになってるハズ。
Docker 無しで mirage を動かしてみる
最新の mirage を落としてくる https://github.com/acidlemon/mirage/releases/download/v0.2.0/mirage-v0.2.0-linux-amd64.zip
解凍したら cp config_sample.yml config.yml して編集
host:
# web api host
# you can use API and Web interface through this host
# webapi: docker.dev.example.net
webapi: mirage.sakura
# host suffix
# if you access to foo.dev.example.net, mirage search "foo" subdomain container
reverse_proxy_suffix: .sakura
listen:
# listen address
# default is only listen from localhost
foreign_address: 0.0.0.0
# listen port and reverse proxy port
http:
# listen 8080 and transport to container's 5000 port
- listen: 80
target: 5000
# not implemented
# we recommend to use frontend http(like nginx) to SSL termination
# HTTPS:
# - 443
docker:
# if you use docker through http, specify like "http://localhost:4243"
endpoint: unix:///var/run/docker.sock
# if you specify this, fill the form as default value on web interface.
# default_image: myapp:latest
storage:
datadir: ./data
htmldir: ./html
で ./mirage として起動すると mirage.sakura でアクセスすると *.sakura で名前解決できるようになっていれば mirage の web interface が見える
dnsmasq
ドメイン持ってる場合は「*」でAレコード設定して mirage.sakura, .sakura をそれに合わせて設定すれば良いかと思う。持ってない場合は dnsmasq 使うと良い感じに名前解決が設定できるのでおすすめ。
brew install dnsmasq して指示に従って起動する
$ brew install dnsmasq
$ cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
$ sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons
$ sudo chown root /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
$ sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
設定はさっきの *.sakura であればこんな感じで良いかと思う。
$ sudo vim /etc/resolver/sakura
nameserver 127.0.0.1
$ vim /usr/local/etc/dnsmasq.conf
address=/sakura/<IP>
Docker で mirage を起動する
mirage は Dockerfile が用意されていて Docker で起動することもできるので試してみる。
Dockerfile と config.yml を持ってきて config.yml を少し変更する
host:
# web api host
# you can use API and Web interface through this host
# webapi: docker.dev.example.net
webapi: mirage.sakura
# host suffix
# if you access to foo.dev.example.net, mirage search "foo" subdomain container
reverse_proxy_suffix: .sakura
listen:
# listen address
# default is only listen from localhost
foreign_address: 0.0.0.0
# listen port and reverse proxy port
http:
# listen 8080 and transport to container's 5000 port
- listen: 8080
target: 5000
# not implemented
# we recommend to use frontend http(like nginx) to SSL termination
# HTTPS:
# - 443
docker:
# if you use docker through http, specify like "http://localhost:4243"
endpoint: unix:///var/run/docker.sock
# if you specify this, fill the form as default value on web interface.
# default_image: myapp:latest
storage:
datadir: /mirage
htmldir: /opt/mirage/html
コンテナを起動するときのポートフォワードの設定に listen port を合わせておくのと storage をDocker 用のものに合わせておく。
あとは docker build, docker run するだけ
$ docker build -t acidlemon/mirage:latest .
$ docker run -d --name mirage -p 80:8080 acidlemon/mirage:latest
あとは mirage を使うだけ
予習完璧やで(完璧とは言っていない)
plantuml にはいつもお世話になっております。mix3です。
リアルタイムプレビュー
plantuml のサイトにリアルタイムプレビューはあってレスポンンスが早いので重宝するのですが
- 広告がチラチラして邪魔
- 普通のフォームなのでインデント効かない
- タブ使えない
- 狭い
と不満がなくもないので自前のプレビュー環境を作りたいな、とは過去に思ってやろうとしたもあるのですが、
plantumlのコマンドを使うとどうもフォーカスが移ってしまうらしくコンソールが前面に来るという問題があって諦めた経緯があります。
が、最近になってこんな記事を見つけました
Mac + emacs + PlantUML でさくさく UML 職人
JAVA_TOOL_OPTIONS="-Djava.awt.headless=true" watcher --dir ./uml/ -- 'java -jar /foo/bar/plantuml.jar ./uml/*.uml && open -g -a Preview ./img/*.png'
どうやら JAVA_TOOL_OPTIONS="-Djava.awt.headless=true" で awt の設定を変更することでフォーカスの問題を回避出来るようです
ということで全然サクっとはいかなかったっけど作りました。
go-bindata-assetfs なるものがある
goで静的ファイル配信をするときは net/http/#FileServer を使うと思うのですが go-bindata でまとめたときにどうしたらいいか、という問題があって、
@fujiwara さんが記事にしていた Consul KVSをバックエンドにしたリアルタイムダッシュボード #monitoringcasual では、http.Dir のところを自作していているようだったので、それを拝借しようかなと思ったのですが、
よくよく探してみると go-bindata-assetfs という bindata の生成から対応しているものがあったのでそっちを使うようにしてみました。
go-server-starter と manners
別にいらないんだけど manners を使って graceful shutdown に対応させて go-server-startar を使って graceful restart も出来るようにしています。
func serve(mux http.Handler) {
l, _ := ss.NewListener()
if l == nil {
var err error
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
log.Fatalf("Failed to listen to port %d", port)
}
}
s := manners.NewServer()
s.Serve(manners.NewListener(l, s), mux)
}
ちゃんと確認してないけど多分これでいけるはず
WAF無し
Build You Own Web Framework In Go を参考にWAF無しで作ってみました。
middleware のところで func を返すようにして Alice でメソッドチェインにするところとかなんかスマートな感じがして好き。
func hogeHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// before
next.ServeHTTP(w, r)
// after
}
return http.HandlerFunc(fn)
}
func hogeHandler(w http.ResponseWriter, r *http.Request) {
// ...
}
func main() {
commonHandlers := alice.New(hogeMiddleware)
http.Handle("/about", commonHandlers.ThenFunc(hogeHandler))
http.ListenAndServe(":8080", nil)
}
contextとか、サードパーティの routing ライブラリの組み込みとか色々参考になる。まあWAF使わない場合の話だけども。
js-base64
リアルタイムプビューを作る上で一番詰まったのが js-base64
- $.base64.encode(RawDeflate.deflate(Base64.utob("hoge"))
- y8hPTwUA
- Base64.encode(RawDeflate.deflate(Base64.utob("hoge")))
- w4vDiE9PBQA=
こんな感じで js-base64 を使うと encode の結果が違って「サーバで上手く解答出来ないむきーっ!!!」て半日潰してしまった。
結局 Base64.btoa(RawDeflate.deflate(Base64.utob("hoge"))) とすると大丈夫だった。encode と btoa って違うのん?と未だに良くわかっていません。
react.js
せっかくだから色々試さないと、ということで最初はjqueryでごりごりやるかなと思ったけど consul-kv-dashboard で react.js を使っているのを見て試してみました
Content
┗ Editor
┗ Image
こんな感じで Component を用意
- Editor は onChange で textarea の入力内容を Content に通知
- Content は1秒毎に変更をチェックして変更があったら Image を更新
- Image は更新毎に渡された入力内容を元に rawdeflate と base64 (と / が混じるので _ にreplace) して url を生成して img タグを描画
こんな感じで 一人React.js Advent Calendar 2014 の10日目あたりまでを参考にしてみた
コンポーネントはモデルを持っていて、どう描画したら良いかを知っているという点で wicket の View に近いなぁと感じた。もっとプレーンな html で構築出来たら最高だなぁって思った。
テンプレートエンジン的な考え方や、vue.js angular.js のような MVVM の感覚に慣れていると大分混乱するなぁってぐらい考え方が違ってて面白い。
どちらかというと angular.js よりも好みなのでもっとメジャーになるといいなぁ。
ということで
まとめて書き出してみるとなんか色々触った感があって楽しかった。react.js が今後どうなるか注目ですね。
最近 DBIC を使ってコードを書いてるけどオブジェクトの永続化の功罪について想いを馳せることが多いmix3です
CREATE TABLE user_item (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id INTEGER UNSIGNED NOT NULL DEFAULT 0,
item_id INTEGER UNSIGNED NOT NULL DEFAULT 0,
is_equiped TINYINT UNSIGNED NOT NULL DEFAULT 0,
is_protected TINYINT UNSIGNED NOT NULL DEFAULT 0,
INDEX idx_1 (user_id, item_id, is_protected),
INDEX idx_2 (user_id, item_id, is_protected, is_equiped),
INDEX idx_3 (user_id, item_id, is_protected, id),
INDEX idx_4 (user_id, item_id, is_protected, is_equiped, id),
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;
こんなテーブルがあったとして、以下の優先順位で抽出したい
- is_protected = 1 は保護されているので除外
- id 順に SELECT
- ただし is_equiped = 1 は SELECT の対象だが装備してるので優先度を低く
このとき user_id = 1, item_id = 1 のものを 2個 SELECT する場合
SELECT * FROM user_item
WHERE user_id = 1
AND item_id = 1
AND is_protected = 0
ORDER BY is_equiped, id ASC LIMIT 2;
多分こんなクエリになると思う 実際にINSERTして試してみる
INSERT into user_item
(user_id, item_id, is_equiped, is_protected)
VALUES
(1, 1, 1, 1), -- 装備中 & 保護されてる
(1, 1, 1, 0), -- 装備中
(1, 1, 0, 0),
(1, 1, 0, 0),
(1, 1, 0, 0),
(1, 2, 0, 0);
mysql> SELECT * FROM user_item WHERE user_id = 1 AND item_id = 1 AND is_protected = 0 ORDER BY is_equiped, id ASC LIMIT 2;
+----+---------+---------+------------+--------------+
| id | user_id | item_id | is_equiped | is_protected |
+----+---------+---------+------------+--------------+
| 3 | 1 | 1 | 0 | 0 |
| 4 | 1 | 1 | 0 | 0 |
+----+---------+---------+------------+--------------+
期待通り id:1 は除外 id:2 は優先度低いので id:3,4 が帰ってきている
select for update
トランザクション内で select for update するとロックしてくれる
BEGIN;
SELECT * FROM user_item WHERE id = 1 FOR UPDATE;
ターミナルを二つ開いてそれぞれで上記SQLを流すと片方の SELECT が待たされる
ところで select for update に ORDER BY LIMIT が含まれる場合はどうなるのだろうか?
SELECT * FROM user_item FORCE INDEX (idx_N)
WHERE user_id = 1
AND item_id = 1
AND is_protected = 0
ORDER BY is_equiped, id ASC LIMIT 2 FOR UPDATE;
したあとに
SELECT * FROM user_item WHERE id = N FOR UPDATE;
を idx_1~4, id = 1 ~ 6 まで試してみた
id=1 | id=2 | id=3 | Id=4 | id=5 | id=6 | |
---|---|---|---|---|---|---|
idx_1 | × | × | × | × | ||
idx_2 | × | × | ||||
idx_3 | × | × | × | × | × | |
idx_4 | × | × |
こんな感じになった
ORDER BY や LIMIT などを含む select for update も index を張っていればロック範囲を最小限に抑えらえるようだ
ただ idx_3 は id=1 までロックされて変な index を張るとロックの範囲も変な感じになるようだ
idx_2 が idx_4 と同じ最小範囲のロックをしているのは id が primary key だからだろうか?
結論
index は大事
洒落になっていない勢いで無能を晒して首が飛びそうになってます 皆さんいかがお過ごしでしょうか mix3 です。
今回は「クエリ飛びすぎなので where in して prefetch しましょう」と言われてwhere inはわかるけどprefetchはなんぞ?となったのでとりあえずサンプル書いてみました。
例えば
- ユーザがいる
- 装備がある
- ユーザは装備を複数持っている
- ユーザはアバターを複数持っている
- アバターは複数の装備セットを持っている
- 装備セットは頭,体,足の装備を持っている
こんな感じでなんかアバターがあって着せ替えが出来る、着せ替えさせやすいようにセットが作れるようになってるみたいな。
で、このとき50人のユーザの現在装備している装備のidを持ってこいとなったとき愚直にやると多分こんな感じ
subtest simple => sub {
my @sqls = trace_sqls {
for my $user_id ( 1 .. 50 ) {
my $user_row = schema->resultset('User')->find($user_id);
$user_row->current_ua;
$user_row->current_ua->current_uae_set;
$user_row->current_ua->current_uae_set->head_uae;
$user_row->current_ua->current_uae_set->body_uae;
$user_row->current_ua->current_uae_set->leg_uae;
$user_row->current_ua->current_uae_set->head_uae->ae;
$user_row->current_ua->current_uae_set->body_uae->ae;
$user_row->current_ua->current_uae_set->leg_uae->ae;
}
};
pass sprintf "count: %d", scalar(@sqls);
};
450クエリ飛ぶ
where in を使ってユーザを取ってくるところを1クエリに絞ると
subtest "where in" => sub {
my @sqls = trace_sqls {
my @ids = 1 .. 50;
my $user_rs = schema->resultset('User')->search( { id => { -in => \@ids } } );
while ( my $row = $user_rs->next ) {
$row->current_ua;
$row->current_ua->current_uae_set;
$row->current_ua->current_uae_set->head_uae;
$row->current_ua->current_uae_set->body_uae;
$row->current_ua->current_uae_set->leg_uae;
$row->current_ua->current_uae_set->head_uae->ae;
$row->current_ua->current_uae_set->body_uae->ae;
$row->current_ua->current_uae_set->leg_uae->ae;
}
};
pass sprintf "count: %d", scalar(@sqls);
};
401クエリ飛ぶ where in で50クエリほど減る。
ここでprefetchを使うと
subtest "where in + prefetch" => sub {
my @sqls = trace_sqls {
my @ids = 1 .. 50;
my $user_rs = schema->resultset('User')->search(
{ 'me.id' => { -in => \@ids } },
{ prefetch => {
current_ua => {
current_uae_set => {
head_uae => 'ae',
body_uae => 'ae',
leg_uae => 'ae',
},
},
},
}
);
while ( my $row = $user_rs->next ) {
$row->current_ua;
$row->current_ua->current_uae_set;
$row->current_ua->current_uae_set->head_uae;
$row->current_ua->current_uae_set->body_uae;
$row->current_ua->current_uae_set->leg_uae;
$row->current_ua->current_uae_set->head_uae->ae;
$row->current_ua->current_uae_set->body_uae->ae;
$row->current_ua->current_uae_set->leg_uae->ae;
}
};
pass sprintf "count: %d", scalar(@sqls);
};
1クエリになる
prefetchの前にjoinというそのままのものもあるけど、joinはjoinしたテーブルの情報は返ってこないので今回のようにjoinした先のデータも必要だったらprefetchを使うと良いっぽい