More Related Content
Similar to Goでヤフーの分散オブジェクトストレージを作った話 Go Conference 2017 Spring (20)
More from Yahoo!デベロッパーネットワーク (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