Anzeige

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

Public Relations um Yahoo!デベロッパーネットワーク
27. Mar 2017
Anzeige

Más contenido relacionado

Presentaciones para ti(20)

Anzeige

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

Más de Yahoo!デベロッパーネットワーク(20)

Anzeige

Último(20)

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

  1. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Yasuharu GOTO (@ono_matope) 2017/03/25 Goでヤフーの分散オブジェクトストレージを作った話 Go Conference 2017 Spring
  2. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. About me 名前: Yasuharu GOTO Twitter: @ono_matope Github: @matope 所属: ヤフー株式会社 データプラットフォーム開発本部 Go歴:3年 コントリビューション: Expect:100-Continueのクライアント実装 (Go1.6) 2
  3. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Agenda • Goでヤフーの基盤ストレージ Dragon を作った話 • Goでの耐障害性向上テクニック 3
  4. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. Dragon
  5. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Dragon • ヤフーで開発している分散オブジェクトストレージ • デザインゴール:高速、高スケーラビリティ、高可用性、低コスト • Go言語 • S3 互換 API • 2016年1月 リリース (14ヶ月の本番稼働実績) 5
  6. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Why we built a new Object Storage? 6 • Octagon(2011-) • 最初の内製オブジェクトストレージ • 諸々の技術的課題から、代替を検討 • 遅い・不安定・運用しづらい・レガシー・etc... • 既存のOSS? • Riak CS : 一部で導入するも、性能がサービス要件を満たさず • OpenStack Swift : スケーラビリティに不安 • パブリッククラウド? • 自社DCと比べてコスト面で不利
  7. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Why we built a new Object Storage? 7 じゃあ作ろう 2014年 実装スタート
  8. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.8 クラスタ数: 2 格納オブジェクト数: 100億 格納データ量: 9PB サービス利用多数(右) その他社内システム多数 Presto (in experiment) 利用規模 Yahoo!オークション (画像) Yahoo!ニュース・トピックス/個人 (画像) Yahoo!ディスプレイアドネットワーク (画像/動画) Yahoo!ブログ (画像) Yahoo!スマホきせかえ (画像) Yahoo!トラベル (画像) Yahoo!不動産 (画像) Yahoo!知恵袋 (画像) Yahoo!飲食店予約 (画像) Yahoo!みんなの政治 (画像) Yahoo!ゲーム (コンテンツ) Yahoo!ブックストア (コンテンツ) Yahoo!ボックス (データ) ネタりか (記事画像) etc...
  9. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Performance (with Riak CS/参考値) • Dragon: API*1, Storage*1,Cassandra*3 • Riak CS: haproxy*1, stanchion*1, Riak (KV+CS)*3 • CassandraとStanchion以外はすべて同一構成のHWを使用。 9 0 500 1000 1500 2000 2500 3000 3500 1 5 10 50 100 200 400 Requests/sec # of Threads GET Object 10KB Throughput Riak CS Dragon 0 100 200 300 400 500 600 700 800 900 1000 1 5 10 50 100 200 400 Requests/sec # of Threads PUT Object 10KB Throughput Riak CS Dragon
  10. Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. 10 Architecture API Nodes HTTP (S3 API) Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node ... Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Cluster Blob Metadata Meta DB
  11. Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. 11 Architecture API Nodes HTTP (S3 API) Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node ... Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Cluster Blob Metadata Meta DB
  12. Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. 12 Upload API Nodes HTTP PUT Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Meta DB ... Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node 格納位置を含む オブジェクトメタデータ HTTP PUT
  13. Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. 13 Download API Nodes HTTP GET Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Meta DB ... Storage Node Storage Node Storage Node Storage Node Storage Node Storage Node Storage Cluster 格納位置を含む オブジェクトメタデータ HTTP GET
  14. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Architecture • シンプル is ベスト • ブラックボックスを減らす • メタDBとしてCassandraを利用 • 十分な可用性とスケーラビリティ • 他にもいろいろな工夫が • 今日は省略 14
  15. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. Go Failure Tlerance Tips: 1. Circuit Breaker 2. Timeout for Streaming
  16. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. Go Tips 1: Circuit Breaker
  17. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Circuit Breaker Storage Nodeが障害で停止・ネットワーク断の場合 • ストレージへのリクエストがコネクションタイムアウトの間ブロック • 他ノードにフェイルオーバーするまでのレイテンシがユーザーリクエストに影響 • 落ちているノードへのリクエストは避けたい 17 API Node Storage Nodes 数秒間ブロック数秒間ブロック 😢
  18. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.18 Circuit Breaker Circuit Breakerパターン ある処理のエラー頻度が閾値をこえたら、 しばらくは処理を省略(Circuit Open)して 即座にエラーを返すパターン タイムアウト待ちを省略してエラーを返せる Remote Server Success Error!(1) Error!(2) Trip Error!(3) Circuit Open! Circuit Breaker Trip
  19. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Circuit Breaker http://github.com/rubyist/circuitbreaker 19
  20. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. API Node 20 Circuit Breaker Dragonでは、Circuit Breakerを HTTPクライアントのDialContextに適用 • 接続先アドレスのDialをCBで管理 • n回連続でDialに失敗したNodeは Circuit Openし、一定期間Dialしない • 即座にフォールバック可能 Storage NodesCircuit Breakers node1 node2 node3 node4 node5 client := http.Client{ Transport: &http.Transport{ DialContext: (&CircuitDialer{}).DialContext, }, }
  21. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. 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 } Circuit Breaker 21
  22. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. 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 } Circuit Breaker 22 接続先ごとにCircuitBreakerを用意
  23. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. 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 } Circuit Breaker 23 接続先のCircuitBreaker がなければ作成 接続先ごとにCircuitBreakerを用意
  24. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. 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 } Circuit Breaker 24 接続先のCircuitBreaker がなければ作成 DialContextにCircuitBreakerを適用 接続先ごとにCircuitBreakerを用意
  25. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. API Node Circuit Breaker Circuit Breakerパターンにより、ノード障害時のリクエストレイテンシを保護 25 Storage Nodes Circuit Dialer 😄
  26. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. Go Tips 2: I/O Timeout for Streaming
  27. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Download 27 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() _, err = io.Copy(w, resp.Body) return err } バックエンドストレージに HTTP GETリクエストを発行 ストレージからのレスポンスボディを io.CopyでResponseWriterに転送 単純化したダウンロードハンドラ実装
  28. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Download 28 • io.Copy(dest io.Writer, src io.Reader) • src (io.Reader) を dest (io.Writer) にコピーする関数 • 内部では32KBバッファ bufを確保し、 src.Read(buf), dest.Write(buf)を繰り返し呼ぶ API Node io.Copy dest.Write() src.Read() GET Response.BodyResponseWriter
  29. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Case1: Storage Blocking on Download 29 1. もしストレージノードにNW障害やHW障害が起こると、 Response.Bodyが流れてこなくなる 2. io.Copy()内のsrc.Read()が無限にブロックする 3. ダウンロード転送が止まる! API Node io.Copy dest.Write() src.Read() GET Response.BodyResponseWriter 😢
  30. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Case2: Client Blocking on Download 30 1. 逆に、何らかの問題で、クライアントがレスポンスのダウンロードを止めると、 ResponseWriter.Write() が無限にブロックする 2. io.Copy()が進まず、ダウンロード転送が止まる 3. リソースリーク! API Node io.Copy dest.Write() src.Read() GET Response.BodyResponseWriter 😢
  31. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Upload 31 アップロードもio.Copyを使っている。 src: クライアントRequest.Body dest: 3ノードへのPUTリクエストのBody(MultiWriterとPipeを経由) API Node PUT Request.Body io.Copy Multi WriterRequest.Body src.Read() dest.Write() PUT Request.Body PUT Request.BodyWriter – Pipe - Reader Writer – Pipe - Reader Writer – Pipe - Reader
  32. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Case3: Storage Blocking on Upload 32 API Node PUT Request.Body io.Copy Multi WriterRequest.Body src.Read() dest.Write() PUT Request.Body PUT Request.BodyWriter – Pipe - Reader Writer – Pipe - Reader Writer – Pipe - Reader 1. ストレージノードに障害が起こると、リクエストBodyが送れなくなる io.Copy()内のsrc.Read()が無限にブロックする 2. アップロード転送が止まる! 😢
  33. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Stream 33 • まとめると… • ストレージ、クライアントどちらかでデータ転送が止まると、 Read()またはWrite()が無限にブロックする • ダウンロード、アップロードのストリームが止まったままになる • リソースリークが起こる
  34. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Stream 34 • なんとかしてI/Oのブロックを検知して、 タイムアウトエラーとしてハンドリングしたい
  35. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Stream 35 • net.Conn.SetDeadline() ? • net.ConnのRead(),Write()に時間制限を指定する機能 • http.ServeHTTPはクライアントのnet.Connにアクセスできない • http.TimeoutHandler ? • データサイズやユーザーの通信帯域がバラバラなので 固定のタイムアウト値が設定できない • サイズ:1Byte〜5GB、帯域:100Kbps 〜 10Gbps • The complete guide to Go net/http timeouts • https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
  36. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Our approach 36 • タイムアウト機能付きの io.Reader, io.Writerを実装して、 すべてのストリーム経路に仕掛ける Request .Body Request .Body API Node io.Copy GET Response.BodyResponseWriter io.CopyRequest.Body Request .Body Writer – Pipe - Reader Writer – Pipe - Reader Writer – Pipe - Reader Multi Writer On Download On Upload Timeout Writer Timeout Writer Timeout Writer Timeout Writer Timeout Reader Timeout Reader
  37. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. TimeoutWriter/Reader • Write()がTimeout時間で完了しなかったら関数 Fn が実行されるWriteラッパー • シンプル! • TimeoutReaderも同様に定義 37 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) }
  38. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Streaming 38 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 } 適用前
  39. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Streaming 39 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 } } Copyを別Goroutineに 適用後
  40. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Streaming 40 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 } } ResponseWriterを TimeoutWriterでラップ。 io.Copyのdestをtwに 適用後
  41. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Streaming 41 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 } } Writeが10秒間ブロックしたら timeoutChに送信 適用後 ServeHTTPを抜けると、 wはClose()してCopyはエラーに
  42. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Timeout for Streaming 42 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 } } io.Copyが終了したら resultChに送信 適用後
  43. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Performance Impact? 43 • io.Read(), io.Write()のタイムアウトをシンプルな実装でハンドルできた😄 • でも遅いんでしょう? • すべてのRead()/Write()にタイマーを仕掛けるなんて…
  44. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Performance Impact? 44 性能インパクトは僅少 • 100KB ダウンロード スループット: -2% 〜 0% • 100KB アップロード スループット: +3% 〜 -5% 0 2000 4000 6000 8000 10000 12000 20 50 100 200 400 800 Requests/sec # of Threads GET Object 100KB Throughput No Timeout Timeout 0 500 1000 1500 2000 2500 3000 3500 4000 20 50 100 200 400 800 Requests/sec # of Threads PUT Object 100KB Throughput No Timeout Timeout
  45. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Go Failure Tlerance Tips 45 大規模な分散システムに要求される耐障害性をシンプルに実装 • CircuitBreaker • Timeout Read/Write
  46. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved.Copyright © 2017 Yahoo Japan Corporation. All Rights Reserved. Conclusion
  47. Copyrig ht © 2017 Yahoo Japan Corporation. All Rig hts Reserved. Conclusion • ヤフーではGoで大規模な分散オブジェクト ストレージDragonを開発・運用中です • サービス基盤のモダン化を進行中 • We’re Hiring • Thank you! 47
Anzeige