無気力生活 (ノ ´ω`)ノ ~゜

脱力系エンジニア。てきとーに生きてます。

golangのディレクトリ構成について悩む

今年もあと僅かで終わる今日このごろですが、ようやく本腰上げてGoでバックエンド書き始めようとしました。

しかし早速ハマるディレクトリ設計_(:3」∠)_ 本来Goは$GOPATH配下で開発を進めます。しかし、今回はフロントエンド側のコードも含めてgithub上でgit管理したいのでこんな構成にしたい。

/
├  docker/
├  app/
└  front/

appというのがGoのAPI実装ですね。

参考書や、https://golang.org/doc/code.html を見る限り$GOPATH/github.com配下に管理されるべきなのですが、Go以外のコードが含まれているのに$GOPATH配下にあるの気持ち悪いなーと思うんです(´・ω・`)とはいえ、$GOPATH以外で開発しようとするとライブラリ周りの部分で面倒そうな対応が必要そうな予感がして面倒だな、と。

さて、この構成どうしたものか。
いろいろ考えたんですが、一番良いのは開発をdocker上に乗っけて、依存関係をdepで解決させることでした。上記でやりたかった構成を適用すると、ざっくりこんな配置になります。

/
├  docker/
│     -  app/
│       └ (今回はイメージそのまま使うのでcomposeへ)
│     - docker-compse.yml
├  app/
│     - code ....
└  front/
         - code ...
         - node_modules

golangのイメージをベースにこんなdocker-composeを書きます。

version: '3'
services:
  app:
    image: 'golang:latest'
    volumes:
      - 'data:/go'
      - '../app/.:/go'
    ports:
      - "8080:8080"
    command: 'go run main.go'
volumes:
  data:
    driver: 'local'

main.goについては、https://qiita.com/macoshita/items/827ae5ac245b94ed4b4c を参考に、8080ポートで待ち受けるやつを適用に書いて動作確認が完了。

docker上の/goに、appディレクトリをマウントして上げれば希望のことが実現できました。

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

2018/12/23 気になった記事まとめ

気づけば毎日ブログ書きを続けて14日。なんとか継続できてますが、油断すると下記忘れそうな状態です_(:3」∠)_

一ヶ月間継続できれば、習慣化できる...のか?

静かに生き始めると人生まさに終わった感あり(´・ω・`)こわい

type.jp

今年8月くらいの昔の記事ですが、なぜかはてブで上がってますね。

企業が最新のテクノロジーをビジネスに活用しようとする時の重要な課題は、社内にどれだけ技術を理解して動かせる人がいるかということ。だからこそ、どんどん自分で動いていくことが有効ですね。

昨日書いたおちんぎんを上げる技術にも、それとなりに書いた話なのですが『問題に挑戦する機会』を得て、『会社の問題を解決できる力』を身につける。

会社の問題にもいろいろありますが、この記事の例だと最新のテクノロジをビジネスに活用することが挙げられています。 それができる人であれば、目立つし、それに関わる仕事が多く舞い込んでくるようになります。自分の選んだやりたい仕事をやり続けられる日々は最高だと思います(`・ω・´)

耐障害性の設計は忘れがちですが

tech.mercari.com

これまでいろいろと開発してきたんですが、割と『障害が起きたときにどう動かすべきなのか?』を意識されているシステム、特にtoCでお金が絡まないサービスにおいてはおざなりになります(´・ω・`)

特にMicroserviceを適用する場合、そのAPI自身も外部のAPIに通信して処理を行うことになります。ここで同期処理を行っていた場合で、外部のAPIで何らかの障害が発生してレスポンスを返せない状況下にあるとそれに引き摺られて一緒に死にます。

非同期通信を使用することで、サービス間の疎結合を保ち自律性を維持することができます。

(私個人が考えた)一言で表すと、「他のサービス死んでても、自サービスは正常に処理を完了できる」です。例えば外部処理をキューに詰める処理だけして後で問い合わせるなり、この記事のようにPub/Subを使うなりの方法を用いて、外部APIの呼び出しもとAPIもまとめて死ぬことを防ぐことが重要です。

前職でモバイルゲームの開発をやっていました。イベントの開始時大量のアクセスが来てUnicornが詰まることがあり、対策を考えていました。そこでExponential Backoffとリトライを使おうとしたことがあります。

まあ結局はAPPサーバーを増台してUnicornのワーカー数を増やすことで決着したので結局利用しなかったのですが、こういうやり方を知っておくだけでも有益だと思います。

jQueryだけでSPAやってました_(꒪ཀ꒪」∠)_

https://www.m3tech.blog/entry/js_3_point

シングルページアプリケーションのHTMLゲーム。やってた時期は今からだと5-6年くらい前ですかね...。

当時はまだReact.jsがOSSになっておらず、jQueryなり軽量jQuery的なものが幅を効かせていた時代でした。 データを扱う層、画面を描画する層を分離することがとても大事。この記事にも書かれていますね。

その時担当していたHTMLゲームでは、なにか操作して画面の表示を切り替える際、まずAPIリクエストでそのページでアクセスされる可能性の高い情報を一括で引いてました。例えば装備一覧画面なら、所持しているすべてのデータをJSON形式で受け取ります。

処理の流れを雑に列挙すると、

  1. 今の条件で表示するデータを1ページ分取得し、内部のHashに保存しておく
  2. 1以外のデータの取得をリクエストしておく。
  3. 2の処理を行っている間、1で取得したデータとテンプレートからDOMオブジェクトを生成し、ページ中のコンポーネントを丸々入れ替える
  4. 2でリクエストしたレスポンスを内部のHashに詰める

で、ここで重要なのは、データの取得と描画処理を分離することです。絶対にxHttpRequest.onreadystatechange でレスポンス受け取った中で描画しないことを心がけてます。

  1. 予め、データ用のModelオブジェクトに受け取ったデータを詰めておく
  2. Modelオブジェクトのデータから、コンポーネントに表示するためのDOMオブジェクトを、テンプレートとデータから生成(当時はhttps://ejs.co/使ってました)
  3. ページ中のComponent要素の直下を、2で生成したDOMオブジェクトで差し替え

データを元に描画するDOMを構築しそれで置き換えるだけ。楽。(`・ω・´) 一番メリット大きかったのは開発が非常に楽だった点ですね。画面を表示したままデータを取得する処理だけ実行してサーバー側の実装変更に追従したり、逆にフロント側の条件分岐を確認するためにデータ書き換えて、renderメソッドだけ実行する。ちょっとした変更をすぐ試せるので効率が良かった記憶があります。

今になって思えば(程度の差こそあれ)Reactに似てるんですよね(`・ω・´) 実はReact等無くても実現できるので、ものすごく小規模なら自前で書いたほうが楽かもしれませんよ。

おちんぎんを増やす技術

12月、年末ですね。今年もあと数日で終わりです。個人的にも2018年はしばらくITエンジニアから離れてたり転職したりで慌ただしい一年でしたヾ(:3ノシヾ)ノシ

12月といえば、賞与の査定が出たり、目標設定フィードバックで昇給などがあった人もいるかもしれません。賞与もらってから転職しようとしている方も、もしかしたらいるかも知れませんね(`・ω・´)



さて、今日は給与と仕事の話でもしようかと思います。おちんぎん、増やしたいですよね?増えなくてもいいという人は居ないと思います。

今日はおちんぎんの増やし方をポエムします(`・ω・´) あくまで社会人10年程度の中堅くらいのITエンジニアのほざきですので世間とは乖離している点があるかもしれません。ということを前置きとして。

「もらってる給与が少ないから頑張らない」

Twitterで情報収集すること多いのですが、たまーに上記のニュアンスに近いことがRTで流れてくることがあります。 まあ気持ちも分かります。給与少ないのに仕事が増えてもやる気なんて出ない。人間、報酬に見合わない働きをするのは嫌ですからねー(´・ω・`)

毎月の給与という短期的な面の話をすると、給与分の働きしかしないこと自体悪いことではないです。使用者との労働契約に基づき、労務を提供してその分の賃金をもらう。労働者の権利ですよね。



『短期的な面』と書きました。私個人的な考えとして、「もらってる給与が少ないから頑張らない」は勿体ないことをしているなー、と感じます。もらっている給与以上の仕事をするのは、毎月給与をもらうという面では損です。

しかし、給与以上の仕事をし続けることで大きな利益が手に入ります。それは『問題解決の経験値』です。

給与はどうやって決定されるのか

さて、ここで少し頭を切り替えましょう。一つ考えてみてください。給与はどうやって決定されているでしょうか?

すごく単純でみんなが知っている当たり前の話なのでサクッと進めますが、企業はその人が持っている能力にお金を払います。

では、その能力とは何でしょうか?もちろん業種や企業の大きさにより答えが変わる話なので、明確にこれだ!という答えはありません。

ただ、共通するのは『会社の問題を解決できる力』です。雑に言われる課題解決能力ですね。例えば、新規顧客を開拓するのがめちゃくちゃ旨い営業の方であれば既存顧客の売上が低迷している企業が欲しがりますし、例えば、我々ITエンジニアであればシステムの高速化・低コストを考慮した設計ができる人や、障害の調査復旧が早い人。といった会社が困っている問題を解決できる能力です。



もちろん、こんなスーパーな話だけではなく、検算が正確で絶対に数字間違わない、という人から見れば小さな能力でも企業が欲しがっている能力と一致していれば、それは課題解決能力として扱われます。

『問題解決の経験値』を得るためにやるべきこと

「もらってる給与が少ないから頑張らない」を貫き続けるとどうなるんでしょうか?

1番大きな問題は『問題に挑戦する機会が与えられないこと』です。振られた仕事だけを適度にゆったりこなしていく。会社としては契約どおりの仕事やってもらってるし、やりたがっているわけでもないのであれば、その手の仕事を与えることはありません。会社の問題を解決する仕事なので、解決に導ける人に渡すものですからねー。

(´・ω・`)まあ黒いところだと無理やり与えるなんてことありますけど(黒い企業出身者で、仕事出来ない人あんまり見ないのと関連あるかも)



企業は、会社の問題を解決できる能力にお金を払います。その経験を積めない以上、いくら長く勤めていても給与が大きく上がることはありません。(年数千円の昇給くらいはあるかもしれませんが)

冒頭で勿体無いと感じているのはこれが理由です。自分で自分の給与が上がらない選択肢を取っている。「もらっている給与が少ないから頑張らない」は給与を上げるために必要な経験を積み難いため、結果いつまで経っても給与が増えていきません。



こう考えてみると「もらってる給与が少ないから頑張らない」は、実は「頑張らなかったからもらってる給与がすくない」で因果関係が逆転しているのかもしれません。

結局どうすればいいのさ?

常にもらっている給料分のより多くの仕事をやることを意識しましょう(`・ω・´) あ、もちろん同僚の仕事が終わらないから手伝う系の仕事はなるべく避けることをオススメします。得るものが小さすぎるので。

いえね、恩はうれるんですけどおちんぎんアップに繋がるかは運絡みです。(昔は手伝うタスクも喜んでやってたんですがあんまり感謝されないし、そのうち当たり前になるし、手伝った相手の手柄にされたりして自分のメリット薄かったので)

先輩や上司の”自分があまり経験したことないけど手伝える”ものを狙いましょう。給与は据え置きかもしれませんが、自分のやれることが増えていく。そうなると『問題に挑戦する機会』が与えられます。

そこで得た『問題解決の経験値』を武器に給与交渉するなり、転職の材料にするなり。その能力を欲しがる企業に対して売りつけられる自分の能力として使えるようにするのが大事(`・ω・´)

終わりに

実は私も最初給与少なくてですね、18で東京に出て来た時は手取り10万前半くらいで大変苦労しました(´・ω・`)

最初の会社に7年居て最初の5年位、もらっている給与分の仕事しかやれてなかったんですよね。結果25でも手取りは18万くらいで、残業代で生活する日々でした。6年めくらいから意識してやれることを増やしていくために、開発のテスト環境を整備したりセキュリティインシデントの社内共有等やってみたりしたんですよね。

そんな感じでやれることを増やし、率先してやったことないことに飛び込むことを続けた結果、今があるわけです。 こんなポエムを書いておいて、めちゃくちゃ高給なわけではないんですが、東京来た時と比較して今は3倍以上のおちんぎんをもらえています。大変ありがたいことです。

普段「もらってる給与が少ないから頑張らない」と思いがちな方。これ効きますよ!!高いおちんぎんをGETするためにぜひチャレンジしていただければと思います(`・ω・´)ゞ



あと、こんな話をしていると「年功序列で上がっていくから程々に働く」という論も出て来るんですよねー。こちらもうまく回っているうちは良いのですが、それなりに問題がある選択です。今日は長くなったのでまた次回にその話はしたいと思います。

2018/12/21 気になった記事まとめ

2018年も年の瀬。今年は一時期エンジニアやってなかったり、転職したり、転職したてですぐ炎上してたりと、仕事面では刺激的な一年でした。来年はもっといい一年になるといいなぁ、と思う次第。

面白いの評価は難しい

qiita.com

記事の本文見て真っ先に考えたのは、「面白いをどう判断するか?」の部分ですねー。 ダジャレに絞って言えば、似たような音韻を重ねるという特徴があるのでいわゆる分かち書きでやってるのかな?と思いましたが、それだとうまく解析できないケースがあるんですね。

しかし、以前に比べて機械学習もかなり普及してきましたねー。ちょっとした学習モデルを作るくらいなら特に難しい数式を使わずにできるようになってきてますね。近々画像の分類周りで触る可能性があるので試してみなくちゃなー(´・ω・`)

複数環境を docker-composeしているときよく困る

acro-engineer.hatenablog.com

可視化についての学び。こうしてすぐ試せる環境を作れるのもDockerの強さですね。DockerをECSでプロダクション運用する際のモニタリングに悩んでいたんですがこの辺参考にできそう。実際に自分で環境組んでみるのが一番効率よく理解できるので、年末年始でちょっと触ってみようかと思います。

非常に理解しやすい強化学習の実装面の話

speakerdeck.com

強化学習の資料をいろいろ目にする機会はあったんですが、ダントツに理解しやすい資料ですね。あんまり知識ない人でもこれなら雰囲気掴めるんじゃないんでしょうか?しかし、学習のやり方もいろいろありますねー

USキーボードを使い始めて半年がたちました

以前から度々チャレンジして来てしっくりこなかったUSキーボード。7月の転職を機にUSキーボードに切り替えて半年使った結果、今では逆にUS以外だと操作がおぼつかなくなってます_(:3」∠_

今回使い始めたのも割と偶然で、それまで仕事で使っていたキーボードが寿命を迎え、新しく買い直す必要に迫られたんですよね。使いやすくて良いキーボードだったんですが、充電式電池の流通が減ってきたのと、基盤がヘタって電源切れたり入ったりを繰り返すようになったので廃棄処分(´・ω・`)

せっかく買うなら自宅と同じキーボードにしたくなるわけですよ。家と仕事の環境はなるべく合わせるの大事ですし(`・ω・´)



今の会社に転職した翌々日くらいに時間に余裕ができたので秋葉原まで足を伸ばし、いくつかの店舗を覗いた結果、まあ普通にヨドバシが(ポイント加味したら)最安値なので、サクッと購入。 このキーボード、値段が比較的お安いんですがしっかりしたキーボードで、本体が重めでどっしりして安定して入力できるため、大変好みです。

購入した翌日、梱包そのままにして会社に持ち込み、出社後即開封を決めます。

封を開けたら鎮座するUSキーボード( ゚д゚)

見事に買い間違えてました。新たな職場に投入される慣れないUS。一瞬返品しようと思ったんですが、ぶっちゃけ自分の過失で買い間違えた結果なので、申し訳なくそのまま使うことになったんですよね( ̄・ω・ ̄)

最初はキー配置が完全に頭に入ってなかったので、キートップを見ながら入力する始末だったわけなのですが、それを繰り返していった結果USの方が入力早くなりました、やはりITエンジニア稼業、記号入力がしやすいUSが大正義だったわけですね。(`・ω・´)

なれていないツールでも使い続ければ順応していくんだなぁ、と改めて感じた学び。あとはカーソルキー依存体質さえ脱せれば、気の迷いで買ったっきり使ってない、この変態キーボードも使えるようになるのではないか?

たぶん(´・ω・`)

2018/12/20 気になった記事まとめ

日課の投稿です。欠かさず習慣化大事( ・`ω・´)

うちの四歳児に期待して買ったことがある(´・ω・`)

goodegg.jp

うちも子どもにこれさせようと思って、ラズパイ3が鎮座しております_(:3」∠)_ 当時四歳児にはまだ無理でした。そりゃそうだ。小学校入った後くらいに再試行しようかと思ってます。

コンピュータの教育の取っ掛かりとしてとても良く説明された投稿ですね。( ・`ω・´)しかし、最近の子供達は何かと恵まれてますね。人類の進化が急速に発展していっている気がします。彼らが大人になったころにはどんな世の中になってるんでしょうかね?楽しみです。

プロダクトを運用している以上避けられないSREとの付き合い方

speakerdeck.com

基本的な考え方や、何をするべきか?についてまとまっている記事。ここにはあんまり触れられはいませんでしたが、システム運用の自動化や運用フローの整備といった業務改善を積極的に行うことも含まれます。

そもそも問題が起きにくい作りにして信頼性を高める。意図的に(サービス影響ない程度の)障害を引き起こして常日頃から復旧に慣れておく。等、やるべきことは数多く(´・ω・`)

そういった体制や環境を常日頃整備して行けば、運用に手を取られずビジネス要件の開発に集中できるため、結果として安定した高速でリリースし続けられるプロダクト開発が実現できるようになります。

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

家具のレンタルサービスって正直どうですか?

subsclife.com

来年引っ越す予定なんですが、家具費用に回せるお金がなかった時に、一時的に使おうと思います。 購入だとどうしても気に入ったものが見つかるまで手が出せないんですが、これならテキトーに借りて生活しつつ、気にいるものが見つかったら即切り替え、みたいな使い方ができるかなぁと( ・`ω・´)

AuroraからOutfile S3で出力したpartファイルを1つに結合したかった話

# 本記事の環境
ruby: 2.5.0
Rails: 5.1

Amazon Auroraには、実行したクエリの結果をそのままS3に出力する機能があります。

docs.aws.amazon.com

Aurora v1.13くらいから使えるようになった機能で、弊チームではデータ分析基盤に流し込むために使っています。Selectした結果をTSVにしてS3として保存してくれるので、出力さえしてしまえば世の中の大方の分析環境で使えるのがいいですね( ・`ω・´)

実際に使うときは、こんな感じ(Aurora側にS3に吐き出すための権限が付与されているかつ、SQLの実行ユーザーにSELECT INTO S3のGrant設定をする必要があり。詳しくは↑のリンク)

SELECT *
FROM table
WHERE created_at > DATE_ADD(CURRENT_DATE(), interval 1 day)
INTO OUTFILE S3 's3-us-west-2://bucket/dir/key' 
OVERWRITE ON

楽ちん。これだけでS3にファイルをはきだせるので、ちょっとしたデータをサクッと集めたいときに便利そうですね。

この機能を使って全テーブルの全データを一日1回S3に投げつけようとします。Railsであれば対象となるModelに対し、columnsを駆使してSQLを組み立てればいけます。

model = User
query = <<~SQL
SELECT #{model.columns.map(&:name).join(',') }
FROM #{model.table_name}
INTO OUTFILE S3 's3-us-west-2://bucket/dir/#{model.table_name}' 
OVERWRITE ON
SQL

model.connection.execute(query)

確かこんな感じで行けるはず( ・`ω・´)

適切な権限があり、Bucketが作成済みであればこれでS3にファイルが出力されます。17000行、6.2MBくらいのデータで出力に700msほど時間がかかりましたが、そこまで遅くはない印象。

今回の困ったこと

さて、今回の本題はここから。 この方法で、例えばbucket/dir/usersとして出力した場合、実際には以下の名前で出力されます。

bucket/dir/users.part_00000

名前の後ろに勝手にpart_xxxxxがついてしまいます。S3への出力オプションを見ても制御する設定はなかったので、おそらく常にこの形式の名前で吐き出される模様(´・ω・`)

名前を見て想像できる通り、データセットのサイズが大きすぎる場合にファイル分割して出力されます。Amazonのドキュメントに記載されている内容をみると、

Amazon S3 バケットに書き込まれるファイルの数は、SELECT INTO OUTFILE S3 ステートメントで選択したデータの量と Aurora MySQL のファイルサイズのしきい値によって異なります。デフォルトのファイルサイズのしきい値は 6 GB です。ステートメントで選択したデータがファイルサイズのしきい値より少ない場合は、1 つのファイルが作成されます。それ以外の場合は、複数のファイルが作成されます。

とあり、6GBを閾値としてファイル分割するみたいです。

複数ファイルに分かれてしまう。特に事情がなければそのまま使ってもいいんですが、複数ファイルに分割すると分析基盤に取り込む際に考慮しなくてはいけないので、可能な限り1ファイルに固めたいもの。

そこで、マルチパートアップロード

S3には、大きすぎるファイルを転送する際、ファイルを複数に分割して送信しS3側で1つにまとめる『マルチパートアップロード』という機能がサポートされています。今回の1ファイルに固める際はこれを使います。

docs.aws.amazon.com

マルチパートは、基本的に以下の手順でファイル送信 + 結合を実現しているようです。

  1. AWS::S3::Client.create_multipart_uploadで、バケット名と出力ファイル名を渡す。実行結果として1つのMultipartを指すOrderIdが取得できる
  2. AWS::S3::Client.upload_partでファイルを送信する。保存できたらetagという固有のIDを返却する。
  3. ファイルをすべて転送した後、2で取得したetagとpart_numberを送信して、ファイルを結合する

これが基本の流れ。Auroraから吐き出したデータをこれに食わせて1ファイルに結合できればすべて解決できそうな気がします。Railsから試すには、gem aws-sdk-3をGemfileに追加します。

まず、今回はAuroraからS3への出力を行っているため、ファイルが存在しています。2の工程はスキップできそうです。後、3の工程で必要なetagですが、以下のコードで取得できます。

def bucket
  @bucket ||= Aws::S3::Resource.new(
    client: ::Aws::S3::Client.new(region: "us-west-2")
  ).bucket("bucket")
end

# キー名をprefixに指定すると、.part_xxxxxの要素が引っかかる。それのetagとpart_number
etag_and_partnum = bucket.objects(prefix: "dir/users").to_a.map{|obj| {etag: obj.etag, part_number: obj.key.match("\d+$").to_s.to_i})

この取得したetag指定してmultipartを実行します。

shared_options = { bucket: bucket.name, key: "dir/users" }
create_result = bucket.client.create_multipart_upload(shared_options)
# complete_multipart_upload: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#complete_multipart_upload-instance_method
bucket.client.complete_multipart_upload(
    shared_options.merge(
        multipart_upload: { parts: etag_and_partnum },
        upload_id: create_result.upload_id
    )
)

これでいけるかと思いきや、無残に失敗します(´・ω・`)マルチパートのetagじゃないぞ、というお怒りだったはず。

やはり正しく1,2,3の工程を実施する必要はありそうです。upload_partでファイルを送信し直すなんで、なんのためにOutfile S3をやったのかという気持ちになりますね(´・ω・`)

そんな折、こんなQiitaをみつけました。

qiita.com

つまり、upload_part_copyメソッドを用いて、バケットに存在するOutfile S3で出力したファイルをcopy_sourceに指定することで、改めてファイルを送信せずに目的にことが実現できる、ということになります。

最終的に、以下のコードで実現できました。

  def join_multipart_object(s3_path_key)
    # upload_part_copyのcopy_source指定のため、出力キーの一覧を取得しておく
    upload_object_keys = bucket.objects(prefix: s3_path_key).to_a.map(&:key)
    return if upload_object_keys.blank?
    
    shared_options = { bucket: bucket_name, key: s3_path_key }
    crete_result = bucket.client.create_multipart_upload(shared_options)
    begin
      # 一度上げたものを結合することはできないので、コピーを元に生成する
      etag_and_partnum = upload_object_keys.map.with_index(1) do |updated_key, idx|
        upload_result = bucket.client.upload_part_copy(
          shared_options.merge(
            copy_source: "#{bucket.name}/#{updated_key}",
            part_number: idx,
            upload_id: crete_result.upload_id
          )
        )
        {etag: upload_result.copy_part_result.etag, part_number: idx}
      end

      # 分割したファイルを結合する
      bucket.client.complete_multipart_upload(
        shared_options.merge(
          multipart_upload: { parts: etag_and_partnum },
          upload_id: crete_result.upload_id
        )
      )
    rescue
      # 処理失敗の場合は、abortを叩いてマルチパートを終了させる
      bucket.client.abort_multipart_upload(
        shared_options.merge(upload_id: part_struct.upload_id)
      )
      raise $!
    end

    # マルチパートを合成したら、元のファイルを削除して完了
    bucket.delete_objects(
       delete: {
         objects: upload_object_keys.map { |key| {key: key} }
       }
    )
end

def bucket
  @bucket ||= Aws::S3::Resource.new(
    client: ::Aws::S3::Client.new(region: "us-west-2")
  ).bucket("bucket")
end