原文:devdocs.io
複数のAPIドキュメントをまとめたDevDocsのお話。
各言語のリファレンスが一覧性高く、速く、検索しやすいインターフェースで提供されている。
めっちゃ便利そう。
原文:getstream.io
PythonからGoを使い始めた経緯のお話。
既に経験のある言語から新しい言語に切り替えるのは大きなステップが必要になる。
streamでは主要なプログラミング言語をPythonからGoに切り替えた。
本記事では、なぜPythonからGoに切り替えたのかの理由を述べている。
GoのパフォーマンスはJavaやC++と同じくらい良い。
彼らのチームではGoのほうがPythonより30倍も早かった。
多くのアプリケーションにおいてプログラミング言語は
ただアプリケーションとデータベースをつなぎ合わせるノリにすぎない。
言語自身のパフォーマンスはそんなに問題にならない。
彼らは500以上の会社と2億人以上のエンドユーザーにAPIを供給している。
Cassandra、PostgreSQL、Redisなどで最適化しているが、言語上の限界に達してしまった。
Pythonは素晴らしい言語であるが、serialization/deserialization、
ランキング、アグリゲーションといったユースケースでパフォーマンスが出なかった。
Cassandraは1msでデータを見つけるが、Pythonは10msでオブジェクトに詰めていた。
Goのチュートリアルにあった以下のコードを見てほしい。
package main type openWeatherMap struct{} func (w openWeatherMap) temperature(city string) (float64, error) { resp, err := http.Get("http://api.openweathermap.org/data/2.5/weather?APPID=YOUR_API_KEY&q=" + city) if err != nil { return 0, err } defer resp.Body.Close() var d struct { Main struct { Kelvin float64 `json:"temp"` } `json:"main"` } if err := json.NewDecoder(resp.Body).Decode(&d); err != nil { return 0, err } log.Printf("openWeatherMap: %s: %.2f", city, d.Main.Kelvin) return d.Main.Kelvin, nil }
このコード例には以下の要素が詰められている。
もしPythonで同じことをやろうとすると、
これらの機能で遊ぶのは楽しいが、多くのプログラマが同意するように、
多くの場合これらのコードは理解するために読むのが難しくなる。
Goは基本に忠実にさせる。
誰が書いたコードであっても簡単に理解するために読めるようにしてくれている。
NOTE: どれだけ簡単か、はユースケースに因る。単純なCRUD APIであればDjangoとDRFかRailsを使うほうが良い。
言語として、Goは物事を単純に保とうとする。
goroutineとchannelによって単純な言語を実現している。
goroutineは軽量のスレッド化手法で、
channelはgoroutine間のコミュニケーションを取る優れた方法である。
goroutineは非常に低コストで生成でき、メモリもわずかしか使用しない。
そのため、一度に何百、何千ものgoroutineを走らせることが出来る。
goroutine間の通信にはchannelを使う事ができる。
Goのランタイムが全ての複雑性をコントロールしてくれる。
goroutineとchannelベースの手法が同期実行を簡単に
使用可能な全てのCPUコアを使用させ、同期IOを扱う。
Python/Javaと比べ、Goは最低限の定型文のみが必要になるだけである。
JavaやC++に比べて非常に短い時間でコンパイルすることが出来る。
Goを書ける開発者はC++やJavaといった古い言語の開発者よりも多くはない。
StackOverflowによると38%の開発者はJavaを書け、19%がC++を書けるが、
Goに関して言えば4.6%しかいない。
Githubのデータも似たような傾向を示している。
幸いにもGoは非常に簡単かつシンプルに学ぶことが出来る言語である。
基本的な機能を提供し、必要なものはそれ以外にない。
その簡潔さでPythonをかける開発者を1ヶ月以内に効率的に出来る。
そのため、他の言語に比べチームを構成するのに容易い。
20人以下の規模のチームにおいてエコシステムは問題になる。
小さな改革を起こさなければ、顧客に価値を提供することは出来ない。
Goは我々の使用しているツールに対してサポートをしている。
充実したライブラリは既に揃っていて、RedisやRabitMQ、PostgreSQL、
テンプレートエンジンやタスクスケジューリング、文法解析などがある。
GoのエコシステムはRustやElixirといった他の新しい言語に概ね勝っている。
Javaやpython、Nodeなどには負けているが、
必要としている基本的なライブラリは既に使用可能である。
Pythonにおけるautopop8のような強制フォーマッティングツールが備わっている。
タブ - スペース論争のようなものはもう必要ない。
Gofmtが全てを解決してくれる。
GoはプロトコルバッファとgRPCを快適にサポートしている。
これらの2つのツールはRPC経由でのマイクロサービスを立てるのに適している。
マニフェストを書くだけで、RPC呼び出しを使える。
サーバーとクライアントのコードはマニフェストから自動生成される。
同じマニフェストから、クライアントのコードをJava, C++, PythonとRubyで生成できる。
これ以上曖昧なRESTのエンドポイントは必要ない。
Goはまだ支配的なフレームワークを有していない。
これはGoコミュニティ内で白熱した論争のトピックとなっているが、
今のところフレームワークを使うべきではない、という。
もしシンプルなCRUD APIを立てたかったら、 DjangoやRailsを使うほうが良い。
Goでは、呼び出し元のコードでエラーをハンドリングする必要がある。
そのため、エンドユーザーに対して意味のあるエラーを返すのを簡単に忘れてしまう。
エラーのパッケージはこの問題をエラーにコンテキストとスタックトレースを追加することで解決している。
他の問題として、簡単にアクシデントによるエラー処理を忘れてしまう。
errcheckやmegacheckのような静的解析ツールはこのミスを便利に防いでくれる。
Goのパッケージ管理は決して完璧ではない。
デフォルトでは依存するライブラリのバージョンを指定する方法はなく、
再現性のあるビルドをする方法もない。
Python, Node, Rubyのいずれも優れたパッケージ管理システムを持っている。
しかし、正しいツールを使えばGoのパッケージ管理もかなり良い。
Depを使って依存性を予定したバージョンで管理することが出来る。
それとは別にVirtual Goというオープンソースツールもある。
原文:Why PostgreSQL is better than MySQL |
PostgreSQLがMySQLよりも優れているお話。
どんな製品にもバグはつきものである。
PostgreSQLのコミュニティでは、発見されたバグは直ぐに修正されるだろう。
PostgreSQLのコミュニティでは常にバグをトラッキングするかは大きな議論になるからだ。
しかし、MySQLは#199にあるような単純なバグが14年間も直されずに放置されてきた。
これが明確に優れている理由である。
巨大なデータを持つテーブルを効率的に読み込む方法についてのお話。
1億行のレコードを持つテーブルからデータを読み込む時、どのように読むべきか。
SELECT user_id, external_id, name, metadata, date_created FROM users;
シンプルに上のようにやってしまうと、処理は終わらない。
おそらく、取得するデータをすべてRAMに展開するためである。
あるいは、データを送る前のプリロードに時間がかかり、クエリーがタイムアウトをしている。
どちらにしろ、この方法でデータを取得することは出来ない。
SELECT user_id, external_id, name, metadata, date_created FROM users ORDER BY user_id ASC LIMIT 0, 10 000;
データをページに入れて取得する方法がある。
データの取得順は物理的/論理的に保証されないため、ソートする必要がある。
これの実行時間は10 000 rows in set (0.03 sec)
で行われている。
それでは、5,000ページ目を取得するとどうだろうか。
SELECT user_id, external_id, name, metadata, date_created FROM users ORDER BY user_id ASC LIMIT 50 000 000, 10 000; --- 5 000th page * 10 000 page size
この実行時間は、10 000 rows in set (40.81 sec)
となる。
確かに、めちゃめちゃ遅い。
さらに最後のページを取ろうとするとどうなるか。
SELECT user_id, external_id, name, metadata, date_created FROM users ORDER BY user_id ASC LIMIT 99 990 000, 10 000; --- 9999th page * 10 000 page size
実行時間は、10 000 rows in set (1 min 20.61 sec)
となる。
実際に使うには、バックグラウンドで動くようなタスクにしか使えない。
実行時間のほかにもう1つ問題になりうるケースがある。
例えば、既に10ページめくっている状態(100,000 件のデータにアクセス)で、
次のページ(100,001 から 101,000)へアクセスしようとしている時を考える。
そのタイミングで、99,998と99,999レコードが消されてしまったら、
ページの最初の結果は、100,003からになってしまう。
つまり、ミュータブルなデータセットの場合、この方法は使えないということになる。
一つ前と非常に似ているが、レコード数でページングをするのではなく、
indexのついたuser_idをオフセットとして利用している。
アルゴリズムとしては、以下のようになる。
1. テーブルからレコードのPAGE_SIZE
を取得し、オフセットを0とする
2. 次ページのために取得したuser_id
の最大値をオフセットとする
3. 現在のオフセットよりも大きいuser_id
を取得する
1ページあたり10,000件のデータを持たせ、5,000ページ目を取得する場合は次のようになる。
SELECT user_id, external_id, name, metadata, date_created FROM users WHERE user_id > 51 234 123 --- value of user_id for 50 000 000th record ORDER BY user_id ASC LIMIT 10 000;
驚くことに実行時間は10 000 rows in set (0.03 sec)
とあるように、1000倍も速い。
user_idの値は連続的ではなく、例えば25345の次は25348となっている。
これならば、読み込む次のデータが消されても問題なく動作する。
この記事では1億行のレコードを持つテーブルを読み込む時に、
プライマリーキーを指定しているオフセットを使うこととしている。