移転しました。${url}を自動で開きます。`); let canonicalLink = document.querySelector('link[rel="canonical"]'); if (!canonicalLink) { canonicalLink = document.createElement('link'); canonicalLink.rel = "canonical"; document.head.appendChild(canonicalLink) } canonicalLink.href = url; setTimeout(() => {location.href = url}, 0); } for (const target of redirectPathnameToTargets) { if (!location.pathname.startsWith(target[0])) continue; renderRedirect(`${NEW_BLOG_PREFIX}${target[1]}`); }

調べたこと、作ったことをメモしています。
こちらに移行中: https://blog.shimazu.me/

Google のエンジニアを辞めて Progate を始めます。

https://cdn-ak.f.st-hatena.com/images/fotolife/a/amiq11/20210401/20210401002831.png

2021年3月31日付で Google を退職し、本日4月1日から Progate でエンジニアとして働くことにしました。*1

そこで、以下の3点について、つらつらと書いていって転職エントリとすることにします。

  1. 東京でGoogleのエンジニアとして働いた5年間
  2. キャリアパスについての悩み
  3. プログラミング教育について期待すること

もし他にも聞きたいことがあったらTwitterでリプでも飛ばしてください。答えられそうなことは答えるかもしれません。

東京でGoogleのエンジニアとして働いた5年間

僕は東京の Chrome チームで、 Service Worker という JavaScriptAPI を実装していました。

const reg = await navigator.serviceWorker.register('sw.js');

という JavaScript のコードを書いたときに Chrome の裏側でどういう C++ のコードが動くのか、詳しく知りたい人は声をかけてくださいね。

Chromiumオープンソースなので具体的な仕事内容をちょっとだけ紹介すると、たとえば Service Worker や Network Loading の大規模リファクタリングをしたり、タイムアウトの実装をしたりといった内部的なものから、JavaScript に API の追加をしたり、Docs の性能最適化なんかをしたりもしていました。 最終的に最後の1年はテックリードとして仕事をさせてもらい、ちょうど大きいプロジェクトをいくつかキリのいいところまで進められたところで退職となりました。

ところで、ブラウザというのはやや特殊なソフトウェアで、自社で仕様を決める事ができず、 W3CWHATWGIETF なんていうところで仕様の標準化をしつつ実装が行われます。そのエコシステムの中において、仕様策定から実装まで一貫してソフトウェアエンジニアとして携われたのは大変大きな経験になったと感じています。普段のコードレビューなどで大変優秀な同僚たちから設計などに関するアドバイスをもらうのみならず、議論が進まないときの進め方、時差をまたいだ仕事のしかた、大規模なリファクタリングをすすめるための手法、標準化の大事さ、オープンソースコミュニティーでの議論のしかたといった、国をまたいで多くの人とインパクトの大きい仕事をするためのコツを学べたのは大きな収穫だったかなと思います。コロナ前には、海外に年3~5回程度出張に行かせてもらっていたのもとても楽しかったです。

また、自主性が重んじられる環境だったというのもとても成長につながったと思います。 なにをするか、どうしていきたいかというのを常に自分で考え、やるべきことを自然と主張できる人が評価されているように感じました。待っていれば与えられるのではないというのは改めてなかなか簡単な環境ではないなと思いますが、常に「次は何をすべきか?」を自分で考えるためのよいトレーニングになったと思います。

ほかにも、 STEP 教育コースというプログラムでアルゴリズムやデータ構造を始めとするソフトウェア開発の基本的な知識を授業で教えたりもしました。大学時代にこんな授業があったら面白かっただろうな、という内容を3時間の授業にまとめ上げるのはなかなか苦労しましたが、教育コースを出た学生さんたちが活躍していく姿を見るととても嬉しくなります。

Google のソフトウェアエンジニアとして働くのは人におすすめできるか?というと、僕個人の意見でいえば Yes です。優秀な同僚や上司、立場に関わらず対等に技術的議論を進められること、正当な評価と正当な評価をすることに対するフィードバックループがあることなどが僕の感じる良い点です。 万人にとって楽な環境ではないと思いますが、よく言われる収入面でのメリットだけでなく、きちんと仕事をするときちんと評価されるという良い環境だと思います。

あとランチはとても美味しかったです!*2

キャリアパスについての悩み

一方で、個人的な課題もいくつかありました。 たとえば英語が苦手なことや知識・経験の不足による標準化会議での議論に参加する壁を感じたこと。これは単純に僕の勉強不足でもあるのですが、議論を引っ張っていく立場になるためにはまだまだ至らないことが多くあると感じました。

また、テックリードとしてたくさんあるタスクから優先順位をつけていくことはしましたが、逆に自分からプロジェクトを創り出すことはできていなかったなと強く感じていました。長期的に Service Worker という API をどうしていきたいか、全体のチームの中で僕たちのチームはどうあるべきか、といったようなことがうまく位置づけられず苦労しました。その中でも特に、自分たちの作っている機能を使ってくれているチームとの距離が遠いように感じられ、何をすると喜ばれるのかというのがわかりづらかったという点が難しかったです。

自身の中において、テックリードとして仕事をするというのは、入社時から決めていたキャリアにおける一つのマイルストーンでした。
それを達成した今、これらの経験を踏まえて改めて自分は今後どうやって仕事をしていきたいかを考えたところ、自分のそもそもの原動力は「面白いものをつくりたい」というところにあるように感じられました。そのためには、プロダクトを使っている場所に近く、何をしたら価値が提供できるか自分で考えられるようなところで仕事をするのが近道に思えます。

そこでチームを移ることを考えましたが、残念ながら今年はコロナ禍で募集がほとんどなかったです。またそれだけではなく、5年後や10年後までこの会社で働くよりは大きく違う環境で働くという経験を積みたいと考えたこと、もともとプログラミング教育には大変興味があり、さらに幸いにも転職を考えていたタイミングで大学の後輩に声をかけて頂いた縁なども重なり、この度は Progate に転職することにしました。

プログラミング教育に期待すること

プログラミングが必修化されることもあり、プログラミングを学ぶことに興味を持ってくださっている方は多くいるように思います。

一方で、学習し始めのところで躓いてしまってそれ以上の勉強にうまくつながっていないようなケースは多くあるように感じています。本やサイト、動画、プログラミングスクールといったコンテンツは世の中にたくさんあります。しかし、実際に「価値のあるものを創る」というプログラミングにおいて最も面白い経験をできるようなレベルまで、体系だった学習を一貫して多くの人に提供できるようなものはあまりなさそうです。 Progate では、そのような「創れる人」をたくさん生み出せるコンテンツを創っていけたらいいなと思っています。

さいごに

これまでお世話になった同僚やインターン、イベントでお話させていただいた方々には大変感謝しております。
5年間という期間は過ぎてみるとあっという間でしたがとても楽しく過ごさせていただきました。ランチいけるようになったら誘ってください!

これからも新しい環境で引き続き頑張っていこうと思います。応援のほど、よろしくお願いいたします!*3

*1:エイプリルフールじゃないよ!

*2:写真見返していたらお腹空いてきた

*3:欲しい物リストもこっそり公開しておきます!

2020年に買ってよかったもの

Amazonの購入履歴とか見ながら振り返ってみた。

小さい充電器

GaNってかいてあるやつ。小さくて持ち運びにとても便利。

Chromebook (Lenovo IdeaPad Duet)

タブレットとしてAndroidのアプリもつかえる。キーボードもスタンドもついているので、漫画リーダー+レシピリーダー+ちょっとした検索用デバイスとして活躍してる。コスパとても良かった。

食洗機(NP-TH-3)

在宅だとご飯作る必要があるけど、食洗機があると食器洗うのが自動なので洗い物のことを考えてやる気が減ることがない。食器をちょっと余分に使ったりしても罪悪感もなくとても良かった。

低温調理器真空パック器

在宅勤務だとお昼を作らないといけないけど仕事中は何作るか考える余裕ないのでいつも困ってたところ、低温調理器が来て鶏ハムを作り置きするようにしてから昼飯にあまり困らなくなった。日曜に1kg(おおよそ4枚)くらい作っておけば平日はそれ食べればだいたいいけるのでとてもいい感じ。 BONIQの加熱時間基準表をとても参考にしてる。

キーホルダーの着脱アダプタ 

自転車の鍵をキーケースにくっつけるために購入。ワンタッチでつけ外しできてとても便利。

Huawei Watch FIT

デジタルウォッチ。電池が10日(常時点灯だと5日)持つのが売り。ほんとに5日くらい持つので、ちょっとした旅行とかなら充電心配しなくていいのでとても良い感じ。コスパ

Insta360 One X2

360度カメラ。録画が基本の使い方。スノボの録画用に買ったけど、雑に写しても思ったより写りが良くて音も結構きれいに取れるのでとてもいい感じ。一緒に買った公式の1.2mの自撮り棒は使う前は少し短いかなとか心配してたけど、使ってみると丁度いい感じ。

Sony α6600

きれいに撮れるよ。最近Webカメラとしても使えるようになった。写真撮るの楽しい。 ズームレンズキットでついてくるレンズで色々撮れて楽しい。

ISUCON10 予選に出てみました

練習もしてなかったのでどうせ本戦通らんでしょ〜という感じでお昼の14時頃から気楽にやり始めたものの、思ったより手詰まりにならず常時やることがたくさんあり、最終的には時間ギリギリまでいろいろ試していました。 最後のベンチマークの結果は1768でした。

時系列を追ってやったことを書いていきます。

はじめの準備

  • vscodesshする
  • localhostでサイトが開けることを確認する
  • gitでソースを管理する
  • etckeeperをいれて/etcをgitで管理する
  • goじゃなくてnodejsを有効にする

アプリの動作確認

まずはじめにベンチマークを回し、動作確認をしました。 topを見てみたところ、mysqlが80%, nodeが20%程度CPUを使っていることがわかりました。 つまり、mysqlを別のマシンに載せ替えるだけで20%程度はスコアが上がりそうです。

また、どういう動作をするアプリなのかということをサイトを操作してみて、以下のような点を確認しました。 はじめに全体のおおよその動作を確認することで、コードを読むときに何をしようとしているのかがわかりやすくなったと思います。

  • トップページ
    • たくさん物件や椅子がでてくる
  • 椅子の検索
  • 物件の検索
    • 同上
  • 椅子の詳細ページ
    • 物件もでてくる
    • 椅子の購入操作ができる。
  • 物件の詳細ページ
  • なぞって検索ページ
    • いかにも何かありそう

結果:初回ベンチマーク484

Slow Queryの確認

mysqlが重いことがわかったので、Slow Queryのログを有効にし、サイトを手動で動かしてみました。1

  • indexがつかわれていなさそう。初期化スクリプトも確認したところ、全く設定されていなさそうだということを確認しました。
  • なぞるページを開くと大量のクエリが出る
  • なぞるページで広い範囲を検索すると0.5sとかかかる

indexを張ってみる

SQLを思い出すために、alter tableやらcreate indexやらを書いてみました。 適当に、よく使われていそうなestate.rentにインデックスを張ってみて/initializeにPOSTして動作を確認したりしていました。

結果:変化なし

なぞってページの最適化

なぞってページのクエリについてコードを見ながら確認してみたところ、以下のような動作をしていることがわかりました。

  • まず最初のクエリでbounding box内に入る全ての物件を取ってくる
  • その後、各物件に関してselectを投げて範囲内にあるかST_Containsで確認する

SQLなのに集合処理をしていない・・・

ということで、クエリを1つにしてみることにしました。 まず、latitude, longitudeをひとつのGeometry型のカラムに入れるための初期化スクリプトを1つ増やしました。

ALTER TABLE isuumo.estate ADD place_point GEOMETRY;
UPDATE isuumo.estate SET place_point = POINT(latitude, longitude);
ALTER TABLE isuumo.estate MODIFY COLUMN place_point GEOMETRY NOT NULL;

また、これを使って以下のようなクエリを投げると、1つのクエリで取ってこれるようになります。

SELECT * FROM estate WHERE ST_Contains(ST_PolygonFromText(%s), place_point) ORDER BY popularity DESC, id ASC LIMIT %d;

最後に、初期化スクリプトにSPATIAL INDEXを足しました。

CREATE SPATIAL INDEX place_point_index ON estate(place_point); 

結果: 509 (without SPATIAL INDEX), 627 (with SPATIAL INDEX)

botに503を返す

nodeのCPUがあまりそうだったので、expressでパパっとスキップするmiddlewareを書きました。 正規表現はレギュレーションのドキュメントからコピペしてそのまま貼りました。

const REGEXPS = [
  /ISUCONbot(-Mobile)?/,
  /ISUCONbot-Image\//,
  /Mediapartners-ISUCON/,
  /ISUCONCoffee/,
  /ISUCONFeedSeeker(Beta)?/,
  /crawler \(https:\/\/isucon\.invalid\/(support\/faq\/|help\/jp\/)/,
  /isubot/,
  /Isupider/,
  /Isupider(-image)?\+/,
  /(bot|crawler|spider)(?:[-_ .\/;@()]|$)/i,
]

module.exports = function (req, res, next) {
  const ua = req.get('user-agent');

  for (regex of REGEXPS) {
    if (ua.match(regex)) {
      res.status(503).end();
      return;
    }
  }

  next();
} 

結果:670

mysqlのお引越し

mysqlを別インスタンスに移住しました。

mysqld.confのbind-addressを消して、nodeサーバーのIPのためのユーザーを作ってあげればOKでした。

mysql> CREATE USER 'isucon'@'[SOURCE_IP]' IDENTIFIED BY 'isucon';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON *.* TO 'isucon'@'[SOURCE_IP]';
Query OK, 0 rows affected (0.00 sec)

+20%程度かなと思っていたので、まぁ妥当な数字が出たように思います。 CPU使用率はmysqlがほぼ100, nodejsは20%程度でした。 node側のCPUを使い切るのが目標になります。

結果:786

椅子購入クエリの最適化

なんとなく(←ひどい)「悪いUPDATEいるんじゃないかなあ」と思ったので眺めてみたところ、椅子の購入でわざわざSELECTしてからUPDATEしているのを見つけました。 stockがゼロだった場合には404を返す必要があるので、affectedRowsを使って以下のように変更しました。

    const id = req.params.id;
    await beginTransaction();
    const result = await query("UPDATE chair SET stock = stock - 1 WHERE id = ? AND stock > 0", [
      id,
    ])
    if (result.affectedRows != 1) {
      res.status(404).send("Not Found");
      await rollback();
      return;
    }
    await commit();
    res.json({ ok: true });

結果:851

mysqldumpslowの確認

ここで、次に何をするかを見極めるため、もうすこしslow queryをきちんと見てみることにしました。 mysqldumpslowをつかうとクエリごとの集計結果が見られるということだったので、使い方を調べつつやってみました。

$ sudo mysqldumpslow -s t /var/log/mysql/mysql-slow.log

Reading mysql slow query log from /var/log/mysql/mysql-slow.log                                                                                                                                                                                                                                                                                                                                                                  
Count: 829  Time=0.08s (65s)  Lock=0.00s (0s)  Rows=20.0 (16580), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                 
  SELECT * FROM chair WHERE stock > N ORDER BY price ASC, id ASC LIMIT N                                                                                                                                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 351  Time=0.09s (31s)  Lock=0.00s (0s)  Rows=25.0 (8775), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                  
  SELECT * FROM estate WHERE rent >= N  AND rent < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 276  Time=0.11s (29s)  Lock=0.00s (0s)  Rows=20.0 (5520), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                  
  SELECT * FROM estate where (door_width >= N AND door_height>= N) OR (door_width >= N AND door_height>= N) OR (door_width >= N AND door_height>=N) OR (door_width >= N AND door_height>=N) OR (door_width >= N AND door_height>=N) OR (door_width >= N AND door_height>=N) ORDER BY popularity DESC, id ASC LIMIT N

Count: 204  Time=0.08s (16s)  Lock=0.00s (0s)  Rows=25.0 (5100), isucon[isucon]@[...]
  SELECT * FROM estate WHERE rent >= N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 144  Time=0.08s (11s)  Lock=0.00s (0s)  Rows=25.0 (3600), isucon[isucon]@[...]
  SELECT * FROM chair WHERE price >= N  AND price < N  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 111  Time=0.08s (9s)  Lock=0.00s (0s)  Rows=25.0 (2775), isucon[isucon]@[...]
  SELECT * FROM estate WHERE door_width >= N  AND door_width < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 96  Time=0.08s (7s)  Lock=0.00s (0s)  Rows=25.0 (2400), isucon[isucon]@[...]
  SELECT * FROM estate WHERE door_height >= N  AND door_height < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 144  Time=0.05s (7s)  Lock=0.00s (0s)  Rows=1.0 (144), isucon[isucon]@[...]
  SELECT COUNT(*) as count FROM chair WHERE price >= N  AND price < N  AND stock > N

Count: 171  Time=0.04s (6s)  Lock=0.00s (0s)  Rows=25.0 (4275), isucon[isucon]@[...]
  SELECT * FROM estate WHERE rent < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 72  Time=0.09s (6s)  Lock=0.00s (0s)  Rows=25.0 (1800), isucon[isucon]@[...]
  SELECT * FROM chair WHERE color = 'S'  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N 

Count: 111  Time=0.06s (6s)  Lock=0.00s (0s)  Rows=1.0 (111), isucon[isucon]@[...]
  SELECT COUNT(*) as count FROM estate WHERE door_width >= N  AND door_width < N

Count: 69  Time=0.08s (5s)  Lock=0.00s (0s)  Rows=25.0 (1725), isucon[isucon]@[...]
  SELECT * FROM chair WHERE kind = 'S'  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 78  Time=0.07s (5s)  Lock=0.00s (0s)  Rows=25.0 (1950), isucon[isucon]@[...]
  SELECT * FROM chair WHERE height >= N  AND height < N  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

Count: 96  Time=0.06s (5s)  Lock=0.00s (0s)  Rows=1.0 (96), isucon[isucon]@[...]
  SELECT COUNT(*) as count FROM estate WHERE door_height >= N  AND door_height < N

Count: 66  Time=0.08s (5s)  Lock=0.00s (0s)  Rows=25.0 (1650), isucon[isucon]@[...]
  SELECT * FROM estate WHERE door_width >= N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N

半分位の時間が

SELECT * FROM chair WHERE stock > N ORDER BY price ASC, id ASC LIMIT N

のクエリに取られていそう・・・

chairの値段順クエリ

price ASCが困っていそうだったので、indexを張ってみた。

CREATE TABLE isuumo.chair
(
    id          INTEGER         NOT NULL PRIMARY KEY,
    name        VARCHAR(64)     NOT NULL,
    description VARCHAR(4096)   NOT NULL,
    thumbnail   VARCHAR(128)    NOT NULL,
    price       INTEGER         NOT NULL,
    height      INTEGER         NOT NULL,
    width       INTEGER         NOT NULL,
    depth       INTEGER         NOT NULL,
    color       VARCHAR(64)     NOT NULL,
    features    VARCHAR(64)     NOT NULL,
    kind        VARCHAR(64)     NOT NULL,
    popularity  INTEGER         NOT NULL,
    stock       INTEGER         NOT NULL,
    INDEX price_index (price)
);

結果:1187

再度mysqldumpslow

Reading mysql slow query log from /var/log/mysql/mysql-slow.log                                                                                                                                                                                                                                                                                                                                                                  
Count: 302  Time=0.11s (32s)  Lock=0.00s (0s)  Rows=20.0 (6040), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                  
  SELECT * FROM estate where (door_width >= N AND door_height>= N) OR (door_width >= N AND door_height>= N) OR (door_width >= N AND door_height>=N) OR (door_width >= N AND door_height>=N) OR (door_width >= N AND door_height>=N) OR (door_width >= N AND door_height>=N) ORDER BY popularity DESC, id ASC LIMIT N                                                                                                             
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 348  Time=0.08s (29s)  Lock=0.00s (0s)  Rows=25.0 (8700), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                  
  SELECT * FROM estate WHERE rent >= N  AND rent < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 165  Time=0.07s (12s)  Lock=0.00s (0s)  Rows=25.0 (4125), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                  
  SELECT * FROM estate WHERE rent >= N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 132  Time=0.08s (10s)  Lock=0.00s (0s)  Rows=25.0 (3300), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                  
  SELECT * FROM estate WHERE door_height >= N  AND door_height < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 135  Time=0.07s (9s)  Lock=0.00s (0s)  Rows=25.0 (3375), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                   
  SELECT * FROM chair WHERE price >= N  AND price < N  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 96  Time=0.08s (7s)  Lock=0.00s (0s)  Rows=25.0 (2400), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                    
  SELECT * FROM estate WHERE door_width >= N  AND door_width < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 135  Time=0.05s (7s)  Lock=0.00s (0s)  Rows=1.0 (135), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                     
  SELECT COUNT(*) as count FROM chair WHERE price >= N  AND price < N  AND stock > N                                                                                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 132  Time=0.05s (6s)  Lock=0.00s (0s)  Rows=1.0 (132), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                     
  SELECT COUNT(*) as count FROM estate WHERE door_height >= N  AND door_height < N                                                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 72  Time=0.09s (6s)  Lock=0.00s (0s)  Rows=25.0 (1800), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                    
  SELECT * FROM chair WHERE kind = 'S'  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 177  Time=0.04s (6s)  Lock=0.00s (0s)  Rows=25.0 (4425), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                   
  SELECT * FROM estate WHERE rent < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 66  Time=0.09s (5s)  Lock=0.00s (0s)  Rows=25.0 (1650), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                    
  SELECT * FROM chair WHERE color = 'S'  AND stock > N ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                                                                                 
Count: 96  Time=0.05s (5s)  Lock=0.00s (0s)  Rows=1.0 (96), isucon[isucon]@[...]                                                                                                                                                                                                                                                                                                                                       
  SELECT COUNT(*) as count FROM estate WHERE door_width >= N  AND door_width < N                                                                                                                                                                                                                                                                                                                                                 
                                                                                                        

次はestateテーブルに対するdoor_width/heightととrentに関するクエリが時間がかかっているっぽいですね。

door_width/heightにインデックス

とりあえず、door_width/heightにインデックスを張ってみることにしました。

CREATE TABLE isuumo.estate
(
    id          INTEGER             NOT NULL PRIMARY KEY,
    name        VARCHAR(64)         NOT NULL,
    description VARCHAR(4096)       NOT NULL,
    thumbnail   VARCHAR(128)        NOT NULL,
    address     VARCHAR(128)        NOT NULL,
    latitude    DOUBLE PRECISION    NOT NULL,
    longitude   DOUBLE PRECISION    NOT NULL,
    rent        INTEGER             NOT NULL,
    door_height INTEGER             NOT NULL,
    door_width  INTEGER             NOT NULL,
    features    VARCHAR(64)         NOT NULL,
    popularity  INTEGER             NOT NULL,
    INDEX rent_index (rent),
    INDEX door_size_index_another (door_width, door_height, popularity, id)
);

結果:1302

popularity desc, id ascとの苦闘

mysqlコマンドでいろいろcreate indexをしつつexplainを見てインデックスがうまく使われているか見てみました。

  SELECT * FROM estate WHERE rent >= N  AND rent < N  ORDER BY popularity DESC, id ASC LIMIT N OFFSET N                                                                                                                                                                                                                                                                                                                          

このクエリを最適化しようと思ったとき、rentとpopularity, あとidの3つにindexが張られていれば良さそうです。 しかし、create index idx on estate(rent, popularity, id)なんてやってもindexは使われていないようです。なんで・・・? 2

ORDER BYの最適化に関する公式のガイドを見てみたところ、どうやらDESCとASCが一緒だとだめだったりするようです。 これは最後まで解決しませんでした。。。

rent >= N and rent < N

2番めに遅いクエリの実際の値をみてみると以下のようなものでした。 SELECT * FROM estate WHERE rent >= 50000 AND rent < 100000 ORDER BY popularity DESC, id ASC LIMIT 25 OFFSET 75;

rent >= 50000 AND rent < 100000で検索をかけてみると、かなりのクエリがこのレンジのようです。 同じようなクエリが多いため、とりあえずクエリの結果をキャッシュしておいて同じクエリならそのまま返すようにすればよさそうです。

ということで、res.jsonで返すJSONmemcachedに取っておいて、もしキャッシュがあれば使うことにしました。

使うところのコードはこんな感じ。とても雑に検索の内容とqueryParamsなどをキー(memKey)に突っ込んでいます。時間がなかったことが伺えますね・・・

const memjs = require("memjs");
const client = memjs.Client.create();

...

app.get("/api/estate/search", async (req, res, next) => {

  ...

  const mGet = promisify(client.get.bind(client));
  const mSet = promisify(client.set.bind(client));

  const memKey = `${searchCondition}-${queryParams.join('_')}-${pageNum}-${perPageNum}`;
  try {
    const result = await mGet(memKey);
    if (result) {
      res.setHeader('content-type', 'application/json; charset=utf8')
      res.send(result);
      return;
    }
  } catch (e) {
    next(e);
    return;
  }

  ...

  try {

    ...

    const result = {
      count,
      estates: camelcaseKeys(estates),
    };
    await mSet(memKey, JSON.stringify(result), {expires: 60});
    res.json(result);
  } catch (e) {

    ...

公式のmemjsのドキュメントにはcallbackしか書かれていないですが、どうやらこれ、Promiseも返せるっぽいということをあとで気づきました。

あと、物件の入稿をすると結果が変わってしまうので、入稿するとキャッシュを全て破棄するようにしました。

    ...
    await client.flush();
    await commit();
    res.status(201);
    res.json({ ok: true });

結果:1727

door_width, heightのAND/ORを節約

sshでログインした直後あたり(つまり一番最初)からずっと寝ていたもう一人のチームメンバーが、終了1時間くらい前に起きてきたのでこれをお願いしました。 椅子の縦、横、高さの小さい2辺がドアに入るかを確かめればよいので、それだけをクエリに渡すコードを書いてもらいました。

結果:1768

感想

普段仕事でやっている内容ではないのでその場で勉強しながらでしたが、徐々にスコアが上がっていくのは楽しかったです。 勉強になりそうなので、過去のISUCONなども少しずつ解いていってみたいですね。 他の方のwrite upも楽しみにしています。


  1. 本当はmysqldumpslowを使ったほうが良かったと思いますが、そのコマンドの存在と使い方はコンテスト中に知りました。。。

  2. あとでDiscord見ていた感じだと、idとpopularityを1カラムにまとめてしまえば良いみたいですね。

Git管理下のファイルを自分のローカルでだけ変更してcommitされないようにする

たとえば環境変数が書かれたファイルに対して、ローカルにだけ変更をしたいときに便利な機能、skip-worktreeというのがある。

 

stackoverflow.com

 

$ git update-index --skip-worktree .dev.development # ファイルへの更新を無視する
$ git update-index --no-skip-worktree .dev.development # ファイルへの更新を反映する

僕はskip/unskipというAliasを設定して使っています。

$ git config --global alias.skip 'update-index --skip-worktree'
$ git config --global alias.unskip 'update-index --no-skip-worktree'
$ git skip .dev.development # これでファイルをスキップできるようになる

MessageChannelを使わずにService WorkerとpostMessageをしよう

Service Workerのテストを書こうとしてメッセージのやりとりにMessageChannelを使おうとしていたところ、同僚氏からこんなことを教えてもらったので備忘録代わりにメモ。

まさにコレだった・・・・

page.html

<script>
self.onload = async () => {
  const controller = navigator.serviceWorker.controller;
  if (!controller) {
    console.log('no controller.');
    console.log('registering...');
    await navigator.serviceWorker.register('sw.js');
    console.log('registered. waiting for activation.');
    await navigator.serviceWorker.ready;
    console.log('ready. please reload the page.');
    return;
  }

  navigator.serviceWorker.addEventListener('message', e => {
    console.log(`received: ${e.data}`);
  });
  navigator.serviceWorker.controller.postMessage('hoge!');
  console.log('sent: hoge!');
}
</script>

sw.js

let x = 0;
self.addEventListener('message', e => {
  console.log(`sw onmessage: ${e.data}`);
  e.source.postMessage(`${++x}`);
});

簡単!

ランダムパスワードジェネレーターをつくった

最近は基本的にログインパスワードは全部ランダムにして、Chromeに覚えさせるようにしてます。ただ、WebサービスってたまにChromeの自動生成パスワードが使えない(出てこない)ときあるじゃないですか。そういうときに適当にウェブアプリ検索してランダムパスワード生成してたんですが、なんか知らないサービス使うの怖いなっておもったので自分でつくってみました。

Random password generator

 

ソースはここで公開してます。

github.com

 

Web App ManifestとServiceWorkerつかってオフライン対応したので、スマホで開いてホーム画面追加しておくといつでも使えて便利だったりします。

 

ところで、いけてるCSSってどうやって書いたらいいんですか。ウェブよくわからない・・・(´・ω・`)

 

--

つくったのはずいぶん前なんですが、検索しても出ないのは悲しいのでとりあえず記事にしてみたっていうのは内緒。

電源入れたらRaspberryPiのターミナルがGUIで全画面表示されるようにする

概要

デモ展示では電源を刺したら自動でプログラムが起動するようにするのが失敗しないコツだと思います。ただ、そのへんの設定をパパっとできるようにまとまってる記事とかあまり見つけられなかったので、備忘録がてら、今回行った設定を書いておきます。

今回はコンソールで日本語の表示をいい感じにしたいと思っていろいろ試しました。 その結果、GUIありでインストールして、lxterminalを全画面で自動起動した上でプログラムを走らせることにしました。もともとGUIは必要ないと思ってRasbian Streach Liteをインストールしていたので、GUIを入れるところからやりました。

GUIのセットアップ

これでGUIに必要な一式がはいります。

$ sudo apt install -y rpi-chromium-mods  python-sense-emu python3-sense-emu python-sense-emu-doc realvnc-vnc-viewer xserver-xorg xinit raspberrypi-ui-mods lightdm

インストール後、raspi-configを使ってGUIで起動するように設定すると再起動を促され、言われるがままにするとGUIが出ます。

$ sudo raspi-config

自動起動するプログラムの設定

まず適当に起動用シェルスクリプトを作ります。なんかautostartとかいうのはダブルクオートとかを勝手に展開しちゃうっぽくてシェルに渡るコマンドではうまくクオートされないという問題に悩みました。 ここではしょうがないのでターミナル起動用とターミナル内で実行するスクリプトの2つスクリプトを作ります。

/home/pi/autostart.sh

#!/bin/bash
lxterminal -t "Title for the demo" -e /home/pi/autostart_terminal.sh

/home/pi/autostart_terminal.sh

#!/bin/bash
while :
do
  echo "Hello world!"
  # 実行したいコマンド
done

次に、起動時に自動で実行するように設定します。

$ vi ~/.config/lxsession/LXDE-pi/autostart

なかみはこんな感じ。はじめの2つは消しちゃってもいいですが、そのままにしておけばデスクトップが普通に起動するので、いざというときにマウス挿してパパっと復帰できると思います。

@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
# @xscreensaver -no-splash
# @point-rpi

@xset s off
@xset -dpms
@bash /home/pi/autostart.sh

lxterminalを全画面で起動する

$ vi ~/.config/openbox/lxde-pi-rc.xml

下の方にある<applications>を探して、その中にこんなものを置きます。

<application class="Lxterminal" name="lxterminal">
        <fullscreen>yes</fullscreen>
        <!--<maximized>yes</maximized>-->
</application>

fullscreenをyesにするとターミナルの真っ黒画面が全画面で出るのであたかも普通のCUIのコンソールのように見えます。 maximizedをyesにするとターミナルのタイトルが普通に見えるので、その場合にはlxpanelやらpcmanfmやらはコメントアウトしたほうがよさそうです。

ここまでで自動起動の準備はOKです。

日本語を出せるようにする

まずフォントを入れます。 たとえばこのへんとかからNoto Sans CJK JPとか落としてきて入れるとよさそうです。

$ mkdir ~/.fonts
$ cd ~/.fonts
$ wget https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip
$ unzip NotoSansCJKjp-hinted.zip

つぎに、lxterminalで使うフォントやらなんやら変えましょう。デモの場合は文字が大きい方が見栄えがよいので、文字も大きくします。また、スクロールバーやメニューバーも消すと良いでしょう。 以下のように設定を変更しました。(これら以外はそのまま)

~/.config/lxterminal/lxterminal.conf

fontname=Noto Sans Mono CJK JP 16
hidescrollbar=true
hidemenubar=true
hideclosebutton=true
hidepointer=true

さいごに、ロケールを変えます。raspi-configでLocalization OptionsからChange Localeを選んで、ja_JP.UTF8とかを選択すると良いと思います。

$ sudo raspi-config

ここまででターミナルに日本語が出るようになっていると思います。

カーソルを消す

unclutterを入れておくと、操作がないときにカーソルが消えます。デモするときに邪魔なカーソルが必要なくなるのでとても便利です。

sudo apt install unclutter

また、unclutterを自動起動するために以下を~/.config/lxsession/LXDE-pi/autostartに追記します。

@unclutter

最後に再起動したら完成です。

さいごに

意外と自動でGUIのプログラムを起動するための手順をまとめた記事がなかったので今回まとめてみました。デスクトップ環境のシステム、あまり良くわからない・・・。 こうすると良いよ!みたいなコツとかあったらぜひ教えてください。