オレ流学習フレームワーク
はじめに
今記事では、大学生の私が独学でコンピュータサイエンスを学習する際に使用している独自の学習フレームワークを紹介していきます。
あくまで持論なので、すべての人に最適であるとは限りません。
これから紹介するのは、いかに楽して知識を得られるかを念頭に置いたフレームワークです。
ここで言う"楽"というのは、無駄な体力を使わないという意味です。
他にこんな学習の仕方してるよ〜というのがあれば、コメントして頂けると幸いです!
主な学習フレームワーク
発信ドリブン型学習
ブログ等で発信したり、人に教えることを前提としたインプットを行います。
インプットしながらどうアウトプットするかを同時に考えることで、高速な理解が可能になります。
なぜ高速な理解が可能になるのでしょうか?
理解とは、脳の棚に整理された形で分けられている状態だと私は考えています。
人に伝えるためには、内容を整理しないといけません。
インプット中に棚に振り分けていくことで、インプット終了後には既に"理解した"という状態になっているという考え方です。
これを習得すると、書籍を読んでいる時に文章が構造的に見えてくるようになります。
遅延評価型学習
これは有名な遅延評価勉強法のことで、その知識が必要になった時に初めて勉強する方法です。
遅延評価とは、元々はプログラミング手法の一つで、必要になるまで値の評価をしないという意味の言葉です。
"必要は発明の母"という言葉がある通り必要性を感じた時の吸収力はかなり高いため、必要度に比例して吸収力も上昇します。
「直近で必要ではないけど、今学習しておきたいことだってあるぞ!」
そう思う方も少なくないと思います。
実際私も、ネットワークの仕組みやミドルウェアの特性等、学生生活で必要になることがほとんどない学習をしています。
別に必要になるのを待つ必要は無いのです。
必要を作り出せばいいのです。
先日私は、ネットワークの仕組みやミドルウェアの特性を理解しないと勝てない大会に出場することを決め、必要を作り出しました。
その時のレポート↓
CyberAgentのアドテクチャレンジで正確なDMPを作る - 爆速でGo!
このように、遅延評価型学習では必要をコントロールしていくことがキーとなってくると考えています。
フレームワークをどう組み合わせるか
爆発的な吸収力を生む遅延評価型でインプットするところから始めます。
しかし遅延評価型でバラバラに集めた情報はTipsにはなるのですが、知識にはならないと思います。
知識にするためには、ある程度網羅的なインプットが必要です。
そこで発信ドリブン型を使います。
網羅的なインプットというと、分厚い書籍を1ページ目から最後まで順に読むという印象がありますが、これは中々体力を使いますね💦
これでは冒頭で述べた"楽して"という言葉に反します。
ここで私は、独自の読書フレームワークであるgoto文型リーディングを使います。
goto文型リーディング
goto文とはプログラミングの制御構造の一つで、指定された場所に処理をジャンプさせるための文です。
このフレームワークは、各章が独立しておらず順に読まないと意味が分からないタイプの書籍に有効です。
手順は以下の通りです。
目次を見て、この書籍ではどんな内容に触れているかにしっかり目を通す。これは毎回読む前に行う
一番興味ある部分から読む
興味が無くなるまで以下を繰り返す
意味が分からないワードや表現が出てくる
目次が頭に入っていれば、その書籍のどこかで説明されていることが分かる
該当箇所にジャンプする
書籍によりますが、これで大半は読むことが出来ます。
大半を読めばその書籍の内容に関してはかなり理解度があるため、残りの部分は小見出しに魅力がなくても興味が湧くことが多いです。
それでも興味がない部分は無理に読む必要ないと思います。
そっと本棚にしまいましょう。
モチベーション・マネジメント
上記を読んで正直面倒くさいと思った方もいると思います。
私も同感で、上記を完遂するためには一定のモチベーションが必要です。
上記を行うためにはモチベーション・マネジメントを常に行う必要があります。
私は、学習をしていく上でこれが一番重要だと思います。
夢や目標が遠すぎると挫折しがちですよね、、
私は、ちょっと頑張れば届く中期目標向かって進んでいる時が一番モチベーションを高く保つことが出来ます。
この中期目標の距離がどのくらい近いと良いのかは人によって違うと思います。
中期目標を最適な場所に置き続けることが大事だと思います。
これはスキルだと思っています。
環境が変化し続ける中で、常に適切な場所に中期目標を置けるスキルがある人の成長は止まらないと思います。
まとめ
最後まで読んで頂きありがとうございます。
冒頭でも申し上げましたが、あくまで自分の中でしっくりきているフレームワークなので、理解し難い部分があると思います。
学習の質をもっとあげていきたいと思っているので、他に実践されているものがあれば、教えて頂けますと幸いです!
CyberAgentのアドテクチャレンジで正確なDMPを作る
先日株式会社サイバーエージェントの2dayインターン、アドテクチャレンジに参加させて頂きました。
そしてなんと優勝させて頂きました!
私はインフラ設計の経験がなく、常に手探り状態でした💦
今記事では、優勝に至るまでの技術的苦悩をまとめていきます。
問題
概要
開発時間終了後、位置情報やIDFAなどのデータが3時間程度大量に送られてくる(2000QPS)。
1リクエストにつき100ms以内に返さないと無効になる。
送られてきたデータを元に問題を解答し、その精度を元に得点がつけられる。
*ちなみにGoogle Cloud Platform使い放題\(^o^)/
詳細
以下の2つの形式でそれぞれ何問か答える
【バッチ問題】
データ送信が完了した後に問題に答える
ex)○時から○時の間に〇〇から半径10km以内にいた人数は?
【リアルタイム問題】
データ送信中に、特定IDFA(端末ごとのID的なやつ)と時間がjsonで送られてくる。
そのユーザーがその時間にどこにいたか等を答える。
1000ms以内に返さないと無効。
アーキテクチャ
最初の案
リアルタイム問題はレスポンスタイム制限があるため、直近で使うデータだけredisに置いておかないと💦
と思っていたのですが、1000msは余裕すぎました。
しかも直近で使うデータなんか分からないから却下。
二番目の案
1000msは余裕と分かり、今回ネックなのは書き込みであることに気付きました。
正直2000QPSが想像つかなかったのですが、直接書き込むのではなくジョブキューを噛ませるのが安全だと考えました。
GCPのCloud Launcherなら、redisがプリインストールされているインスタンスを素早く立ち上げられるし、
クラスタリングも比較的簡単に出来そうだったので決まりか!?と一瞬笑顔になりました。
しかしredisはインメモリDBなので、ログを1つでも取りこぼしてはいけない今回は不適切でした。
三番目の案
様々なミドルウェアを検討しましたが、フルマネージドサービスで統一した方ががつなぎ込みも楽だと考えたので、Cloud Pub/Subを採用しました。
データストアにはBigtableを採用しました。GCPサービスの中では最も時系列データを扱うのに向いていたからです。(後ほど説明します)
さらにバッチ問題用にBigQueryを採用しました。集計が速く、且つ全員が慣れていて学習コストが低いSQLで操作できるためです。
データをBigtableからBigQueryにエクスポートしようと思っていたのですが、そのためのBigtableのリージョンがus-centralとeurope-westしかありませんでした。。。
そこでログをリアルタイムでエクスポートしてくれるStackdriverLogging採用。(後ほど審査時に突っ込まれます)
完成!・・・
しかし全員が口をそろえて言いました。
時間足りなくね(-.-;)
採用案
とりあえずQPS2000でリクエストを流してみたところ、ジョブキュー噛ませなくても余裕で耐えられることが判明しました。
ということで、APIから直接Bigtableに書き込むアーキテクチャで決定しました。
言語
Go
理由
2日間という短い期間で高トラフィックを捌くには最適
- 実行速度○
- ビルド速度○
- go fmtのお陰で規約がいらない
- GCPサービスとの相性良さそう
- みんなのGoモチベが高い (超大事)
Bigtableのスキーマ設計
問題は時間指定が多かったため、時間順にソートしておきたいと考えていました。
リファレンスを読むとこんなことが書いてありました。
Cloud Bigtable ではデータが非構造化列として行に保存され、各行には行キーがあり、行キーは辞書順で並べ替えられています。
そこで行キーを’timestamp#IDFA'という形式にすることで、探索アルゴリズムの高速化を図りました。
ex)'2017-07-03 10:57:23 +0000 UTC#xxxxxxxxxxxxxx'
StackdriverLoggingとAPIのつなぎ込み
以下の数行コードで通常のstring型ログの書き込みが可能です。
Payloadというinterface{}型フィールドに文字列を渡します。
import "cloud.google.com/go/logging" var logClient *logging.Client logger := logClient.Logger("テストログ") logger.Log(logging.Entry{Payload: "テストテキスト"})
StackdriverLoggingはBigQueryで爆速集計するために採用したため、構造化したログを渡すことが必須でした。
BigQueryにはJsonPayloadという型があるため、Payloadフィールドにjsonで渡すのが最適解なのでは?という仮説のもとリファレンスを漁りましたが、のってませんでした(;´Д`)
GCPのソースコードを読んでみました
cloud.google.com/go/logging/logging.go 506~509行目
// Payload must be either a string or something that // marshals via the encoding/json package to a JSON object // (and not any other type of JSON value). Payload interface{}
jsonオブジェクト渡せってことかな??
早速以下のように構造体をエンコードして[]byte型に変換!
import ( "encoding/json" "cloud.google.com/go/logging" ) type Info struct { Latitude string `json:"latitude"` Longitude string `json:"longitude"` DeviceID string `json:"device_id"` SysName string `json:"sysname"` SysVer string `json:"sysver"` Timestamp string `json:"timestamp"` } info := &Info{} i, err := json.Marshal(info) logger := logClient.Logger(logName) logger.Log(logging.Entry{Payload: i})
エラー!!
深くまでソースを見てみると、
cloud.google.com/go/logging/logging.go 637行目
jb, err := json.Marshal(v)
Payloadフィールドの値を最終的にエンコードしてる!!
普通に構造体渡せば良かったのね!!!
import "cloud.google.com/go/logging" type Info struct { Latitude string `json:"latitude"` Longitude string `json:"longitude"` DeviceID string `json:"device_id"` SysName string `json:"sysname"` SysVer string `json:"sysver"` Timestamp string `json:"timestamp"` } info := &Info{} logger := logClient.Logger(logName) logger.Log(logging.Entry{Payload: info})
無事StackdriverLoggigにjsonログが流れました。
BIgQueryには1カラムに複数のデータを入れることが出来るRECORD型というデータ型があります。
jsonログをStackdriverLoggingに流すことで自動的にRECORD型のカラムを生成してくれます。(設定は必要)
オペレーション
開発時間が終わり、いよいよデータ送信の時間になりました。
すごい速度でリクエストが流れ、興奮していたのも束の間。
http: Accept error: accept tcp [::]:8080: accept4: too many open files
開始30分後、テスト時には発生しなかったエラーが!!
TCPが開いたままになってるのかな??と思いプログラムコードを確認したところ、リクエストボディを閉じ忘れていました。
func collect(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // some codes... }
すぐにデプロイ!
Goはシングルバイナリを上げればいい!素晴らしい!
http: Accept error: accept tcp [::]:8080: accept4: too many open files
なぜだ、、、
KeepAliveオフにしたら解決するかな、、
net/http/request.go 203~206行目
// For client requests, setting this field prevents re-use of // TCP connections between requests to the same hosts, as if // Transport.DisableKeepAlives were set. Close bool
Closeという公開フィールドをfalseにすればDisableKeepAlives
にできるっぽい!
関数が用意されてない時点で非推奨だと思うけど、やるしかない!
func collect(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // some codes... r.Close = false }
http: Accept error: accept tcp [::]:8080: accept4: too many open files
1分で死にました。
結局データ送信中は、人間が常にstatusを監視するという力技で乗り切りました笑
大会終了後に社員さんに聞いたところ、linuxのファイルディスクリプタ数(同時に開けるファイル数)の上限を上げれば解決したそうです。
何が起きていたかというと、
- ファイルopenしてはcloseを繰り返す
- リクエストが急増するとcloseがopenに追いつかず、too many open filesが発生
勉強になりました。
このような、知らないと解けない問題を解けるエンジニアになりたいです。
まとめ
とにかく死ぬほど楽しかったので、サーバーサイドに興味ある学生さんは絶対参加するべきだと思います!
技術的なことしか書きませんでしたが、チームビルディング等、多くのことを学びました。
また参加したいと思います!
*インフラ初体験の大学生2年生が書いてるので、間違いがあるかもしれません!
もし見つけたら指摘して頂けるとありがたいです。
優勝やったー!
2017年は「決める年」
今週のお題「2017年にやりたいこと」
タイトルに書いたように、2017年は「決める年」にします。
以下に記す中期的な目標のために具体的な3つの目標を定める。
- 人生哲学(人生で何をしたいかという最上級の目標。心理学的には究極的関心というらしい)を「決める」
- 全てにおいて「決める」スピードと精度を高める
- めりはりをつけるために逐一何時までになにをするかを「決める」