けいぞうのメモ帳

言語設計のお勉強

QUICメモ "Design and Internet-Scale Deployment"

この記事は ACM SIGCOMM “17において発表される The QUIC Transport Protocol: Design and Internet-Scale Deployment の紹介です。 僕が読んだことや関連したdraftをつらつらと記述していきます。

このpaperはIETFにおいて標準化作業中のQUIC transport protocol の動機と機能、そしてGoogle内部における導入事例の紹介を行うものです。 paperの前半は機能の説明、ストリーム多重化やAEAD、ハンドシェイクなどの説明、 後半はGoogleにおける運用状況の報告になります。

Googleは、数年前から標準化に先んじて前進となるプロトコルを実装していました。 これはGoogle QUIC(以下 GQUIC)と呼ばれ、IETF quicwgにおいて標準化作業中のinternet draftの仕様を指してIETF QUIC(以下QUIC)と呼びます。

Google内部間やモバイル/ウェブのChromeYoutubeGoogle検索とGoogle間における通信は可能な限りでGQUICで行われています。 これはgoogle chrome(chromium)においてGQUICが有効になっている場合、 chrome://net-internals にてその通信を見ることが出来ます。

今回紹介する"The QUIC Transport Protocol: Desing and Internet-Scale Deployment"では、 Googleが行った数々の計測と実験の結果を公開しています。

  • Google検索とYoutubeにおけるモバイル、ウェブそれぞれの性能計測
  • 地域ごとの性能
  • サーバのCPU使用率
  • さまざまな条件における到達可能性の評価.

などです。

これらはGoogle Chromeのtreeにあり、https://github.com/google/proto-quic として公開されている実装を 用いて行われました。 GoogleはHTTPサーバを独自に実装していますが、それらもproto-quicの実装と同じものを用いています。

UDP関係

QUICはUDP上に実装されているため、UDPの事情に強く影響を受けます。 最たるものはUDPがsend and forgetであることですが、その他にも多くあります。

UDP PMTU

UDPは経路MTUを考慮した通信を行う仕様を含みません。 そのため、QUICは自らでIP フラグメンテーション対策を行う必要があります。
PMTU DiscoveryやMTU Discoveryを自らで行います。 UDPのPMTUの計測結果がFigure12 in Section 7.1に掲載されています。

Server

CPU Utilizatoin

QUICはTLS over TCPに比べていくらかのCPU使用率の増加が見込まれます。 これは

  1. QUICの内部状態
  2. TLSによる常時暗号化
  3. ストリーム並列化

が主な原因であるとされています。 1,2について記述します。

1 QUICの内部表現

QUICは一つのUDPのエンドポイントを複数のコネクションで共有して用います。 また、ひとつのコネクションを複数のストリームで共有して用います。 そのため、他のプロトコルスタックに比べてより複雑な内部状態を持つことになります。

Googleは、これを実装の検討により改善することが出来るとし、より適切な実装を行うことでたしかに性能は 改善されたとしました。また、まだ考慮すべき点は多くあり、性能の向上の余地は未だあるとしています。

  1. TLSによる常時暗号化

QUICはTLS1.3の多くの機能を暗号化や認証のために用いています。 ヘッダーは認証され、ペイロードは暗号化されます。 このコストは相応に高くつくため、CPU負荷となっています。 Googleは独自に最適化したChaCha20を用いることで負荷の軽減としました。

良い性能が出ないケース

QUICは常に他より良い結果が得られるわけではありません。 その事例についての記述も存在します。

  • 広い帯域幅、低いRTT、少ない損失
  • pre warmed endpoint
  • モバイル端末

QUICは高RTT,低帯域幅、高い損失の対策として様々な情報を付加しています。 そのため、それらが起こらない場合は情報を更かした分だけ転送効率が下がることがあります。

また、pre warmed connectionは0 RTT ハンドシェイクの恩恵を受けることができないため、 TCPより性能が低下することがあります。

モバイル端末での計算資源はデスクトップ端末でのそれよりも制約が厳しく、 効率の良いデータ転送を実現したとしても、処理しきれないことがあります。 また、モバイルアプリケーションの通信は、すでに特定用途で特化していることが多いため ネットワークスタックの改善の余地が少ないこともありえます。

haskell のcereal package example

{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveGeneric     #-}
module Main where

import           Data.Serialize
import           GHC.Generics

data Dist = Dist { distInt :: Int, distString :: String }
          deriving (Generic, Show)

instance Serialize Dist

main :: IO ()
main = let bs = encode d
        in do
           print bs
           case ( decode bs) of
             Right res -> print (res  :: Dist)
             Left str  -> print str
    where d = Dist { distInt = 1
                   , distString = "hello"}

ref:

Data.Binary.GetのGet MonadにおけるMonadFail

できること

Get Monadの中でControl.Monad.Fail.failを実行すると、runGetOrFailのときにLeftを取ることが出来る。 runGetOrFailが取る引数はユーザが定義した任意の構造体へのdecoderであるGet Monadのため、 Get Monadの失敗をユーザがハンドルできる。

MonadFail

モナドの中での失敗を意味する型クラス。 もともとはMonadのメソッドだったところを「分離していいよね?」という提案の元、型クラスに分離された経緯がある。

ref:

Control.Monad.Fail

PoC

{-# LANGUAGE OverloadedStrings #-}
module Main where


import           Data.Binary.Get

main :: IO ()
main = print $ f

f = case (runGetOrFail decode "") of
      Right (rest, _,f) -> ""
      Left _            -> "runGetOrFail return Left when called in Get Monad"


data Sample = Sample Int
            deriving Show

decode :: Get Sample
decode = fail "you shoul fail"

b読み飛ばしても良い経緯

inary packageのData.Binary.Getを正常に失敗させる方法を知りたい。
binary packageのData.Binary.Get.runGetOrFailでは 失敗するかもしれないGetモナドを実行することができる。 これをユーザがハンドルする方法がドキュメントに書いてなかった。 しかしGetモナドはMonadFailのインスタンスであり、MonadFailは モナドの失敗を扱うことが出来る型クラスであるためこれを用いたときに、失敗時の処理となるのではないかと思った。

一人ソフトウェアの基礎勉強会 草案

経緯

やり方

  • twitterハッシュタグ #self_taught_sf に色々書いていく。
  • ある程度まとまったらtogetterにでもまとめる
  • 作ったものはブログにまとめる

以下なんか合ったらリンクを貼る

haskellでbinaryにread&write

binary packageのGet/Put Monadを使いBinary classにgetとputを実装することでData.ByteString.Lazy.Internal.ByteStringとデータ構造を変換する事ができる。

ネットワークプロトコルフォーマットパーサの実装の参考として、http2 packageを覗いた。 unsafeperfomeIOを使ってWord8をPtr aとして扱っていた。おそらくこれは高速化のためだと思うのでベンチマークをあとで取ることにする。

gistaa91f312fdf7acb5c8a0e58a558ca8eb