Google のエンジニアを辞めて Progate を始めます。
2021年3月31日付で Google を退職し、本日4月1日から Progate でエンジニアとして働くことにしました。*1
そこで、以下の3点について、つらつらと書いていって転職エントリとすることにします。
もし他にも聞きたいことがあったらTwitterでリプでも飛ばしてください。答えられそうなことは答えるかもしれません。
東京でGoogleのエンジニアとして働いた5年間
僕は東京の Chrome チームで、 Service Worker という JavaScript の API を実装していました。
const reg = await navigator.serviceWorker.register('sw.js');
という JavaScript のコードを書いたときに Chrome の裏側でどういう C++ のコードが動くのか、詳しく知りたい人は声をかけてくださいね。
Chromium はオープンソースなので具体的な仕事内容をちょっとだけ紹介すると、たとえば Service Worker や Network Loading の大規模リファクタリングをしたり、タイムアウトの実装をしたりといった内部的なものから、JavaScript に API の追加をしたり、Docs の性能最適化なんかをしたりもしていました。 最終的に最後の1年はテックリードとして仕事をさせてもらい、ちょうど大きいプロジェクトをいくつかキリのいいところまで進められたところで退職となりました。
ところで、ブラウザというのはやや特殊なソフトウェアで、自社で仕様を決める事ができず、 W3C や WHATWG 、 IETF なんていうところで仕様の標準化をしつつ実装が行われます。そのエコシステムの中において、仕様策定から実装まで一貫してソフトウェアエンジニアとして携われたのは大変大きな経験になったと感じています。普段のコードレビューなどで大変優秀な同僚たちから設計などに関するアドバイスをもらうのみならず、議論が進まないときの進め方、時差をまたいだ仕事のしかた、大規模なリファクタリングをすすめるための手法、標準化の大事さ、オープンソースコミュニティーでの議論のしかたといった、国をまたいで多くの人とインパクトの大きい仕事をするためのコツを学べたのは大きな収穫だったかなと思います。コロナ前には、海外に年3~5回程度出張に行かせてもらっていたのもとても楽しかったです。
また、自主性が重んじられる環境だったというのもとても成長につながったと思います。 なにをするか、どうしていきたいかというのを常に自分で考え、やるべきことを自然と主張できる人が評価されているように感じました。待っていれば与えられるのではないというのは改めてなかなか簡単な環境ではないなと思いますが、常に「次は何をすべきか?」を自分で考えるためのよいトレーニングになったと思います。
ほかにも、 STEP 教育コースというプログラムでアルゴリズムやデータ構造を始めとするソフトウェア開発の基本的な知識を授業で教えたりもしました。大学時代にこんな授業があったら面白かっただろうな、という内容を3時間の授業にまとめ上げるのはなかなか苦労しましたが、教育コースを出た学生さんたちが活躍していく姿を見るととても嬉しくなります。
Google のソフトウェアエンジニアとして働くのは人におすすめできるか?というと、僕個人の意見でいえば Yes です。優秀な同僚や上司、立場に関わらず対等に技術的議論を進められること、正当な評価と正当な評価をすることに対するフィードバックループがあることなどが僕の感じる良い点です。 万人にとって楽な環境ではないと思いますが、よく言われる収入面でのメリットだけでなく、きちんと仕事をするときちんと評価されるという良い環境だと思います。
あとランチはとても美味しかったです!*2
キャリアパスについての悩み
一方で、個人的な課題もいくつかありました。 たとえば英語が苦手なことや知識・経験の不足による標準化会議での議論に参加する壁を感じたこと。これは単純に僕の勉強不足でもあるのですが、議論を引っ張っていく立場になるためにはまだまだ至らないことが多くあると感じました。
また、テックリードとしてたくさんあるタスクから優先順位をつけていくことはしましたが、逆に自分からプロジェクトを創り出すことはできていなかったなと強く感じていました。長期的に Service Worker という API をどうしていきたいか、全体のチームの中で僕たちのチームはどうあるべきか、といったようなことがうまく位置づけられず苦労しました。その中でも特に、自分たちの作っている機能を使ってくれているチームとの距離が遠いように感じられ、何をすると喜ばれるのかというのがわかりづらかったという点が難しかったです。
自身の中において、テックリードとして仕事をするというのは、入社時から決めていたキャリアにおける一つのマイルストーンでした。
それを達成した今、これらの経験を踏まえて改めて自分は今後どうやって仕事をしていきたいかを考えたところ、自分のそもそもの原動力は「面白いものをつくりたい」というところにあるように感じられました。そのためには、プロダクトを使っている場所に近く、何をしたら価値が提供できるか自分で考えられるようなところで仕事をするのが近道に思えます。
そこでチームを移ることを考えましたが、残念ながら今年はコロナ禍で募集がほとんどなかったです。またそれだけではなく、5年後や10年後までこの会社で働くよりは大きく違う環境で働くという経験を積みたいと考えたこと、もともとプログラミング教育には大変興味があり、さらに幸いにも転職を考えていたタイミングで大学の後輩に声をかけて頂いた縁なども重なり、この度は Progate に転職することにしました。
プログラミング教育に期待すること
プログラミングが必修化されることもあり、プログラミングを学ぶことに興味を持ってくださっている方は多くいるように思います。
一方で、学習し始めのところで躓いてしまってそれ以上の勉強にうまくつながっていないようなケースは多くあるように感じています。本やサイト、動画、プログラミングスクールといったコンテンツは世の中にたくさんあります。しかし、実際に「価値のあるものを創る」というプログラミングにおいて最も面白い経験をできるようなレベルまで、体系だった学習を一貫して多くの人に提供できるようなものはあまりなさそうです。 Progate では、そのような「創れる人」をたくさん生み出せるコンテンツを創っていけたらいいなと思っています。
さいごに
これまでお世話になった同僚やインターン、イベントでお話させていただいた方々には大変感謝しております。
5年間という期間は過ぎてみるとあっという間でしたがとても楽しく過ごさせていただきました。ランチいけるようになったら誘ってください!
これからも新しい環境で引き続き頑張っていこうと思います。応援のほど、よろしくお願いいたします!*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でした。
時系列を追ってやったことを書いていきます。
はじめの準備
アプリの動作確認
まずはじめにベンチマークを回し、動作確認をしました。 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のお引越し
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で返すJSONをmemcachedに取っておいて、もしキャッシュがあれば使うことにしました。
使うところのコードはこんな感じ。とても雑に検索の内容と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も楽しみにしています。
Git管理下のファイルを自分のローカルでだけ変更してcommitされないようにする
たとえば環境変数が書かれたファイルに対して、ローカルにだけ変更をしたいときに便利な機能、skip-worktreeというのがある。
$ 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を使おうとしていたところ、同僚氏からこんなことを教えてもらったので備忘録代わりにメモ。
いくつか方法があるから分かりにくいですよね。ExtendableMessageEvent.source を知らずに、わざわざ別途 MessagePort を渡して通信してるのをたまに見る気がする
— nhiroki (@nhiroki_) November 8, 2018
まさにコレだった・・・・
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の自動生成パスワードが使えない(出てこない)ときあるじゃないですか。そういうときに適当にウェブアプリ検索してランダムパスワード生成してたんですが、なんか知らないサービス使うの怖いなっておもったので自分でつくってみました。
ソースはここで公開してます。
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のプログラムを起動するための手順をまとめた記事がなかったので今回まとめてみました。デスクトップ環境のシステム、あまり良くわからない・・・。 こうすると良いよ!みたいなコツとかあったらぜひ教えてください。