isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

Web広告系のトラッキングタグは何でこんなに遅いんだ。

お仕事でECサイトを作った。
僕はWebページの高速化が得意だ。
広告系のトラッカーを入れるまではPageSpeed Insightsが97点だった。

 

それから言われたサードパーティの広告系のタグを入れた。
FacebookのトラッキングタグにYahoo広告のトラッキングタグに......その他いろいろ。

 

それらを入れただけでPageSpeed Insightsが80点を切った。
ため息が出る。
もう少し気を使ってくれ。
Google Analyticsくらいは良い奴だと思っていたのに。

 

こういうのは全部WebWorkerに持っていく流れにならないかな。

 

もしくは、どうせサードパーティCookieは消されていくんだし、サーバーサイドからログを送信する方向になってほしい。サーバーからIPとUAとURLを送れば大体事足りるでしょう。

AtCoderの精進管理ツールを作った

AtCoder水色になりました。
丁度ABCを全埋めしたタイミングでした。
ABCを全埋めしたら水色くらいなれるという言説があるようですが、ある程度確からしと思います。

さて、僕はAtCoderで二周目に入ろうと思いました。
AtCoder Problemsは緑色に染まってしまったので、二周目以降の進捗を管理できるツールを作りました。

atcoder-chart.net

 

問題と難易度のデータはAtCoder ProblemsさんのAPIを使わせていただきました。

解けた問題は○をつけ、少しでも詰まった問題は△を付けます。
次回以降は△を付けておいた問題だけを解いていきます。

なぜこんなものを作ろうと思ったのか。
僕はチャート式を使って数学の勉強をする感覚でAtCoderを使うのがいいと思っています。
頑張って難しい問題に挑むより、解答方法丸暗記でもいいから低難易度の問題は必ず全部解けるようにしたほうが成長が速いのではないかと考えています。

 

僕は水色コーダーです。
自分は今まで「茶色問題までなら瞬殺できる。緑色問題もちょっと考えたら解ける」と思っていました。
しかし、このツールで記録を付けながら二周目をした所、驚くことが分かりました。
実は茶色問題でも1/3くらいは悩んだり間違えたりしていました。緑問題の中にも解説を見ないと解けない問題がそこそこありました。
二周目なのに!
一周目の僕は緑色問題が解けたら「緑色余裕!」と思い、解けなかったら「今のはたまたま運が悪かった」と思っていました。実は「運が悪かった」が1/3以上を占めていたのです。運ではなく実力不足です。

あなたも、○○色くらいなら瞬殺出来ると思っているはずです。多分大して解けてないと思います。

僕と同じように、下位問題を全部潰しておきたい人はぜひ使ってください。

CSSの競技プログラミングを作りたい

最近競技プログラミングというものを始めた。

これは大変素晴らしい。
自分の練習のための教材になるし、他人のスキル感を大雑把に掴むことが出来る。
あらゆる技能についてこういう仕組みが備わっていくべきだ。

ところで、最近マークアップエンジニアの募集をせねばならなくなった。HTMLとCSSを書くお仕事の募集だ。

マークアップエンジニアも難しい仕事だ。僕はHTMLのセマンティクスを重視したHTMLを書いてほしいと思っている。そして変更に強いCSSを書いてほしい。様々な画面サイズで意図したとおりに表示してほしい。パフォーマンスについても最低限の理解をしてほしい。Pictureタグとか。deferとか。古いブラウザのことは考えなくていいと思っているが、モダンブラウザ間の差異の吸収の仕方は知っておいてほしい。

専門学校に募集をかけようという話になったことがある。その場にいた専門学校卒の人がこういった。「専門学校には一つのクラスに2、3人だけ凄い人がいて、それ以外は本当に何もできない」。そんな人を引き当ててしまったら困る。まともな人を見分けたい。

マークアップエンジニアのスキルを測るのは難しい。一般的に作品を見せてもらうということにしているらしい。しかし、この方法は問題が多い。まず、どれくらい時間をかけて作ったのかわからないということだ。そして、網羅的な知識を持っているか判断しづらい。
応募者側も、何を作ったら認めてもらえるのかわからないから困っちゃうだろう。つまり、こちらは、基礎技術が有るか無いかを見たいのに、応募者側はネタを見せたくなっちゃうというギャップがある。

だったら、明確なお題がある方が混乱させないだろう。

そこでこういうシステムを考えた。

AtCoderみたいに時間を決めてコンテストを開催する。
問題はワイヤーフレーム形式で出題される。
色や影や角丸の指定がある場合は、画像だとわかりにくいので文章で伝えたほうがいいだろう。
提出されたHTMLとCSSサーバーサイドでレンダリングする。Headless Chromeなどを使えばいいだろう。この時、様々な画面サイズを指定してレンダリングする。
そして、何かしらの判定技術で正誤を付ける。画像処理的に採点するという方法があるし、各DOM要素のclientHogeをチェックしていく方法でもいいだろう。
最後に、正解にかかった時間を元にレーティングを付ける。

こういうアイディアも考えた。
1問提出するごとに、変更依頼が発生するといいのではないか。
AtCoderは1問目と2問目は全く関係ない問題になっていることが多いが、あえて1問目と2問目に繋がりを持たせる。すると、変更に強いマークアップが出来るほど有利になる。

これは採用に困ってる企業からお金を貰えそうだ。

 

2019年の振り返り

去年、人生の五カ年計画を描いた。

 

blog.isyumi.net

 

このとき僕は25歳で30歳までに到達したい目標を書き出したものだ。

26才の一年間でどれだけ達成できたか振り返ってみる。

達成したこと

善処した上で諦めたこと

五箇年計画から外そうと思ったこと

  • レッドコーダーになる

  • 数学検定一級

来年の課題

  • イエローコーダーになる

  • LPIC Level3になる

再来年以降に持ち越す課題

  • TOEIC 800点

  • Webフロントエンドのベストプラクティスホスティングサービスを作る

  • Google Cloud Certified

  • データベーススペシャリスト

  • セキュリティスペシャリスト

  • 年収1000万になる

  • CPUを作る

  • OSを作る

  • 言語を作る

  • RDBの理論面を勉強する

  • 型システムの理論面を勉強する

  • 素晴らしい数式処理システムを作る

  • 暗号論を勉強する

  • 新しいWebの仕様を策定しバーナーズリーの後継者になる

  • 絵を描けるようになる

  • 何か楽器ができるようになる

新しく追加する課題

  • 宣言的に非正規化できるツールを作る

総評

思ったより数学検定準一級にてこずったため人生が遅れた。

もう一級は諦めようと思う。

応用情報技術者ネットワークスペシャリストを連続撃破できたのは嬉しい。

AtCoderを始めた。去年の段階で気前よく「レッドコーダーになる」などと恥ずかしいことを言ってしまった。いざ始めてみると、全力で頑張っても黄色が限界だと思った。

去年の段階でぼんやりしていたWebアプリケーション開発の未来像が見え始めてきたので、いくつかの目標を削除してより具体的な目標を入れた。

僕は今年、Webアプリケーション(スマホアプリ含む)開発の生産力を100倍にする方法を見つけたかもしれない。どんなフレームワークがあれば100倍の速さでWebアプリケーションを作れるか分かった気がする。だから、僕はそれを実現することをライフワークにしようと思った。

最強のサーバーサイド・フレームワーク『イシュミ・デルティスト』

 

Diff as a Service

差分を扱うのが得意なサーバーサイドフレームワークを作りました。

 https://github.com/rikuTanide/isyumi_deltist 

概要

僕はFirestoreにViewを作る!

問題意識

Firestoreはどこまで正規化するべきか問題があります。
バックエンドを書いている時の僕はできるだけ正規化したい派です。
しかし、クライアントを書いている時の僕はそのまま表示できる形式で渡してほしい派です。
また、僕は「非正規形のデータを直接いじるプログラムを書くと絶対間違える」という硬い信念を持っています。
なので、非正規形データは正規形データから純粋関数で展開するべきだと思います。

解決策案

単純な解決策

正規化したデータと非正規化したデータを別々にFirestoreに保存する。正規形データが変更されたら非正規形データをまるごと更新する
豪快にやっちゃいます。
データ量が少ないなら一番簡単な方法です。
しかし、データ量が増えてきたら無理です。read/writeの遅延や課金が大きくなります。

現実的な解決策

正規化したデータと非正規化したデータを別々にFirestoreに持つ。正規形データが変更されたら非正規形データを更新する 。ただし、どの正規形データへの更新がどの非正規データに伝播するか細かくプログラミングする

今の所の最善策だと思います。

このやり方には2つの欠点があります。
一つはテーブルとビューの依存関係を洗い出すのがめんどくさいことです。どのテーブルへの変更がどのビューのどの行に影響するかを確実に洗い出すのは難しいことです。

もう一つは、最適化をしたいならコードを沢山書かないといけないことです。例えば商品ごとの売上なら、売上が発生するごとに売上一覧を集計して出すより、前回の計算結果に今回の売上を加算したほうが速いです。
このような最適化の余地はたくさんありますがめんどくさいです。

夢想的な解決策

全部オンメモリでビューを計算する

まず、全てのテーブルを元に全てのビューをつくる関数を一つ用意します。
メモリに現在のテーブルを載せておき、どこかに変更が発生したらその関数を実行します。
そして前回作ったビューと今回のビューの差分をチェックしてFirestoreを更新しに行けばいいかと思います。
この方法の最大の欠点はメモリ使用量です。
EC2でも、2~4GBよりたくさんのメモリを使えるインスタンスは高いです。

最終解決策 イシュミ・デルティスト

そこで僕は、フレームワークを作りました。

プログラマはこの2つをします。

  • 正規化したテーブルのスキーマDSLで記述
  • テーブルを非正規形に展開する宣言をDSLで記述

フレームワークはこれをします。

  • 起動時
    • JSONなどでエクスポート出来る形式でビューを実体化する
  • テーブルの変更イベント受信時
    • ビューを差分更新する
    • どんな差分が発生したのかを出力する

テーブルとビューのデータはディスクに保存してあるのでメモリをほとんど使いません。
また、どのビューのどの行がどのテーブルのどの行に依存しているかについて細かい依存グラフを作成しているので、最短距離で差分更新できます。

あとは、このフレームワークが返してきたDIFF情報をそのままFirestore APIに書き込めばOKです。
Viewができた!

使い方

デルティストは単純なステートマシンです。
ReduxのReducerに似ています。
Tableへの書き込みイベントを引数にビューの差分が帰ってくる関数です。
返ってくるのは新しいビューではないです。
その中で変更があった行だけが返ってきます。
疑似コードですがこんな感じに動きます。

売上一覧テーブルに鉛筆の売上を挿入したら「月ごとの売上合計ビュー」と「商品ごとの売上ビュー」が1行ずつ変更された
void main() {
  var db = new Database();
  var diff = db.insert("Sales", {productID: 'pencil', date: '2019-09-01' });
  print(diff); 
  
}

出力
[
 {
   viewName: 'SalesParMonth' ,
   data: {year: 2019, month: 8, sum: 1000},
 },
 {
   viewName: 'SalesParProducts',
   data: {productID: 'pencil', productName: '鉛筆', sum: 500 }
 }
] 

Firestoreで使うことを想定していますが、入出力は単純な構造体なので何にでも使えます。
ブラウザからWebSocketで直接つなぐこともできますし、MySQLにMaterialized Viewを作る用途で使うこともできます。

ディスクを使うのでLambdaやGAEなどで使うことはできません。
EC2インスタンスが一つ必要です。

ロードバランスやSPOF回避の仕組みは何も考えていません。

実装方法

デルティストはビューを作る途中の仮想テーブルも全てビューとみなし実体化してディスクに保存しています。
デルティストでは3個以上のテーブルやビューをJOINする方法はありません。JOINは2個に限ります。
3個以上のテーブルやビューをJOINしたければパラマス式トーナメントにします。
そしてそれぞれを実体化します。
例えば4個のテーブルをJOINしてビューを作るなら、合わせて3つビューが実体化されることになります。
下の図の場合、本当にほしいビューがView3ならこうなります。

Table1 -┐
        |-View1┐
Table2 -┘      |-View2-┐
Table3 --------┘       |-View3-
Table4 ----------------┘

テーブルが更新されると、ビューに行変更イベントを通知します。
受け取るのはテーブルに直接依存しているビューだけです。
変更イベントを受け取ったビューは、そのイベントを元に自分を変更しないといけないかどうかを判断します。
もし変更があった場合は自身を書き換えてから行変更イベントを作成しディスパッチします。
それを再帰的に行うと全てのビューが更新されます。
この時の重要なのが、最小コストでビューを更新出来るようにすることです。
例えば、TweetsテーブルとUsersテーブルをJOINしてTimelineビューを作るとします。Tweetsテーブルに一行挿入されたらUsersテーブルからそのTweetのuserIDと一致するUsersテーブルの行だけを取得してJOINしてTimelineビューに挿入します。
逆にUsersテーブルから行が削除されたらTimelineビューの中でuserIDが一致するツイートだけを削除します。

実体化したビューはRocksDBというデータベースで保存します。
RocksDBはKVSです。
TableとViewのPrimary Key == KVSのKeyです。
DBサーバーではなくCのライブラリとして使えるので運用が楽です。
デルティストは1万行の中の1行を書き換えるような処理が多いので、RocksDBのLSM-Treeというアルゴリズムと相性がいい(らしい)です。詳しくは知りません。

大変だった点

DartC++インテロップ

始めてDartC++インテロップを書きました。大変でした。

DSLの文法設計

どういう文法だったらプログラマにわかりやすくて間違えにくいか一生懸命考えました。
データを設定する時に文字リテラルでテーブル名やカラム名を指定するようなことはしたくなかったです。

// 嫌な例
row.set("Tweets" , "UserID", 10);

これは誤字脱字が発生します。
このように書きたいです。

row.set(tweets.userID, 10);

この時型を間違ったらコンパイルエラーにしたいです。

row.set(tweets.userID, "10"); // 第二引数がintじゃないのでエラー

Dartの型システムの強さに救われました。

ビュー更新の実行計画

アルゴぢからが足りないため、大変苦労しました。
やることは各演算のオペランドが増えたときと減った時にそれぞれ前回の結果からどう変わるかを1個ずつプログラミングすることです。
例えばMax関数なら

  • Insert
    • 入って来た数が既存のMaxより大きい -> Maxを更新
    • 入って来た数が既存のMaxより小さい -> 無視
  • Delete
    • 消した数が既存のMaxと等しい -> 二番目をMaxに昇格
    • 消した数が既存のMaxより小さい -> 無視

みたいなコードをいっぱい書きます。

インデックス

RocksDBはRDBのようなインデックスの機能がありません。
その代わりPrefix Seekという機能があります。キーのビット列が前方一致する行を全て列挙してくれます。
つまり主キー以外の属性で行を取得したいなら、その属性をキー、テーブルの主キーをバリューにしたKVSを別に作っておく必要があります。
例えば、Timelineビューの主キーはtweetIDだったとします。
ユーザー名が変更された時にTimelineビューを更新するにはuserIDをキー、tweetIDをバリューにしたKVSが必要です。
どんなインデックスが必要なのか全て洗い出すのが大変でした。

進捗

今のところ完成している機能は

  • INNER JOIN
  • UNION
  • SELECT
  • Optional型
  • Float型
  • 負の数

開発中・調整中なのが

  • GROUP BY
    • MAX
    • MIN
    • COUNT
    • AVERAGE
    • ARRAY
  • OUTER JOINとLEFT JOIN
    • COALESCE
  • RANK
    • DESC
    • ASC
  • WHERE
  • UPDATEの高速化(Insert/Deleteに比べてUpdateは最適化可能性が多すぎるのでだいぶサボってる)
  • 生成列
  • 初期データ

構想中なのが

  • Infinite ScrollのためのSelect機能
  • 休眠ユーザーを識別して計算量をサボる機能

です。
引き続きがんばります。

mozaic bootcampに行ってきました

mozaic bootcampとは

講義形式のトレーニングです。講師はJxckさん矢倉さんです。
4/28-5/1の4日間、10:00-17:00の時間のスケジュールでした。
Webの仕様を中心に、Web系開発者が知っておくべきことが詰め込まれていました。
mozaic.fmが4日間朝から夕方まで続く感じです。

場所

一休さんのオフィススペースを貸していただきたました。一休さん本当にありがとうございました。

形式

大雑把に

  1. ncコマンドでHTTPを手書きしてexample.comにHTTP Requestしてみる
  2. TCPサーバーを立て、そこにブラウザでアクセスして、送られてくるHTTP Requestを見てみる
  3. Wiresharkで通信を見てみる
  4. 重要なポイントについて仕様を読む
  5. Jxckさんから解説がある
  6. 仕様に他のオプションが書いてあれば、その動きを試してみる
  7. 以上を踏まえて、「〇〇(←ありがちな要件)を正しく実装するにはどうするべきか」を問われるので、自分なりに実装してみる

というサイクルでした。
質問はその場で聞くことも出来るし、所定のチャットに書き込むこともできました。
自分は同期的に聞いておかないとわからなくないそうなことは前者、補足事項のようなことは後者というように使い分けていました。

内容

  • HTTPのスタートライン( GET /foo HTTP/1.1 のこと)
  • HTTPの文法
  • Content-Length
  • Content-Type
  • Content-Encoding
  • Keep-Alive
  • Transfer-Encoding
  • HTTP Status Code
  • リダイレクト
  • noreferrer
  • HTMLのForm
  • キャッシュ
  • Cookie
  • Origin
  • Same-Origin Policy
  • RESTFul
  • Mixed Contents
  • 認証
  • TLS

などでした。
最終日は、ここまで学んだことを総動員して「正しいサインアップ・ログインフォームを作る」という実技演習がありました。
これが一番楽しかったです。

何ではないか

特定の言語・フレームワークについての解説ではないです。
最新の機能についての解説でもないです。
とりあえず動くものを作って楽しもうという感じでもないです。

きっかけ

僕はmozaic.fmのリスナーなので、mozaic.fmで知りました。
僕は最近、自分がとっくに初心者を脱しているものの、その先のステップで伸び悩んでいるという自覚がありました。
特に、僕のように独学でやってきた人は、「特定のパターンに落とし込めばWebアプリなんか簡単に作れてしまうが、しかし網羅的な知識はない」という人が多いと思います。
世の中には初心者向けの教材はたくさんありますが、中級者以上のための教材はあまり多くありません。
色々模索していた時にこのbootcampを知り、その内容の説明から「自分にちょうどいい難易度ではないか?」と思い応募してみました。

雰囲気

ガチンコ! ファイトクラブみたいなノリだったらやだなと思っていましたが、Jxckさんも矢倉さんも他の参加者のみなさんも優しかったので安心しました。
まず、他の参加者の方がとても真剣だったので、いい場所に来たなと思いました。それが一番大事なことだったかもしれません。
手を動かす→話を聞く→手を動かす、のサイクルだったので集中が途切れず最後までついていくことができました。
初日はみんな緊張していましたが、矢倉さんがいい感じにJxckさんに質問を挟んで場をほぐしてくれました。
真剣さと和やかさが7:3くらいで、とてもちょうどよかったと思います。

飲み会

最終日の後にみんなで飲み会に行きました。普段の収録の裏話や、他のpodcastの話、各技術コミュニティの話、URLはどうなっていくか、キャリアの悩み、ビザの話、好きな言語の話などをしました。

感想

そもそも僕は基本的なことをだいたい知っていると思っていました。
しかし、今回これに参加したことで、自分が如何にWebの基本をわかっていなかったか思い知らされました。
僕ぐらいのレベル感の人が、まさかHTTPのスタートライン(GET / HTTP/1.1)やFormタグやCookieに知らないことが詰まっているなんて思いもよらないです。
僕は、特定の1パターンを知っているだけで、それ以外のところを全く知りませんでした。
結構クリティカルな思い違いに気づいたこともありました。
ある意味とても不安になりました。
しかし、仕様の読み解き方をみっちり教わったので、これからは自分で乗り越えていけるだろうという感触も得ました。

素晴らしさ

まず、4日間という長さです。体力的にも結構きついし、それだけのスケジュールを抑えるのは普通の人には大変なことだと思います。(幸か不幸か僕はスケジュールを抑えるのに何の苦労もありませんでした)。しかし、Webを使って何かの機能を正確に作ろうと思ったら、Cache-ControlとCookieとSame-Origin Policyの正確な知識は確実に必要です。そしてそれを学ぶにはどうしても4日間は必要だった思います。そこを頑張り抜いた価値は合ったと思います。

次に、必ず仕様を元に説明される点が良かったです。自分が教える側だと、「こうしたら動くから」と説明してしまうことが多いです。しかし、それでは他の選択肢を知る機会がなく、欲しい機能が仕様に含まれているにもかかわらず自分で再実装してしまったりするでしょう。逆に、「〇〇ヘッダーを入れておけば安全」と思いこんでしまって深刻な脆弱性を作ってしまうことにつながります。必ず、まず仕様を読むということが大事なんだと学びました。

更に、他の参加者さんがいたことが大きかったと思います。というのも、一人で話を聞いていると、本当はわかってなくてもわかった気になってしまうことが多いからです。そんな時に、他の参加者さんが言語化しにくい疑問点を言葉にして聞いてくれるので「そうだ、僕はそこがわかってなかった」と気づくことがあります。

最後に、あまり本題の邪魔はしない範囲で、こういう機会に普段から気になっていたことを聞けるのがいいと思います。
僕は、「普通の中小企業は自前でPassword管理をせずOAuthやFirebaseを使ったほうがいいと思うがどう考えるべきか」「ブラウザにはDigest認証やクライアント証明書などのセキュアそうな認証機能があるが、いまいち流行ってないのはなぜか」と言うようなことを聞けました。

次回参加者へ

次があるのかどうか全くわからないですが、もしあったときのためのアドバイスをしたいと思います。
まず、このための言語の勉強はほとんど不要だと思います。今回はRubyを使いましたが、最低限の文法(ifとか forとかpとか)がわかれば大丈夫です。
このためにHTTPについて予習する必要も特にないと思います。
一番大事なのは普段からたくさんWebアプリを作って引き出しを増やしておくことだと思います。