デザインパターンをできるだけ短く表す試み前半

背景

流し読みしたデザインパターンの本をQuick Recapするための記事

参考: 増補改訂版 Java言語で学ぶデザインパターン入門 Kindle版

Iteratorパターン

繰り返しのロジックを集合体から分離することで、集合体を変更しても繰り返しのロジックを作成しなくてすむ

Adopterパターン

元々のクラスやインスタンスのラッパーを書くことで、呼び出し元で違うメソッド名として呼び出せる

Template Methodパターン

抽象クラスやインターフェースでメソッドをざっくりと定義しておいて、サブクラスや実装クラスでその詳細を実装する

Factory Methodパターン

コンストラクタではなくメソッドでインスタンスの生成を行うことによって、スーパークラスが具体的なクラス名を知っている必要がなくなる

Singletonパターン

プロジェクトの中でインスタンスが1つしか存在しないことを保証する。コンストラクタをprivateにして外部から呼ばれないようにし、getInstance() を初めて呼んだときにthis.singletonが初期化され、その後はそのインスタンスが返される。

Prototypeパターン

微妙に挙動が異なる複数のクラスを定義するのではなく、雛形を作っておいて、その雛形にパラメータを与えることで、挙動の違うインスタンスを表現する

Builderパターン

Builderという抽象クラス/インターフェースを継承/実装するクラスを複数作成し、どのクラスを呼び出し元に与えるかを変えることで、どんな形式で(html/textなど)出力するか分岐できる

感想

納得感は強いが、実際に使わないと定着はしなさそう。 以下のパターンは実装したことがなかったので、今後使っていきたい。

  • Singleton
  • Prototype

よく使うサービスの設計を調べる~Twitter編~

背景

普段よく使っているサービスをどう設計するかとかあんまり考えたことがなかったから、これからちょくちょく調べていくことにする

関心

  • 増え続けるデータに対してのテーブルの設計はどうするか

要約

元記事: Big Data in Real-Time at Twitter

Twitterが抱えるReal Time Data処理について、以下の点が問題となった

  • Tweets
  • Timelines
  • Social graphs
  • Search indices

Tweet

おなじみのツイート。ユーザーが投稿するつぶやきのエントリ。

  • もともとのテーブル設計はid(主キー), user_id, text, created_atをカラムに持つRDB
  • Master-Slave構成で、キャッシュ層としてMemocached

問題1

ディスク容量。一時は容量の90%が使用されている状態となった

解決策1

パーティション化を工夫する。

ここで、どのようにパーティション化するかが課題

時間単位でパーティションを分けることで、2つの問題を解決した

この場合、どちらでid, user_idのどちらでクエリするにしても、単一のパーティションで収まるということはない(=O(N)になるはず)。

しかし、Twitterというサービスは直近のツイートに対してのクエリが最も多く、実質的にほぼO(1)のオーダーになる。

問題2

書き込み時のスループットMySQLデッドロックを起こすようになった。また、新しいシャードを作成するのはかなりの手間だった

解決策2

Cassandraの導入。パーティションキーをツイートのid, ソートキーをuser_idにする。

読み込みにはMemocachedを使う。

Cassandra+キャッシュの構成は始めて見た。実在するんだな。

Timelines

自分がフォローしている人のツイートの集合

元々のSQL(出典: Big Data in Real-Time at Twitter)

SELECT * FROM tweets
WHERE user_id IN (
  (SELECT source_id 
   FROM followers
   WHERE destination_id = ?)
ORDER BY created_at DESC
LIMIT 20

問題

メモリに載らないほど友達が多すぎる場合、サブクエリの部分でめちゃくちゃ時間がかかる

解決策

  • Memocachedにツイートidのシーケンスをもたせる
  • オフラインでファンアウトする

要は、Memocachedに一方的に配信するようにして、ユーザーが後にMemocachedを見に行くようにするPub/Subの構造にしたっぽい

→これで120万req/sに対応できるようになった

Social Graphs

フォロー/フォロワーや、リスト、ブロックなどのつながり もともとの実装 以下のように中間テーブルを実装する |source_id|destination_id |-|-| |20|12| |29|12| |34|16|

問題

解決策

  • 関係を片方向のみ保持するテーブルを作る
  • ユーザーでパーティションを分け、時間でインデックスを貼る

これは、非正規化されたテーブルになるし、逆方向の同じリンクを別々のテーブルが持つようになる

Forward

source_id destination_id updated_at x
20 12 20:50:14 x
20 13 20:51:32
20 16

Backword

source_id destination_id updated_at x
12 20 20:50:14 x
12 32 20:51:32
12 16

課題

データの一貫性を持たせるのが難しい →結果整合性のデータモデルにする

Search Indices

検索バーでの検索

元々の実装

  • Mater-Slave構成で、垂直にスケールする
term_id doc_id
20 12
20 86
34 16

問題

インデックスがメモリに載らない

解決策

時間でパーティション分けする 将来的には、ドキュメントと時間の両方でパーティション分けし、確認するレイヤーをもたせる

感想

大規模だと素直にテーブル設計してもスケールしないことはわかるけど、解決策が時間ごとのパーティション分けになるのは目から鱗。サービスについて深く理解してないと、こういう解決策は出てこないな。 テーブルを非正規化するのは非常にシンプルな解決策だから引き出しの一つとして持っておきたい

【随時更新】C言語

背景

まともにC言語の勉強をしたことがなかったため、C言語の知識が足りなさすぎる。

調べるたびに追記してなんとか自分のものにしていきたい

関数

戻り値の型 関数名(引数の型 引数) {
   return 戻り値
}

足し算で言うなら以下のような感じ

int add(int a, int b){
  return a + b;
}

用語

  • エントリポイント
    • 起動時に最初に実行される関数
  • インクルードガード
    • 同じファイルを複数回インクルードしてコンパイルエラーになることを防ぐ機能
#ifndef INCLUDED_XXX
#define INCLUDED_XXX

#include <Hoge.h>

#endif

意味はifndef(= if undefined?) INCLUDED_XXX = INCLUDED_XXXが定義されていないとき, INCLUDED_XXXを定義する。そしてendif

随時更新・awkコマンドまとめ

背景

awkはとても便利。

コマンド集

tsvファイルの読み込み。

  • 1つめのカラムのみ出力する
awk -F '\t'  '{print $1}' test.tsv 
  • 3つめのカラムの値が1のとき、1つめのカラムを出力する
awk -F '\t' '{(if $3 == 1){ print $3 }}' test.tsv
  • 最初からn行オフセットして表示する
awk -F '\t' '{(if NR > n){ print }}' test.tsv

随時更新・自分的に忘れがちなHTTPヘッダー

TL;DR

自分なりに一言でまとめることに意味がある的な記事

背景

AWSのプロフェッショナル試験で、CloudFrontのキャッシュの挙動に関する勉強をしてるときにHTTPヘッダーの理解が浅くてかなり混乱したので、自分なりにまとめておく。

 

今回は使った(模試に出てきた、業務で触った)もののみまとめてみて、今後新しく触ったものがあれば追記していく

 

中身

Authorizationヘッダー: Authorizationヘッダーの中身を使ってサーバーから認証を受けられる

HTTP の Authorization リクエストヘッダーは、ユーザーエージェントがサーバーから認証を受けるための証明書を保持し、ふつうは、必ずではありませんが、サーバーが 401 Unauthorized ステータスと WWW-Authenticate ヘッダーを返した後に使われます

Cookieヘッダー: サーバーから送信されたCookieCookieヘッダーに乗っけてクライアントを認証する

Cookie は HTTP のリクエストヘッダーで、以前サーバーが Set-Cookie ヘッダーで送信し、保存された HTTP クッキーを含みます。

Cookie ヘッダーは任意であり、例えば、ブラウザーのプライバシー設定でクッキーをブロックしている場合などは省略できます。

Hostヘッダー: リクエスト先のサーバーのホスト名とポート番号を指定する。例えばプロキシがHostヘッダーを後ろに転送すれば、元のリクエストが最初にどこにリクエストを送ってたか知ることができる。

Host リクエストヘッダーは、リクエストが送信される先のサーバーのホスト名とポート番号を指定します。

User-Agentヘッダー: 自分のデバイス、OSなどをサーバーに伝える。スマホなのかPCなのかを知らせたり。

User-Agent リクエストヘッダーは、サーバーやネットワークピアがアプリケーション、オペレーティングシステム、ベンダーや、リクエストしているユーザーエージェントのバージョン等を識別できるようにする特性文字列です。

文字コードとサロゲートペア

背景

Javaの文字列から絵文字を取り除く必要に迫られたから調べてみた。

Teradataへの投入処理の際に、Unicode文字列を変換できないことによるエラーが発生する。Teradataへの投入にはFastLoadを用いている。

Teradataはこの問題に Unicode Pass Throughという機能を用意しているが、FastLoadではサポートされていない。

Unicode Pass Through is not supported with FastLoad, MultiLoad, Teradata Parallel Data Pump (TPump), and FastExport.Use Teradata Parallel Transporter (TPT), BTEQ, or the Teradata JDBC Driver to load or unload data containing pass through characters.

Javaでの実装

絵文字の削除のためだけに既存のバッチをBteqで書き直すのも工数が大きいので、FastLoadに渡す前にJavaで絵文字を削除する処理を挟むことにした。

試行錯誤の末、サロゲートペアで表される文字を全て除外することにした。

サロゲートペアは、上位サロゲートと下位サロゲートの組み合わせで表現されるので、以下の組み合わせの範囲が除外対象となる

上位サロゲート: U+D800〜U+DBFF
下位サロゲート: U+DC00〜U+DFFF

String target;
return target.replaceAll("[\\ud800\\udc00-\\udbff\\udfff]", "")

簗などの難読漢字も置換されてしまうが、Teradataにはこれらの難読漢字も投入できなかったため、今回は止むを得ずこちらで対応した。

結論

余裕があったらBteqとUnicode Pass Throughを使った方が良さそう。

参考

Unicode, UTF についてひっかかったので色々メモ

Unicodeエスケープシーケンス変換ツール

@VisibleForTestingについて

今までなんとなく使ってきた@VisibleForTestingを改めてちゃんと調べてみる

まずはドキュメント

Annotates a program element that exists, or is more widely visible than otherwise necessary, only for use in test code.

テストコードのために必要以上に可視性があるプログラムにつくらしい。和訳が難しい。

つまり、アプリケーション上ではprivateで使われるメソッドを、テストコードのためだけにpublicとかprotectedにしなくちゃいけないときにつけるらしい。

メソッドの挙動を変えるわけではないが、コードを見た時になんでこの公開範囲になるのかがわかりやすくなる。