Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

HTTP サーバと Worker プロセスを 1 つの Dyno で立ち上げて節約

More than 5 years have passed since last update.

Heroku では通常 Web サーバと Worker プロセスでそれぞれ異なる Dyno を起動する必要があり、それぞれで課金されます。
個人用途レベルのツールであれば Dyno を 2 つも立ち上げるのはもったいないと感じることでしょう。

Ruby では EventMachine を使うことによって 1 Dyno で済ませるやり方が知られています。

HerokuのSinatraにバックグラウンドワーカーを詰め込んで節約

Golang においては goroutine をつかうことによって、より自然に実現することができます。
(まぁ Golang においては当たり前の話なので、Golang を使ったことない人に、Golang だとこういうことができる!という感じに読んでもらえればと思います。)

コード例

以下は Worker では 10 秒おきに数値をカウントアップし、HTTP サーバではその数値を出力するだけの例です。

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "sync/atomic"
    "time"
)

var count uint64

// 10 秒おきに数値をカウントアップし続けるだけの関数
func countUpdater() {
    for {
        atomic.AddUint64(&count, 1)
        time.Sleep(10 * time.Second)
    }
}

func main() {
    port := os.Getenv("PORT")

    if port == "" {
        log.Fatal("$PORT must be set")
    }

    // カウントアップ用の goroutine (Worker) を起動
    go countUpdater()

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Count: %d\n", count)
    })
    // HTTP サーバを起動
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}

実行例

数値が 10 秒置きに増えていくことが確認できると思います。

https://heroku-golang-http-with-worker.herokuapp.com/

活用例

睡眠時間を避けて Keep-Alive する

Dyno の種類にもよりますが、Heroku では一定時間リクエストがないと Dyno がスリープ状態に入ります。
ですが、Worker (goroutine) 内で自信を一定時間おきに GET するようにすればスリープしないようになります。
(ここではそれを Keep-Alive と表現しています)

ですが、Heroku は 2015 年から料金プランが改定され、無料プランの場合は 1 日に Dyno が 6 時間はスリープ状態となるようになりました。
なので、例えば睡眠時間を避けた時間帯のみ Keep-Alive するようにすれば、自分がおきている時間は快適にアプリを利用できるし、Worker も動き続けることになります。
もちろん、アプリの用途によっては寝ている時間に動いていて欲しいものもあるとは思いますが、その辺はうまく調整しましょう。

重い処理を Worker で行う

まぁ Worker の使い方としては普通ですね。
クローラを Worker として動かし、そのフロントエンドとして HTTP サーバを動かす、といった感じがよくあるんじゃないでしょうか。

注意点

Dyno は再起動されることを念頭におく

デプロイ時もそうですし、スリープ後の起動時もそうです。
(それとらとは関係なしに一定時間の再起動も行われていたかも)

なので、インメモリの変数は再起動時にクリアされてしまいます。
上記のコード例がまさにそうですね。
永続化は Heroku Postgres や Heroku Redis などの永続化層を用意しましょう。

柔軟なスケールアップは難しい

例えば Dyno を 2 つにした時点で HTTP サーバも Worker も 2 つになります。
HTTP だけ 2 つにして Worker は 1 つでいい、といったことを実現するのであれば、普通にプロセスごとに Dyno を割り当てていくのが良いでしょう。

あくまでも個人的なレベルのツールを節約して使いたい時用ということで。

yuya_takeyama
Ruby / PHP / JavaScript / Golang
http://yuyat.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away