Goでヤフーの分散オブジェクトストレージを作った話 Go Conference 2017 Spring

1.1K Views

March 27, 17

スライド概要

Go Conference 2017Springの発表資料です。
Yahoo! JAPANで開発しているGo製オブジェクトストレージ「Dragon」の紹介と、Dragonで利用している耐障害性向上のためのテクニックについて説明します。

profile-image

2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

Goでヤフーの分散オブジェクトストレージを作った話 Go Conference 2017 Spring Yasuharu GOTO (@ono_matope) 2017/03/25 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

2.

About me 名前: Yasuharu GOTO Twitter: @ono_matope Github: @matope 所属: ヤフー株式会社 データプラットフォーム開発本部 Go歴:3年 コントリビューション: Expect:100-Continueのクライアント実装 (Go1.6) 2 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

3.

Agenda • • 3 Goでヤフーの基盤ストレージ Dragon を作った話 Goでの耐障害性向上テクニック Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

4.

Dragon 0 1 7 Yahoo Yaho o Japan Jap anCorporation. Co rp o ratio n.AllAll Rig hts Reserved . Co p yrig ht © Copyright Rights Reserved. © 22017

5.

Dragon 5 • ヤフーで開発している分散オブジェクトストレージ • • • デザインゴール:高速、高スケーラビリティ、高可用性、低コスト Go言語 S3 互換 API • 2016年1月 リリース (14ヶ月の本番稼働実績) Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

6.

Why we built a new Object Storage? • Octagon(2011-) • • • 既存のOSS? • • • Riak CS : 一部で導入するも、性能がサービス要件を満たさず OpenStack Swift : スケーラビリティに不安 パブリッククラウド? • 6 最初の内製オブジェクトストレージ 諸々の技術的課題から、代替を検討 • 遅い・不安定・運用しづらい・レガシー・etc... 自社DCと比べてコスト面で不利 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

7.

Why we built a new Object Storage? じゃあ作ろう 2014年 実装スタート 7 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

8.

利用規模 クラスタ数: 2 格納オブジェクト数: 100億 格納データ量: 9PB サービス利用多数(右) その他社内システム多数 Presto (in experiment) 8 Co p yrig ht © 2 0 1 7 Yahoo!オークション (画像) Yahoo!ニュース・トピックス/個人 (画像) Yahoo!ディスプレイアドネットワーク (画像/動画) Yahoo!ブログ (画像) Yahoo!スマホきせかえ (画像) Yahoo!トラベル (画像) Yahoo!不動産 (画像) Yahoo!知恵袋 (画像) Yahoo!飲食店予約 (画像) Yahoo!みんなの政治 (画像) Yahoo!ゲーム (コンテンツ) Yahoo!ブックストア (コンテンツ) Yahoo!ボックス (データ) ネタりか (記事画像) etc... Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

9.

Performance (with Riak CS/参考値) GET Object 10KB Throughput PUT Object 10KB Throughput 3500 1000 900 800 2500 2000 Riak CS 1500 Dragon 1000 Requests / sec Requests / sec 3000 700 600 500 Dragon 300 200 500 100 0 0 1 9 Riak CS 400 5 10 50 100 # of Threads 200 400 1 5 • Dragon: API*1, Storage*1,Cassandra*3 • Riak CS: haproxy*1, stanchion*1, Riak (KV+CS)*3 • CassandraとStanchion以外はすべて同一構成のHWを使用。 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved . 10 50 100 # of Threads 200 400

10.

Architecture HTTP (S3 API) API Nodes Meta DB Metadata Blob Storage Cluster Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node ... 10 Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved.

11.

Architecture HTTP (S3 API) API Nodes Meta DB Metadata Blob Storage Cluster Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node ... 11 Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved.

12.

Upload HTTP PUT API Nodes 格納位置を含む オブジェクトメタデータ Meta DB HTTP PUT Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node ... 12 Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved.

13.

Download HTTP GET API Nodes 格納位置を含む オブジェクトメタデータ Meta DB HTTP GET Storage Cluster Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node ... 13 Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved.

14.

Architecture • シンプル is ベスト • ブラックボックスを減らす • メタDBとしてCassandraを利用 • 十分な可用性とスケーラビリティ • 他にもいろいろな工夫が • 今日は省略 14 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

15.

Go Failure Tlerance Tips: 1. Circuit Breaker 2. Timeout for Streaming 0 1 7 Yahoo Yaho o Japan Jap anCorporation. Co rp o ratio n.AllAll Rig hts Reserved . Co p yrig ht © Copyright Rights Reserved. © 22017

16.

Go Tips 1: Circuit Breaker 0 1 7 Yahoo Yaho o Japan Jap anCorporation. Co rp o ratio n.AllAll Rig hts Reserved . Co p yrig ht © Copyright Rights Reserved. © 22017

17.

Circuit Breaker Storage Nodeが障害で停止・ネットワーク断の場合 • ストレージへのリクエストがコネクションタイムアウトの間ブロック • 他ノードにフェイルオーバーするまでのレイテンシがユーザーリクエストに影響 • 落ちているノードへのリクエストは避けたい 数秒間ブロック 数秒間ブロック API Node 17 😢 Storage Nodes Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

18.

Circuit Breaker Remote Server Circuit Breaker Circuit Breakerパターン ある処理のエラー頻度が閾値をこえたら、 しばらくは処理を省略(Circuit Open)して 即座にエラーを返すパターン Success Error!(1) タイムアウト待ちを省略してエラーを返せる Error!(2) Error!(3) Circuit Open! Trip Trip 18 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

19.

Circuit Breaker http://github.com/rubyist/circuitbreaker 19 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

20.

Circuit Breaker Circuit Breakers Dragonでは、Circuit Breakerを HTTPクライアントのDialContextに適用 Storage Nodes node1 node2 • 接続先アドレスのDialをCBで管理 • n回連続でDialに失敗したNodeは Circuit Openし、一定期間Dialしない • 即座にフォールバック可能 node3 node4 node5 API Node 20 Co p yrig ht © 2 0 1 7 client := http.Client{ Transport: &http.Transport{ DialContext: (&CircuitDialer{}).DialContext, }, } Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

21.
[beta]
Circuit Breaker
type CircuitDialer struct {
mu
sync.Mutex
dialer
net.Dialer
breakers map[string]*circuit.Breaker
}
func (d *CircuitDialer) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
d.mu.Lock()
if d.breakers == nil {
d.breakers = map[string]*circuit.Breaker{}
}
if _, ok := d.breakers[addr]; !ok {
d.breakers[addr] = circuit.NewConsecutiveBreaker(4)
}
cb := d.breakers[addr]
d.mu.Unlock()
err = cb.CallContext(ctx, func() error {
conn, err = d.dialer.DialContext(ctx, network, addr)
return err
}, 0)
return conn, err
}

21

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

22.
[beta]
Circuit Breaker
type CircuitDialer struct {
mu
sync.Mutex
dialer
net.Dialer
breakers map[string]*circuit.Breaker
}

接続先ごとにCircuitBreakerを用意

func (d *CircuitDialer) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
d.mu.Lock()
if d.breakers == nil {
d.breakers = map[string]*circuit.Breaker{}
}
if _, ok := d.breakers[addr]; !ok {
d.breakers[addr] = circuit.NewConsecutiveBreaker(4)
}
cb := d.breakers[addr]
d.mu.Unlock()
err = cb.CallContext(ctx, func() error {
conn, err = d.dialer.DialContext(ctx, network, addr)
return err
}, 0)
return conn, err
}

22

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

23.
[beta]
Circuit Breaker
type CircuitDialer struct {
mu
sync.Mutex
dialer
net.Dialer
breakers map[string]*circuit.Breaker
}

接続先ごとにCircuitBreakerを用意

func (d *CircuitDialer) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
d.mu.Lock()
if d.breakers == nil {
d.breakers = map[string]*circuit.Breaker{}
}
接続先のCircuitBreaker
if _, ok := d.breakers[addr]; !ok {
がなければ作成
d.breakers[addr] = circuit.NewConsecutiveBreaker(4)
}
cb := d.breakers[addr]
d.mu.Unlock()
err = cb.CallContext(ctx, func() error {
conn, err = d.dialer.DialContext(ctx, network, addr)
return err
}, 0)
return conn, err
}

23

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

24.
[beta]
Circuit Breaker
type CircuitDialer struct {
mu
sync.Mutex
dialer
net.Dialer
breakers map[string]*circuit.Breaker
}

接続先ごとにCircuitBreakerを用意

func (d *CircuitDialer) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
d.mu.Lock()
if d.breakers == nil {
d.breakers = map[string]*circuit.Breaker{}
}
接続先のCircuitBreaker
if _, ok := d.breakers[addr]; !ok {
がなければ作成
d.breakers[addr] = circuit.NewConsecutiveBreaker(4)
}
cb := d.breakers[addr]
d.mu.Unlock()
err = cb.CallContext(ctx, func() error {
conn, err = d.dialer.DialContext(ctx, network, addr)
return err
}, 0)
return conn, err
}

24

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

DialContextにCircuitBreakerを適用

25.

Circuit Breaker Circuit Breakerパターンにより、ノード障害時のリクエストレイテンシを保護 Circuit Dialer 😄 25 API Node Storage Nodes Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

26.

Go Tips 2: I/O Timeout for Streaming 0 1 7 Yahoo Yaho o Japan Jap anCorporation. Co rp o ratio n.AllAll Rig hts Reserved . Co p yrig ht © Copyright Rights Reserved. © 22017

27.

Download 単純化したダウンロードハンドラ実装 func (s *Server) Download(w http.ResponseWriter, r *http.Request) error { // ... resp, err := s.HTTPClient.Get(blobURL) if err != nil { return err } defer resp.Body.Close() } 27 _, err = io.Copy(w, resp.Body) return err Co p yrig ht © 2 0 1 7 バックエンドストレージに HTTP GETリクエストを発行 ストレージからのレスポンスボディを io.CopyでResponseWriterに転送 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

28.

Download • io.Copy(dest io.Writer, src io.Reader) • src (io.Reader) を dest (io.Writer) にコピーする関数 • 内部では32KBバッファ bufを確保し、 src.Read(buf), dest.Write(buf)を繰り返し呼ぶ io.Copy ResponseWriter dest.Write() src.Read() API Node 28 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved . GET Response.Body

29.

Case1: Storage Blocking on Download 1. もしストレージノードにNW障害やHW障害が起こると、 Response.Bodyが流れてこなくなる 2. io.Copy()内のsrc.Read()が無限にブロックする 3. ダウンロード転送が止まる! io.Copy ResponseWriter dest.Write() src.Read() API Node 😢 29 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved . GET Response.Body

30.

Case2: Client Blocking on Download 1. 逆に、何らかの問題で、クライアントがレスポンスのダウンロードを止めると、 ResponseWriter.Write() が無限にブロックする 2. io.Copy()が進まず、ダウンロード転送が止まる 3. リソースリーク! io.Copy ResponseWriter dest.Write() src.Read() GET Response.Body API Node 😢 30 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

31.

Upload アップロードもio.Copyを使っている。 src: クライアントRequest.Body dest: 3ノードへのPUTリクエストのBody(MultiWriterとPipeを経由) io.Copy Request.Body src.Read() dest.Write() Multi Writer Writer – Pipe - Reader PUT Request.Body Writer – Pipe - Reader PUT Request.Body Writer – Pipe - Reader PUT Request.Body API Node 31 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

32.

Case3: Storage Blocking on Upload 1. ストレージノードに障害が起こると、リクエストBodyが送れなくなる io.Copy()内のsrc.Read()が無限にブロックする 2. アップロード転送が止まる! io.Copy Request.Body 😢 32 src.Read() dest.Write() Multi Writer Writer – Pipe - Reader PUT Request.Body Writer – Pipe - Reader PUT Request.Body Writer – Pipe - Reader PUT Request.Body API Node Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

33.

Timeout for Stream • まとめると… • ストレージ、クライアントどちらかでデータ転送が止まると、 Read()またはWrite()が無限にブロックする ダウンロード、アップロードのストリームが止まったままになる リソースリークが起こる • • 33 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

34.

Timeout for Stream • 34 なんとかしてI/Oのブロックを検知して、 タイムアウトエラーとしてハンドリングしたい Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

35.

Timeout for Stream • net.Conn.SetDeadline() ? • net.ConnのRead(),Write()に時間制限を指定する機能 • http.ServeHTTPはクライアントのnet.Connにアクセスできない • http.TimeoutHandler ? • データサイズやユーザーの通信帯域がバラバラなので 固定のタイムアウト値が設定できない • • The complete guide to Go net/http timeouts • 35 サイズ:1Byte〜5GB、帯域:100Kbps 〜 10Gbps https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

36.

Our approach • タイムアウト機能付きの io.Reader, io.Writerを実装して、 すべてのストリーム経路に仕掛ける Timeout Writer Request .Body Timeout Writer Request .Body Timeout Writer Request .Body Writer – Pipe - Reader Request.Body Timeout Reader io.Copy Multi Writer On Upload Writer – Pipe - Reader Writer – Pipe - Reader ResponseWriter Timeout Writer GET Response.Body io.Copy Timeout Reader On Download API Node 36 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

37.

TimeoutWriter/Reader • Write()がTimeout時間で完了しなかったら関数 Fn が実行されるWriteラッパー • シンプル! • TimeoutReaderも同様に定義 type TimeoutWriter struct { W io.Writer Timeout time.Duration Fn func() } func (w *TimeoutWriter) Write(p []byte) (int, error) { timer := time.AfterFunc(w.Timeout, w.Fn) defer timer.Stop() return w.W.Write(p) } 37 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

38.

Timeout for Streaming 適用前 func (s *Server) Download(w http.ResponseWriter, r *http.Request) error { // ... blob, _ := s.GetBlobStream(ctx, object, byteRange) n, err := io.Copy(w,blob) return err } 38 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

39.
[beta]
Timeout for Streaming
適用後
func (s *Server) Download(w http.ResponseWriter, r *http.Request) error {
// ...
blob, _ := s.GetBlobStream(ctx, object, byteRange)
timeoutCh := make(chan struct{}, 1)
resultCh := make(chan resultAndError, 1)
go func() {
tw := TimeoutWriter{
W: w,
Timeout: 10 * time.Second,
Fn: func() { timeoutCh <- struct{}{} },
}
n, err := io.Copy(tw,blob)
resultCh <- resultAndError{n:n, err:err}
}()
select {
case <-timeoutCh:
return RequestTimeoutError
case result := <-resultCh:
return result.err
}
}

39

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

Copyを別Goroutineに

40.
[beta]
Timeout for Streaming
適用後
func (s *Server) Download(w http.ResponseWriter, r *http.Request) error {
// ...
blob, _ := s.GetBlobStream(ctx, object, byteRange)
timeoutCh := make(chan struct{}, 1)
resultCh := make(chan resultAndError, 1)
go func() {
tw := TimeoutWriter{
W: w,
Timeout: 10 * time.Second,
Fn: func() { timeoutCh <- struct{}{} },
}
n, err := io.Copy(tw,blob)
resultCh <- resultAndError{n:n, err:err}
}()
select {
case <-timeoutCh:
return RequestTimeoutError
case result := <-resultCh:
return result.err
}
}

40

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

ResponseWriterを
TimeoutWriterでラップ。
io.Copyのdestをtwに

41.
[beta]
Timeout for Streaming
適用後
func (s *Server) Download(w http.ResponseWriter, r *http.Request) error {
// ...
blob, _ := s.GetBlobStream(ctx, object, byteRange)
timeoutCh := make(chan struct{}, 1)
resultCh := make(chan resultAndError, 1)
go func() {
tw := TimeoutWriter{
W: w,
Timeout: 10 * time.Second,
Fn: func() { timeoutCh <- struct{}{} },
}
n, err := io.Copy(tw,blob)
resultCh <- resultAndError{n:n, err:err}
}()
select {
case <-timeoutCh:
return RequestTimeoutError
case result := <-resultCh:
return result.err
}
}

41

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

Writeが10秒間ブロックしたら
timeoutChに送信

ServeHTTPを抜けると、
wはClose()してCopyはエラーに

42.
[beta]
Timeout for Streaming
適用後
func (s *Server) Download(w http.ResponseWriter, r *http.Request) error {
// ...
blob, _ := s.GetBlobStream(ctx, object, byteRange)
timeoutCh := make(chan struct{}, 1)
resultCh := make(chan resultAndError, 1)
go func() {
tw := TimeoutWriter{
W: w,
Timeout: 10 * time.Second,
Fn: func() { timeoutCh <- struct{}{} },
}
n, err := io.Copy(tw,blob)
resultCh <- resultAndError{n:n, err:err}
}()
select {
case <-timeoutCh:
return RequestTimeoutError
case result := <-resultCh:
return result.err
}
}

42

Co p yrig ht © 2 0 1 7

Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

io.Copyが終了したら
resultChに送信

43.

Performance Impact? 43 • io.Read(), io.Write()のタイムアウトをシンプルな実装でハンドルできた😄 • でも遅いんでしょう? • すべてのRead()/Write()にタイマーを仕掛けるなんて… Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

44.

Performance Impact? 性能インパクトは僅少 • 100KB ダウンロード スループット: -2% 〜 0% • 100KB アップロード スループット: +3% 〜 -5% GET Object 100KB Throughput PUT Object 100KB Throughput 12000 4000 3500 8000 6000 No Timeout 4000 Timeout 2000 3000 2500 2000 No Timeout 1500 Timeout 1000 500 0 0 20 44 Requests / sec Requests / sec 10000 50 100 200 # of Threads 400 800 Co p yrig ht © 2 0 1 7 20 50 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved . 100 200 # of Threads 400 800

45.

Go Failure Tlerance Tips 大規模な分散システムに要求される耐障害性をシンプルに実装 • CircuitBreaker • Timeout Read/Write 45 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .

46.

Conclusion 0 1 7 Yahoo Yaho o Japan Jap anCorporation. Co rp o ratio n.AllAll Rig hts Reserved . Co p yrig ht © Copyright Rights Reserved. © 22017

47.

Conclusion • ヤフーではGoで大規模な分散オブジェクト ストレージDragonを開発・運用中です • サービス基盤のモダン化を進行中 • We’re Hiring • Thank you! 47 Co p yrig ht © 2 0 1 7 Yaho o Jap an Co rp o ratio n. All Rig hts Reserved .