Persistence

続けることに意味がある。

Redisの基礎知識

Redisについて主に 公式ドキュメント を読み漁った備忘録です。Redisを使って何ができるのかをざっくり知るための情報です。

Redisとは

https://aws.amazon.com/jp/redis/

いきなり公式じゃなくてAWSのドキュメントですが、Redisの特徴やユースケースをわかりやすくまとめられていてとてもイメージが湧いてきました。

チュートリアル

http://try.redis.io/

Try Redisは対話形式でRedisの基本的なコマンドを学ぶことのできるコンテンツです。公式のトップページにもリンクされています。Redisを全く触ったことがない場合は、まずはここでRedisを体験してみるのが良さそうです。

データ型

https://redis.io/topics/data-types-intro

チュートリアルよりも詳しいデータ型についての説明です。

Stream型

https://redis.io/topics/streams-intro

Redis5.0から追加された新しい型です。Pub/Subができる時系列データです。

https://aws.amazon.com/jp/redis/Redis_Streams/

こちらのAWSブログの記事では、Stream型を使った具体例が示されており、理解の役に立ちました。

主要な機能

https://redis.io/documentation

Documentationの Programming with Redis の項目についての概要です。

Pipelining

https://redis.io/topics/pipelining

  • 複数のコマンドをまとめてリクエストする機能です
  • Redis内部ではコマンドがキューで管理され、全てのコマンドが処理されたらレスポンスが返ります
  • コマンドを1件ずつリクエストするよりも、ネットワークのRTTやRedisコンテキストスイッチを減らすことができ、待ち時間を減らすことができます
  • ReadとWriteをまとめて行いたい場合には、Redis2.6で追加されたScriptingを使います

Redis Pub/Sub

https://redis.io/topics/pubsub

  • チャンネルにメッセージを送信したり、チャンネルをSubscribeする機能です
  • 複数のチャンネルをSubscribeすることができます
  • SubscribeやUnsubscribeにはパターンマッチが使えます
  • パターンマッチの場合はメッセージの形式が通常のメッセージの形式とは異なります
  • パターンマッチでSubscribeシテイルチャンネルのメッセージ受信と、通常のSubscribeしているチャンネルのメッセージ受信は重複します

Redis Lua scripting

https://redis.io/commands/eval

  • Redis2.6で追加されたLuaインタープリタの機能です
  • LuaスクリプトをEVALコマンドで送信してRedis上で実行します
  • Redisの組み込み関数 redis.call(), redis.pcall() などが提供されています

Debugging Lua scripts

https://redis.io/topics/ldb

Memory optimization

https://redis.io/topics/memory-optimization

  • このページは機能ではなくメモリ最適化のためのチェックリストです

Expires

https://redis.io/commands/expire

  • キーにタイムアウトを設定することで、自動的にキーが削除される機能です

Redis as an LRU cache

https://redis.io/topics/lru-cache

  • キャッシュとして利用するときに、メモリの上限に合わせて古いキャッシュを削除する機能です
  • LRUというアルゴリズムでキャッシュを管理します
  • Redis4.0ではLFUというアルゴリズムも導入されました

Redis transactions

https://redis.io/topics/transactions

  • コマンドをグループにして実行する機能です
  • グループのコマンドが実行されるとき、他のコマンドの割り込みは発生しないのでアトミックな操作として実行できます
  • つまりグループのコマンドがすべて処理されるか、全て処理されないかのいずれかの状態になります

Mass insertion of data

https://redis.io/topics/mass-insert

  • 大量のデータを挿入する機能です
  • 1コマンドずつ登録する方法や、Pipelineを使って登録する方法は推奨されません
  • データが書かれたファイルを読み込ませて登録する方法を利用します

Partitioning

https://redis.io/topics/partitioning

  • 複数のインスタンスでデータを分割する機能です
  • すべてのインスタンスにはキーのみを格納し、データは分散して管理します
  • 1台のコンピュータのメモリ搭載量を超えるデータを扱うことができるようになります

Distributed locks

https://redis.io/topics/distlock

  • RedlockというアルゴリズムによりRedisで分散ロックを実装する方法を記載しています
  • 実際にそれを行うのはクライアントライブラリです

Redis keyspace notifications

https://redis.io/topics/notifications

  • Redis2.8で追加されました
  • Pub/Subのチャンネルを通してメッセージ以外の情報を通知する機能です

Creating secondary indexes with Redis

https://redis.io/topics/indexes

  • 構造化されたデータに対してインデックスを追加する機能です
  • ソートされたセット型のデータのソートキーにインデックスを作ることでvalueとObjectIDのインデックスを作ったりできます
  • ただしインデックスの値は手動で登録/更新が必要です
  • いろいろ柔軟に使えそうです

Redis modules API

おわり

RedisについてはSidekiqのデータストアとしてしか利用したことがなかったのですが、キャッシュやPub/Subやその他にも色々な使い方ができそうということが分かりました。Pub/SubやStreamについてサンプル実装などがあったので、次は手を動かしてみたいと思います。

参考

HTTP負荷テストツール vegeta をサポートする nappa というツールを作りました

vegeta というGoで作られたHTTP負荷テストツールがあります。

github.com

初めて使ってみたのですが、シナリオや状態(Cookieなど)を伴わない単純な負荷テストにはこれで十分と感じました。

ですが、ターゲット(リクエスト)の指定について、単純なGETリクエストなら良いのですが、ヘッダーやボディを含めると指定がやや面倒と感じました。具体的には、RFC 2616形式のテキストファイルにターゲットの情報(URLやヘッダー、ボディなど)を書いて読み込ませるか、JSON Schema形式で標準入力として渡すかの、2通りの方法があります。いずれも普段扱うフォーマットではないため、これを手作りする必要がありました。

そこで、curlコマンドからJSON Schema形式を出力するツールを作りました。curlコマンドであれば、色々なツールから出力できるようになっていることが多いので、簡単に作ることができます。

github.com

このツールにcurlコマンドを食わせると、必要なパラメータだけがJSON Schema形式に変換されて標準出力に出力されます。それをパイプでつないで vegeta に渡してやればいいわけです。

$ nappa <paste a curl command> | vegeta attack -format=json -duration=1s -rate=1/s | vegeta encode

ffmpegでm4aをmp3に一括変換する

備忘メモ。

お風呂場で使っている音楽プレイヤーがmp3形式にしか対応していないのだが、我が家の音楽ファイルはもう大体がm4a(aac)で保存いているため変換が必要になる。

以下のコマンドを使うと、ディレクトリ内のすべてのm4aファイルからmp3ファイルを生成することができる。

fish shell

for file in *.m4a; ffmpeg -i $file -f mp3 -b:a 192k (basename $file .m4a).mp3; end

GitHub Browser Extensionsのうちいくつかを1ヶ月ほど使ってみた感想

GithubにはCollectionsというリポジトリのキュレーション機能があります。その中に、Collection: GitHub Browser Extensionsという、GitHubを便利にする拡張のCollectionがあります。ここで紹介されている拡張のうちいくつかを実際につかてみたので、その感想を書いていきます。

なお、拡張にプライベートリポジトリのアクセストークンを渡すのはリスクを伴うことは理解して使いましょう。

ovity/octotree: GitHub on steroids

期待度: ★★★ おすすめ度: ★★★

リポジトリの内容をツリー構造で表示してくれます。ディレクトリをリンクを辿って開かなくてもリポジトリのルートから一発で特定のファイルを開くことができます。Pull Requestの画面では、コミットされたファイルのみをツリー構造で表示してくれて、スクロールを移動できるので、コミットされたファイル数が多いときには便利です。

OctoLinker/OctoLinker: OctoLinker — Links together, what belongs together

期待度: ★★★ おすすめ度: ★★

ソースコードを解析して、参照宣言などから参照元のファイルにリンクを生成してくれます。便利ではあるものの、ソースコードを見るときはローカルにあるか、GitHub自体の検索機能が優秀なので余り使う機会はなかったです。

Justineo/github-hovercard: Neat hovercards for GitHub.

期待度: ★★ おすすめ度: ★

リンクにカーソルを当てたときのHoverをリッチにしてくれます。Issueの中身をIssueの一覧から確認できるなど便利かなと思ったのですが、それ以上に邪魔に感じることが多かったので削除しました。

sindresorhus/refined-github: Browser extension that simplifies the GitHub interface and adds useful features

期待度: ★★ おすすめ度: ★★

この拡張は機能が多すぎて把握しきれていないのですが、細かいところをたくさん改善してくれています。私が良いと感じている機能は、コメントのリアクションにリアクションした人のアイコンが表示される(誰がどのリアクションをしたかが一目でわかる)のと、ヘッダー部分にPRが作られていないブランチがシュッと出てくる機能です。これだけ機能が多いと中には使いたくない機能もあるのですがそういうのは無効化できるのも良いです。

マルチテナントサービスの拡張性パターン

本記事は、ゆるWeb勉強会@札幌のアドベントカレンダーの11日目の記事として投稿しました。

コミュニティには参加したことがないのですが、主催者さんが記事を募集していたのと、私自身も期限を設けることで書ききりたかったので参加させてもらいました。

はじめに

私はここ2年ほど、マルチテナントサービスの立ち上げに開発メンバーとして関わってきました。少ない人数での開発だったので、サービスの設計からインフラ構築から実装から運用まですべてを経験しました。そして今また、新しいマルチテナントサービスの立ち上げで設計・実装を行ってる最中です。これらの経験を通じて、マルチテナントサービスで拡張性を実現する方法をある程度パターン化できそうと思ったので、アウトプットしてみようと思いました。ちなみに本記事の内容は、実際に試したものもあれば、妄想段階のものもありますので、その点はご了承ください。

本記事の言葉の定義

  • サービス: サービスと一口に言っても色々な提供形態がありますが、本記事が想定する(私が関わってきた)サービス)は、いわゆるBaaSのようなサービスです。特定の分野のクライアントアプリの開発に必要な機能をAPIやデータベース、管理画面などと共に提供します。
  • マルチテナント: マルチテナントは、複数のテナント(利用者)が1つのリソースを共有する形で提供するサービスのことです。もちろんテナントごとにデータは分離されているので、テナント(利用者)から見ると他のテナント(利用者)の情報は見えません。
  • 拡張性: 拡張性とは、あるテナントが独自の仕様(データのスキーマや機能の振る舞い)を実現することをいいます。

なぜ拡張性が必要なのか?

利用目的が決まっているサービスであれば、基本的にはサービスとしての仕様を決めて利用者はその中の機能を利用することが前提となります。BaaSのような開発者向けのサービスの場合、できることがクライアントアプリでできることと直結するため、機能の拡張性(自由度といったほうがしっくりくるかも)が求められます。例えば、データのスキーマを定義できたり、任意の認証プロバイダと連携できたり、外部のサービスとデータを連動したり、などなど。そういうことができないと、サービスとクライアントアプリの中間にもうひとつProxy的なサービスを置く必要があり、トータルとして利用者側の負担が高くなってしまいます。

拡張性を実現する難しさ

拡張性を内包することは常にトレードオフが付きまといます。できることが増えることで、組み合わせが増えて仕様が複雑になりがちです。また、一度公開したインターフェイスは提供し続けなければなりません。安易な方法で拡張性を実現すると、その部分はすぐに負債となり開発速度や品質の低下を招きます。

また、どのくらい拡張性を見越して設計するかも難しいところです。私は普段はYAGNIの原則を強く意識していますが、こういったサービスではどこまでの拡張性を想定するかを非常によく考え、オープン・クローズドの原則を強く意識します。

パターン

拡張性を実現する部分を、システムのレイヤーごとに分類してみるとこのようになりました。

  • データベースレイヤー
  • アプリケーションレイヤー
  • インターフェイスレイヤー
  • プレゼンテーションレイヤー
  • その他

1つのパターンが複数のレイヤーに跨っている場合もあります。

あと、どのパターンが一番良いというものではありません。それぞれにトレードオフが存在します。そしてベストな選択はサービスの特性やステージによっても異なります。

データベースレイヤー

データベースレイヤーでの拡張性はサービスの利用者の要求を叶えるためにとても重要です。なぜなら利用者は利用者側のドメインモデルをできるだけサービスに正しく反映させたいからです。データベースに拡張性をもたせることで、利用者の独自のデータを管理することができるようになります。データベースレイヤーでは2つの方法を紹介します。どちらも古典的なパターンだと思います。基本的にはRDBを想定していますが、NoSQLについてもサービス側でスキーマ定義を持つのでだいたい同じことが言えるかと思います。

拡張フィールド

  • 利用者が自由に使うことができるフィールドを用意します。フィールドを複数提供したり、JSON型のフィールドを提供することでより自由な使い方をさせることができます。
  • サービスは利用者のフィールドの使い方についての知識を持たないため、サービスとしては維持コストが低いです。
  • フィールドの使い方は利用者のクライアントのみが知識を持つため、フィールドのデータの管理はすべてクライアントが責任を持つことになります。

f:id:bisque3311:20191210225636p:plain

拡張フィールド+メタ情報

  • サービスが拡張フィールドのメタ情報(フィールド名や型など)を管理します。利用者から見るとモデルやエンティティを定義するようなイメージです。
  • サービスとしては抽象的な実装になるため、やや高度な実装になりますが、サービスのインターフェイスAPIや管理画面など)をメタ情報を通じて具体的な情報にすることができるので、クライアントには優しいです。
  • メタ情報を他のレイヤーでも参照することで様々な部分で拡張フィールドの情報を利用することができます。

f:id:bisque3311:20191210225713p:plain

アプリケーションレイヤー

データベースレイヤーの拡張により扱えるデータを増やすことはできましたが、ビジネスロジックを含むことはできません。アプリケーションレイヤーの拡張では、ビジネスロジックの拡張を実現します。

Config

  • いわゆるフラグです。フラグの設定によりロジックを切り替えます。拡張性というよりは、サービスが提供するロジックのパターンを増やして、利用者がそれを選択するというパターンです。
  • サービスとして提供するロジックを増やしているので、安易にパターンを増やしていくとすぐに組合せ爆発を引き起こし手がつけられない状態になります。なので、このパターンを使う場合は、フラグごとにモジュールを切り出すような実装にしたほうが良いです。
  • 例えば、サービスとしていくつかの認証サービスに予め対応しておき、テナントごとにどれを利用するかを選択できるようにする、などといった設定ができます。

f:id:bisque3311:20191210225735p:plain

Plugin

f:id:bisque3311:20191210225759p:plain

ロジックの埋め込み

  • サービスに利用者が独自の任意のロジック(プログラム)を埋め込み、任意のトリガーで実行することでビジネスロジックインターフェイスなどを拡張します。
  • ロジックのインターフェイス(ロジックに与えるパラメータ)や実行リソースはサービスが提供します。インターフェイスが決まっているという点ではPluginの進化系と言えるかもしれません。
  • 例えば「APIの認証で任意のロジックを実行して利用者が管理する認証サービスでトークンを検証する」とか、「特定のイベントが発生したことをトリガーとして任意のロジックを実行して外部サービスと連携する」とか、色々考えられて自由度が高いです。
  • 実際のサービスの例としては、認証基盤サービスのAuth0にRulesという機能があります。Rulesは、特定のイベントが発生したタイミングで(ユーザがログインしたときなど)実行されるロジックで、決められたインターフェイスを通じてパラメータを受け取り、ユーザの属性を操作したり、idTokenの内容を拡張したり、外部のサービスと連携したりすることができます。
  • 実行環境としては、GCPのCloud RunやAWS Lambdaなど、起動時間が短くコンピューティングリソースの分離や制限がしやすいServerless環境との相性が良いと思います。

f:id:bisque3311:20191210225819p:plain

インターフェイスレイヤー

サービスの拡張性をクライアントアプリに連携するには、サービスと通信するインターフェイスの拡張も必要です。

GraphQL

  • 不特定多数のクライアントに利用されるAPIの場合、GraphQLを使うことでクライアントが必要とするクエリを組み立てやすくなります。REST APIではリソースがエンドポイントで分離されているので何度もリクエストしなければならなかったのが、1回のリクエストで取得できるようになったりします。
  • さらにデータベースレイヤーの拡張パターンのメタ情報を用いることで、メタ情報をそのままスキーマに反映することができます。つまりクライアントごとに独自のスキーマを提供することができます。これをいち早く実現したサービスにGraphCMSというサービスがあります。

SearchTemplate

  • Elasticsearchにある機能で、予め検索条件とパラメータ項目を設定したqueryを登録しておき、クライアントからパラメータを与えて検索することができます。任意のqueryを利用者ごとに設定できるようになり、非常に自由度が高いです。
  • AWSであればマネージドサービスがありますのでクラスタの管理コストは低いですが、費用的なコストはやや高めです。瞬時にスケールできないのでバースト対策にはレートリミットを設けるなど注意が必要です。

プレゼンテーションレイヤー

メールやプッシュ通知などのプレゼンテーションレイヤーの拡張性です。

テンプレート

  • テンプレートを用意しておき可変部分を変数化してパラメータを埋め込みます。
  • 細かい話ですが、プッシュ通知の拡張性で大変なのがバッジです。全て一律で1にするのなら問題ないのですが、2以上の数字を扱う場合はサービスとクライアントで数字の同期(整合性合わせ)が必要になるためとても大変です。

その他

サービスの外側で拡張するパターンです。

Webhook

  • サービスは特定のイベントをトリガーとして、データを任意の場所に投げます。
  • 利用者は受け取ったデータを使って任意の処理を行うことができます。

Event Messaging

  • サービスは、特定のイベントをトリガーとして、メッセージをキューやPub/Subなどのサービスに追加します。
  • 利用者はキューやPub/Subから受け取ったデータを使って任意の処理を行うことができます。

Proxy

  • サービスとクライアントアプリの間に位置して、データを変換したり、機能を拡張したり、外部のサービスと連携したりします。

おわりに

  • 目次を書き始めたときは、本ができそうなくらい書けることがたくさんあると思ったのですが、いざ書いてみるとまとまった文章を書くのが難しくて、持っている情報を全然出しきれず薄い内容になってしまいました。
  • 全体的に見ると目新しさは少ないですが、個人的にはロジックの埋め込みはServerlessのパラダイムで出現した新しいパターンかなと思っていますので、今後深堀りしていきたいと思っています。

GraphQL Subscription の調査

GraphQLでSubscriptionを実装する場合、どういう構成になるのかなと思い調べてみました。

GraphQL Subscription Spec

SubscriptionはGraphQL specのJune2018で仕様が策定されています。

https://graphql.github.io/graphql-spec/June2018/#sec-Subscription

仕様で言及されていることはインターフェイスのみで、ネットワークレイヤーの仕様は言及されていません。 GraphQLはあくまでインターフェイスの仕様ということですね。

Implementation

How To GraphQL の Tutorial

https://www.howtographql.com/graphql-js/7-subscriptions/

WebSocketを使っていることが伺えますが、実装はprismaのライブラリにラップされています。 今回はpureなライブラリで実装する方法を知りたかったので深入りはしません。

Tutorial: GraphQL Subscriptions on the Server

Apolloの記事です。

https://blog.apollographql.com/tutorial-graphql-subscriptions-server-side-e51c32dc2951

graphql-subscriptionssubscriptions-transport-ws を使って実装していることがわかります。

こちらもやはりWebSocketです。

他にも色々調べてみましたが、javascriptではこの2つのライブラリがスタンダードと言って良さそうです。

ちなみにこの2つのライブラリはapolloがオーナーですが、apollo専用ではありません。

実装してみる

GraphQLの基本的な部分はサラッと作りたかったので、Apollo Serverを使うことにします。

https://www.apollographql.com/docs/apollo-server/getting-started/

ここまでできたら、 graphql-subscriptions のREADMEを読みながらSubscriptionを組み込みます。

できました。あれ?WebScoketについて何も設定していないのにWebSocketになっている。あとから気づきましたが、 apollo-server を使う場合は、 apollo-server-codegraphql-subscriptionsubscriptions-transport-ws が含まれているので、importは不要なのでした。

まとめ

GraphQL で Subscription を実装する場合、現状ではWebSocketがデファクトスタンダードと言えそうです。

実際にProduction環境で利用するには、以下も必要と思いますのでサンプルを実装してみようと思います。

  • PubSubをスケールさせる方法
  • GraphQLサーバ(WebSocketサーバ)をスケールさせる方法

now.sh 2.0 Introduction

最近注目しているnow.shの2.0のコンセプト・機能について紹介します。

now.shとは

Now – Global Serverless Deployments

アプリケーションをすばやくデプロイするためのプラットフォームサービスです。 ごく小さな設定ファイルを書いて、nowコマンドを実行するだけ(本当にこれだけ!)でデプロイが完了します。 標準でhttpsがサポートされ、CDNも提供されます。 無料プランから始められアクセス数などに応じて従量課金になります。

2.0のコンセプト

2.0以前をあまり知らないのですが、以前はサーバアプリケーション(常時起動のサービス)やBateですがDockerコンテナをサポートしていました。しかし、2.0ではDockerは完全に姿を消し、サーバアプリケーションも以前からの利用者のために一応残したという程度になっています。重厚なサーバアプリケーションをターゲットから外し、フロントエンドアプリケーションとAWS Lambdaのようなファンクションサービスにピボットしたことが伺えます。

2.0の機能

ZEIT – Now 2.0

builder

設定ファイルにアプリケーションに合ったbuilderを指定することで、アプリケーションのビルドやランタイムの提供が行われます。例えば、Next.jsのプロジェクトの場合は @now/next というbuilderが標準であり、これを指定するだけで静的なコンテンツ、フロントエンドアプリケーション、サーバアプリケーションが一気にデプロイされます。builderは自分で作ることもできるので、アプリケーションに合ったbuilderを作ったり、他の人が作ったbuilderを使うこともできます。実行環境はnow.shが提供するランタイムから選択することになります。ランタイムはもう少し数が増えたり新しいバージョンを早くサポートしてくれると良いですね。

Monorepo × Massive build parallelization

1つのプロジェクトに複数のファンクションのエントリーポイントを持つことを、Monorepo(1つのリポジトリでたくさんの機能を管理する)と表現しています。Next.jsの例のように、1つのプロジェクトに実行環境の異なる複数のエントリーポイントを持つことも含まれているかもしれません。Monorepoにすると、エンドポイントごとに並列でビルドが実行されるためビルド時間が早く、実行時にも実行ファイルが小さく、起動時間が早いというメリットがあります。now.shがroutingとdispatchを受け持ってくれる感じですね。こういうMonorepoなFunctionsを作るFrameworkってあったりするんでしょうか。

Universal Cloud

now.shの利用者には見えない部分ですが、ベンダーロックインを避けるため、インフラにはAWSGCPを両方使えるようにしているようです。

誰が使うといいの?

個人的な所感としては以下で使えるかと思いました。

  • フロントエンドアプリケーション
  • 規模の小さなファンクションサービス
  • プロトタイピング

サーバサイドアプリケーション(Webサーバが起動して待機するタイプ)も一応サポートされていて、プロトタイピングレベルならありかと思います。実際にExpressやNestjsのミニマムのアプリケーションなら動かすことができたんですが、いま自分が作っているアプリケーションがまだ動かない(504が返る)状態なので、もう少し調査をしてみようと思います。