Cleaning up heap allocations in Go

This is the 18th article of the Eureka Advent Calendar 2020. Yesterday we had a post from Jun Ernesto Okumura on diversity in Spotify’s recommender systems.

Introduction

Thanks to efficient in-built memory management in the Go runtime, we’re generally able to prioritise correctness and maintainability in our programs without much consideration for the details of how allocations are occurring. From time to time though, we may discover performance bottlenecks in our code, and want to look a little deeper.

Anyone who’s run a benchmark with the -benchmem flag will have seen the allocs/op stat in output like the below. …


ジェネリクスの使いどころ(イメージ)

はじめに

Goの世界で最近話題になっているのが2020年6月12日に発表されたジェネリクスのドラフトです。仕様がやや固まってきただけでなく、go2goで実際にコンパイルと実行することが可能になりました。
本記事では連結リストを実装しながらドラフトの仕様を見ていきます。全てのコードはサンプルレポジトリにあります。

2020/7/15 UPDATE: 型パラメータのシンタックスで()の代わりに[]が使用される可能性があります。近いうちgo2goが更新され、両方使えるようになります。詳しくはGoogle Groupsまで。

2020/8/21 UPDATE: []の使用が確定となりました。また、typeがなくなる代わりにConstraintが必須となります。Constraintがない場合はanyという新しい予 …


はじめに

この記事は「eureka Advent Calendar 2019」22日目の記事です。

こんにちは!APIチームの@Jimeuxです。今夏、エウレカはPairsエンゲージ(以下エンゲージ)という結婚コンシェルジュサービスをリリースしました。本記事ではPairsのサーバーサイドアーキテクチャから学んだことを今回の新規開発にどう活かしたかを紹介していきます。

Pairsのサーバーサイドについて

現在のPairsのサーバーサイドは、PHPからGo言語でフルスクラッチに開発され、2015年にリリースされました。それ以来、複数のマイクロサービスがmonorepoで開発され、今ではGo言語のソースコードの行数が約40万行まで増えてきています。(※空行・コメントを除く)

エンゲージのサーバーサイド開発に着手する前、チームではPairsのア …


パイプ(土管)をGo言語でも楽しめる

はじめに

前回はGo言語のmime/multipartパッケージによるファイルのアップロードを見ましたが、パフォーマンスの特徴にはあまり触れませんでした。
大規模なETLジョブや、制限の厳しいサーバーレスの環境などでは、ファイルを扱うプログラムのリソースを慎重に考える必要があります。本記事ではメモリ使用量を大幅に減らすio.Pipeの使い方を見ていきます。
全てのコードはサンプルレポジトリにあります。

同期処理にある問題

前回のコードをもう一度見てパフォーマンスを考えてみましょう。

mime/multipartパッケージによる同期的なファイルアップロード

データの流れを考えるとステップが大きく2つあります。

  1. 12行目でファイルの中身をエンコードしながらio.Copyでバッファ(bytes.Buffer)に書き込む。
  2. http.Postの …


Goによるファイルアップロード(ランタイムの中の様子)

はじめに

こんにちは。エウレカ APIチームのジェームスです。
最近Go言語でファイルをアップロードしたりするAPIクライントを作っていて、色々と学ぶことがありました。今回は基本に戻りマルチパートメッセージの仕様を考えながら、標準ライブラリのmime/multipartパッケージでファイルをアップロードする方法を紹介していきます。
全てのコードはサンプルレポジトリにあります。

マルチパートメッセージのおさらい

マルチパートメッセージはHTTPのデータ転送に利用されることが多いですが、その定義はもともとSMTPメールのために設計されたMIME規格にあります。そのため、「メッセージ」などの通常HTTPリクエストに対して使わない単語が出たりします。

HTTPの場合は、ファイルのアップロードによく使いますが、「マルチパート」という名の通り複数のパート(ファイル、テキストなど)を1つのメッセージの中で転送するための形式です。パートはそれぞれ以下の要素からなります。

  • バウンダリ(パートの区切り文字)
  • ヘッダエリア(通常Content-DispositionContent-Type
  • 空白行
  • ボディエリア(ファイルの中身など)

バウンダリはボディエリアの中で出現してはいけないので、基本はランダムに生成されます。Content-Dispositionヘッダはファイル名やフィールド名など、パートのフィールドについての情報を含みます。Content-Typeヘッダの種類についてはウェブ開発者向けの重要な MIME タイプを参照してください。

マルチパートのHTTPリクエストを送信する時に、リクエストレベルのContent-Typeヘッダにmultipart/form-dataというサブタイプを指定しバウンダリも含めます。ボディにはパートを1つ以上含め、末尾にバウンダリがもう一度挿入されます。

以下はHTTPリクエスト例です。バウンダリがそれぞれContent-Typeヘッダ、パートの冒頭、メッセージの末尾にあることが確認できます。

POST /upload HTTP/1.1
Host: localhost:3000
Accept-Encoding: gzip
Content-Length: 254
Content-Type: multipart/form-data; boundary=c7245ee369df31f524686275eb89381b30581b1ca5557de2453f9f8cf66c
User-Agent: Go-http-client/1.1
--c7245ee369df31f524686275eb89381b30581b1ca5557de2453f9f8cf66c
Content-Disposition: form-data; name="file"; filename="hello.txt"
Content-Type: application/octet-stream
hello--c7245ee369df31f524686275eb89381b30581b1ca5557de2453f9f8cf66c--

Go言語でマルチパートメッセージを生成する

Go言語のmime/multipartパッケージはマルチパートメッセージを読み書きするための便利な型や関数を提供しています。メッセージを作るにはmultipart.Writerという型を使います。まずはその定義を見てみましょう。

NewWriterは引数にメッセージ全体のデータを保持するio.Writerw)を取ります。呼び出されたらバウンダリ(boundary)に使うbase-16文字列がランダムに生成されます。パートを追加するたびにwにそのデータが書き込まれ、最後に追加したパートのポインター(lastpart)が更新されます。

パートを追加するには、ヘッダを引数にCreatePartを呼び出し、返却されるio.Writerにパートのボディを書き込みます。最後にCloseの呼び出しでメッセージの最終バウンダリが挿入されます。その流れをコードで見てみましょう。

パート毎にヘッダを設定するのが冗長になるため、それを省いてくれるCreateFromFile(ファイル用)とCreateFromField(フォームフィールド用)メソッドが準備されています。他にもリクエストのContent-Typeヘッダに使える値を出すFormDataContentTypeもあります。以下のようにバウンダリを含みます。

multipart/form-data; boundary=0d9f057fe9d23d97213ee9b391c3acff605dbde7478fdb97e079f4649a0e

ファイルをアップロードする

これでファイルをアップロードするための知識が全て揃っています。コードコメントと一緒にサンプルを一通り見てみましょう。

ファイルとリクエストの操作以外は前とほぼ同じ流れです。*bytes.Bufferを使ったのでContent-Lengthヘッダはhttp.Requestが生成される時に自動で設定されます。アップロードが確認できるように簡単なサーバーを作っておきましょう。

サーバーを起動してからアップロードのリクエストをもう一度送信したら、サーバー側でhttputil.DumpRequestの出力が上記のHTTPリクエスト例とほぼ同じになります。サンプルレポジトリで試すことができます。

まとめ

Go言語の標準ライブラリのパッケージは使い勝手が良く、読みやすいコードに繋がることが多いです。mime/multipartでも完結で分かりやすいコードでファイルアップロードが実装できました。

ところが、注意事項が1つあります。今回のサンプルではリクエストが送信される時にボディがファイルの中身全体を保持していました。ファイルが小さい場合は問題ないかもしれませんが、大きい場合はSREチームが決して喜ばないでしょう。「Go言語のio.Pipeでファイルを効率よくアップロードする方法」ではio.Pipeを使った、より効率のいいやり方を見ていきます。

参照

Cover photo by Markus Spiske temporausch.com from Pexels


弊社アプリ情報サービスApplivへネイティブ広告を出すため、自社システムApplivAdを運営しています。今まではモノリシックなアーキテクチャで開発され、コード量が増えるにつれアプリケーションが複雑化してきました。

複雑化への対策としてマイクロサービス アーキテクチャへの移行を検討していますが、パフォーマンスが重視されるシステムでは、サービス間通信が大きな懸念点となります。分散コンピューティングの落とし穴はもちろん、サービス間の結合性からもさまざまな問題が生じます。

サービス間通信にRESTのAPIやgRPCなどの同期的なプロトコルが使われることが多いです。しかし、ApplivAdのような計測イベントの多いシステムでは、非同期的なプロトコルをも活かすことで、サービス間の結合性を低めるばかりかリアクティブマニフェストの実現により近づくことも可能になります。

SNS+SQSによる非同期メッセージ

AMQPプロトコル (RabbitMQActiveMQ)、アクターモデル (Akka)、分散ストリーミングプラットフォーム (Kafka) など、非同期メッセージに使えるミドルウェアが数多くあります。弊社ではインフラコストを抑えシステムの柔軟性を高めるため、可能な限りマネージドサービスを利用することにしています。

AWS上のソリューションを少し調べると、SNSのPubSub機能とSQSのメッセージキューを組み合わせている動画をAWSのYouTubeチャンネルで見つけました。複数コンシューマー向けで疎結合性、耐障害性、弾力性といったメリットが魅力的でした。

SNS+SQSによる非同期メッセージ

動画ではアーキテクチャのメリット・デメリットが取り上げられていましたが、本記事では構築と実装に集中したいので以下の項目をカバーします。

  • SNSトピックの作成
  • SQSキューの作成とSNSトピックへのサブスクリプション
  • SNSトピックへのメッセージの発行
  • SQSキューからのSNSメッセージの受信と削除

トピック、キュー、サブスクリプションのセットアップ

SNSの画面新しいトピックの作成ボタンをクリックし、トピック名を入力してからトピックの作成ボタンでトピックを新しく作成しましょう。(トピックの作成を参照)

James Kirk

Server-side engineer and gopher at Eureka.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store