Walk, Don't Run

若手エンジニアが日々学んだことをさらけ出すブログです

springで@Asyncによる非同期処理でスレッドプールする場合の設定周り

以下の辺りが設定可能なので、それぞれメモを残す。

  • スレッドプールの方式(Executor)
  • スレッドプールのパラメーター(ThreadPoolTaskExecutor の設定)
  • Reject時(スレッド数が溢れた場合)のポリシー

スレッドプール方式(Executor)

@Asyncのついたメソッドを呼び出す場合、org.springframework.core.task.TaskExecutor をbean登録することで任意のExecutorに処理をさせることができる。TaskExecutorはスレッドプールをどのように行うかを定義するもので、java.util.concurrent.Executor を継承したinterface。以下のExecutorが用意されており、デフォルトだとSimpleAsyncTaskExecutorが使われるが、スレッドプールをしたい場合は通常はThreadPoolTaskExecutorを使えば良さそう。

  • SyncTaskExecutor
    • 非同期で実行されない。テストとか用。
  • SimpleAsyncTaskExecutor
    • 呼び出すたびに新しいスレッドを生成する。デフォルトだとこれ。
  • ConcurrentTaskExecutor
    • java.util.concurrent.Executor のadaptorとして実装。ThreadPoolTaskExecutorで要件が満たせない場合に代わりに使えるらしい。
  • ThreadPoolTaskExecutor
    • 基本はこれでOK。Bean形式でjava.util.concurrent.ThreadPoolExecutorの設定が可能。

スレッドプールのパラメーター(ThreadPoolTaskExecutor の設定)

ThreadPoolTaskExecutorはjava.util.concurrent.ThreadPoolExecutorをラップしており、主にcorePoolSize, maxPoolSize, queueCapacity の3つのパラメーターでスレッドプールの挙動を設定する。

それぞれのパラメーターの挙動については、 https://blog.ik.am/entries/443 が非常に理解しやすいので、まずはここを読むのがおすすめ。

デフォルトだとcorePoolSizeが1, maxPoolSizeとqueueCapacityがInteger.MAX_VALUEになっているので、このままだと実質シングルスレッドの処理となる。

スレッド数がmaxPoolSizeを超えるとTaskRejectedExceptionが発生するので、必要なら適切なハンドリングを行う(後述)。

その他に、keepAliveSecondがある。アイドル状態のスレッドがこの時間を過ぎると削除されるらしい(corePoolSizeを越えてプール内にスレッドが存在する場合)。デフォルトは60秒のようだ。

Reject時(スレッド数が溢れた場合)のポリシー

タスクの消化が追いつかない場合どんどんスレッドが膨らみ、やがてmaxPoolSizeを超えると例外が投げられてしまう。個人的には、maxを越えたらその時点で例外を投げるのではなく、スレッドが消化されるまで待機されるような動きになっていてほしい。

・・・とか思っていたら、同じような疑問がstackoverflowにあった。

https://stackoverflow.com/questions/49290054/taskrejectedexception-in-threadpooltaskexecutor

例外を投げるのを防ぐには、一番上の回答を見るに、以下のような方法で対応できそう。

  1. キューサイズを指定せず、無限にキューにタスクを積む作りにする。スレッド数の上限はcorePoolSizeで、タスクが終わる度にキューから新しいタスクが取り出される。
  2. RejectedExecutionHandlerを指定する
  3. 独自のExecutorを実装する(Semaphoreとか利用して)

1が楽そうだが、これだとスレッド数がcorePoolSizeに固定されてしまい、スケーラビリティが損なわれるように見える。3はなんだか面倒そう。ということで2を調べてみる。

RejectedExecutionHandlerは自分で実装しても良いが、あらかじめ用意されているクラスがある。

  • CallerRunsPolicy
  • AbortPolicy (デフォルトだとこれ)
  • DiscardPolicy
  • DiscardOldestPolicy

CallerRunsPolicyは、呼び出し元がそのままタスクを実行するというもの。

参考: https://stackoverflow.com/questions/46064786/rejectedexecutionhandler-callerrunspolicy-vs-abortpolicy

とりあえず自分のユースケースだとこの動きで問題なさそう。

実装例

以下のような設定をした場合の挙動を追いかけてみる。コードの全体は、 https://github.com/suzukimit/spring-demo/tree/master/spring-async-demo へ。

メインタスク(@Scheduledで毎秒呼び出される)

@Component
class MainTask(val subTask: SubTask) {

    @Scheduled(fixedRateString = "1000")
    fun execute() {
        taskCount++
        println("${Date()} : task$taskCount start : ${Thread.currentThread().id} ")
        for (i in 1..5) {
            subTask.execute(taskCount, i)
        }
        println("${Date()} : task$taskCount end   : ${Thread.currentThread().id} ")
    }

    companion object {
        var taskCount = 0
    }
}

サブタスク(@Asyncでメインタスクから5回呼び出される)

@Component
class SubTask {
    @Async
    fun execute(taskCount: Int, subTaskCount: Int) {
        println("${Date()} : sub-task$taskCount-$subTaskCount start : ${Thread.currentThread().id}")
        Thread.sleep(3000)
        println("${Date()} : sub-task$taskCount-$subTaskCount end   : ${Thread.currentThread().id}")
    }
}

TaskExecutorの設定クラス

@Configuration
class TaskExecutorConfig {
    @Bean
    fun taskExecutor(): TaskExecutor {
        return ThreadPoolTaskExecutor().apply {
            corePoolSize = 2
            maxPoolSize = 4
            setQueueCapacity(4)
            setRejectedExecutionHandler(ThreadPoolExecutor.CallerRunsPolicy())
        }
    }
}

※動作確認のために適当な小さめのPoolSizeを指定。実運用ではより大きなサイズにチューニングしていく必要があるかと。

コンソールの出力内容

適当に、1分間くらい流した結果を貼り付ける。

Mon Nov 04 15:59:44 JST 2019 : task1 start : 21 
Mon Nov 04 15:59:44 JST 2019 : task1 end   : 21 
Mon Nov 04 15:59:44 JST 2019 : sub-task1-1 start : 24
Mon Nov 04 15:59:44 JST 2019 : sub-task1-2 start : 25
Mon Nov 04 15:59:45 JST 2019 : task2 start : 21 
Mon Nov 04 15:59:45 JST 2019 : sub-task2-2 start : 31
Mon Nov 04 15:59:45 JST 2019 : sub-task2-4 start : 21
Mon Nov 04 15:59:45 JST 2019 : sub-task2-3 start : 32
Mon Nov 04 15:59:47 JST 2019 : sub-task1-1 end   : 24
Mon Nov 04 15:59:47 JST 2019 : sub-task1-2 end   : 25
Mon Nov 04 15:59:47 JST 2019 : sub-task1-3 start : 24
Mon Nov 04 15:59:47 JST 2019 : sub-task1-4 start : 25
Mon Nov 04 15:59:48 JST 2019 : sub-task2-2 end   : 31
Mon Nov 04 15:59:48 JST 2019 : sub-task2-3 end   : 32
Mon Nov 04 15:59:48 JST 2019 : sub-task2-4 end   : 21
Mon Nov 04 15:59:48 JST 2019 : sub-task2-1 start : 32
Mon Nov 04 15:59:48 JST 2019 : sub-task1-5 start : 31
Mon Nov 04 15:59:48 JST 2019 : task2 end   : 21 
Mon Nov 04 15:59:48 JST 2019 : task3 start : 21 
Mon Nov 04 15:59:48 JST 2019 : sub-task3-4 start : 21
Mon Nov 04 15:59:50 JST 2019 : sub-task1-4 end   : 25
Mon Nov 04 15:59:50 JST 2019 : sub-task1-3 end   : 24
Mon Nov 04 15:59:50 JST 2019 : sub-task2-5 start : 25
Mon Nov 04 15:59:50 JST 2019 : sub-task3-1 start : 24
Mon Nov 04 15:59:51 JST 2019 : sub-task1-5 end   : 31
Mon Nov 04 15:59:51 JST 2019 : sub-task2-1 end   : 32
Mon Nov 04 15:59:51 JST 2019 : sub-task3-4 end   : 21
Mon Nov 04 15:59:51 JST 2019 : sub-task3-3 start : 32
Mon Nov 04 15:59:51 JST 2019 : sub-task3-2 start : 31
Mon Nov 04 15:59:51 JST 2019 : task3 end   : 21 
Mon Nov 04 15:59:51 JST 2019 : task4 start : 21 
Mon Nov 04 15:59:51 JST 2019 : sub-task4-4 start : 21
Mon Nov 04 15:59:53 JST 2019 : sub-task2-5 end   : 25
Mon Nov 04 15:59:53 JST 2019 : sub-task3-1 end   : 24
Mon Nov 04 15:59:53 JST 2019 : sub-task3-5 start : 25
Mon Nov 04 15:59:53 JST 2019 : sub-task4-1 start : 24
Mon Nov 04 15:59:54 JST 2019 : sub-task3-2 end   : 31
Mon Nov 04 15:59:54 JST 2019 : sub-task4-4 end   : 21
Mon Nov 04 15:59:54 JST 2019 : sub-task4-2 start : 31
Mon Nov 04 15:59:54 JST 2019 : sub-task3-3 end   : 32
Mon Nov 04 15:59:54 JST 2019 : task4 end   : 21 
Mon Nov 04 15:59:54 JST 2019 : sub-task4-3 start : 32
Mon Nov 04 15:59:54 JST 2019 : task5 start : 21 
Mon Nov 04 15:59:54 JST 2019 : sub-task5-4 start : 21
Mon Nov 04 15:59:56 JST 2019 : sub-task4-1 end   : 24
Mon Nov 04 15:59:56 JST 2019 : sub-task3-5 end   : 25
Mon Nov 04 15:59:56 JST 2019 : sub-task4-5 start : 24
Mon Nov 04 15:59:56 JST 2019 : sub-task5-1 start : 25
Mon Nov 04 15:59:57 JST 2019 : sub-task4-2 end   : 31
Mon Nov 04 15:59:57 JST 2019 : sub-task5-2 start : 31
Mon Nov 04 15:59:57 JST 2019 : sub-task4-3 end   : 32
Mon Nov 04 15:59:57 JST 2019 : sub-task5-4 end   : 21
Mon Nov 04 15:59:57 JST 2019 : sub-task5-3 start : 32
Mon Nov 04 15:59:57 JST 2019 : task5 end   : 21 
Mon Nov 04 15:59:57 JST 2019 : task6 start : 21 
Mon Nov 04 15:59:57 JST 2019 : sub-task6-4 start : 21
Mon Nov 04 15:59:59 JST 2019 : sub-task5-1 end   : 25
Mon Nov 04 15:59:59 JST 2019 : sub-task4-5 end   : 24
Mon Nov 04 15:59:59 JST 2019 : sub-task5-5 start : 25
Mon Nov 04 15:59:59 JST 2019 : sub-task6-1 start : 24
Mon Nov 04 16:00:00 JST 2019 : sub-task5-2 end   : 31
Mon Nov 04 16:00:00 JST 2019 : sub-task5-3 end   : 32
Mon Nov 04 16:00:00 JST 2019 : sub-task6-2 start : 31
Mon Nov 04 16:00:00 JST 2019 : sub-task6-3 start : 32
Mon Nov 04 16:00:00 JST 2019 : sub-task6-4 end   : 21
Mon Nov 04 16:00:00 JST 2019 : task6 end   : 21 
Mon Nov 04 16:00:00 JST 2019 : task7 start : 21 
Mon Nov 04 16:00:00 JST 2019 : sub-task7-4 start : 21
Mon Nov 04 16:00:02 JST 2019 : sub-task6-1 end   : 24
Mon Nov 04 16:00:02 JST 2019 : sub-task5-5 end   : 25
Mon Nov 04 16:00:02 JST 2019 : sub-task6-5 start : 24
Mon Nov 04 16:00:02 JST 2019 : sub-task7-1 start : 25
Mon Nov 04 16:00:03 JST 2019 : sub-task6-2 end   : 31
Mon Nov 04 16:00:03 JST 2019 : sub-task6-3 end   : 32
Mon Nov 04 16:00:03 JST 2019 : sub-task7-2 start : 31
Mon Nov 04 16:00:03 JST 2019 : sub-task7-4 end   : 21
Mon Nov 04 16:00:03 JST 2019 : sub-task7-3 start : 32
Mon Nov 04 16:00:03 JST 2019 : task7 end   : 21 
Mon Nov 04 16:00:03 JST 2019 : task8 start : 21 
Mon Nov 04 16:00:03 JST 2019 : sub-task8-4 start : 21
Mon Nov 04 16:00:05 JST 2019 : sub-task7-1 end   : 25
Mon Nov 04 16:00:05 JST 2019 : sub-task6-5 end   : 24
Mon Nov 04 16:00:05 JST 2019 : sub-task7-5 start : 25
Mon Nov 04 16:00:05 JST 2019 : sub-task8-1 start : 24
Mon Nov 04 16:00:06 JST 2019 : sub-task7-2 end   : 31
Mon Nov 04 16:00:06 JST 2019 : sub-task8-4 end   : 21
Mon Nov 04 16:00:06 JST 2019 : sub-task7-3 end   : 32
Mon Nov 04 16:00:06 JST 2019 : sub-task8-2 start : 31
Mon Nov 04 16:00:06 JST 2019 : task8 end   : 21 
Mon Nov 04 16:00:06 JST 2019 : sub-task8-3 start : 32
Mon Nov 04 16:00:06 JST 2019 : task9 start : 21 
Mon Nov 04 16:00:06 JST 2019 : sub-task9-4 start : 21
Mon Nov 04 16:00:08 JST 2019 : sub-task8-1 end   : 24
Mon Nov 04 16:00:08 JST 2019 : sub-task7-5 end   : 25
Mon Nov 04 16:00:08 JST 2019 : sub-task9-1 start : 25
Mon Nov 04 16:00:08 JST 2019 : sub-task8-5 start : 24
Mon Nov 04 16:00:09 JST 2019 : sub-task8-3 end   : 32
Mon Nov 04 16:00:09 JST 2019 : sub-task9-4 end   : 21
Mon Nov 04 16:00:09 JST 2019 : sub-task8-2 end   : 31
Mon Nov 04 16:00:09 JST 2019 : sub-task9-2 start : 32
Mon Nov 04 16:00:09 JST 2019 : task9 end   : 21 
Mon Nov 04 16:00:09 JST 2019 : sub-task9-3 start : 31
Mon Nov 04 16:00:09 JST 2019 : task10 start : 21 
Mon Nov 04 16:00:09 JST 2019 : sub-task10-4 start : 21
Mon Nov 04 16:00:11 JST 2019 : sub-task8-5 end   : 24
Mon Nov 04 16:00:11 JST 2019 : sub-task9-1 end   : 25
Mon Nov 04 16:00:11 JST 2019 : sub-task10-1 start : 25
Mon Nov 04 16:00:11 JST 2019 : sub-task9-5 start : 24
Mon Nov 04 16:00:12 JST 2019 : sub-task10-4 end   : 21
Mon Nov 04 16:00:12 JST 2019 : sub-task9-2 end   : 32
Mon Nov 04 16:00:12 JST 2019 : task10 end   : 21 
Mon Nov 04 16:00:12 JST 2019 : task11 start : 21 
Mon Nov 04 16:00:12 JST 2019 : sub-task11-3 start : 21
Mon Nov 04 16:00:12 JST 2019 : sub-task9-3 end   : 31
Mon Nov 04 16:00:12 JST 2019 : sub-task10-3 start : 31
Mon Nov 04 16:00:12 JST 2019 : sub-task10-2 start : 32
Mon Nov 04 16:00:14 JST 2019 : sub-task10-1 end   : 25
Mon Nov 04 16:00:14 JST 2019 : sub-task9-5 end   : 24
Mon Nov 04 16:00:14 JST 2019 : sub-task10-5 start : 25
Mon Nov 04 16:00:14 JST 2019 : sub-task11-1 start : 24
Mon Nov 04 16:00:15 JST 2019 : sub-task11-3 end   : 21
Mon Nov 04 16:00:15 JST 2019 : sub-task10-3 end   : 31
Mon Nov 04 16:00:15 JST 2019 : sub-task10-2 end   : 32
Mon Nov 04 16:00:15 JST 2019 : task11 end   : 21 
Mon Nov 04 16:00:15 JST 2019 : sub-task11-2 start : 31
Mon Nov 04 16:00:15 JST 2019 : sub-task11-4 start : 32
Mon Nov 04 16:00:15 JST 2019 : task12 start : 21 
Mon Nov 04 16:00:15 JST 2019 : sub-task12-4 start : 21
Mon Nov 04 16:00:17 JST 2019 : sub-task10-5 end   : 25
Mon Nov 04 16:00:17 JST 2019 : sub-task11-1 end   : 24
Mon Nov 04 16:00:17 JST 2019 : sub-task11-5 start : 25
Mon Nov 04 16:00:17 JST 2019 : sub-task12-1 start : 24
Mon Nov 04 16:00:18 JST 2019 : sub-task11-4 end   : 32
Mon Nov 04 16:00:18 JST 2019 : sub-task12-4 end   : 21
Mon Nov 04 16:00:18 JST 2019 : sub-task11-2 end   : 31
Mon Nov 04 16:00:18 JST 2019 : sub-task12-2 start : 32
Mon Nov 04 16:00:18 JST 2019 : sub-task12-3 start : 31
Mon Nov 04 16:00:18 JST 2019 : task12 end   : 21 
Mon Nov 04 16:00:18 JST 2019 : task13 start : 21 
Mon Nov 04 16:00:18 JST 2019 : sub-task13-4 start : 21
Mon Nov 04 16:00:20 JST 2019 : sub-task11-5 end   : 25
Mon Nov 04 16:00:20 JST 2019 : sub-task12-1 end   : 24
Mon Nov 04 16:00:20 JST 2019 : sub-task12-5 start : 25
Mon Nov 04 16:00:20 JST 2019 : sub-task13-1 start : 24
Mon Nov 04 16:00:21 JST 2019 : sub-task12-3 end   : 31
Mon Nov 04 16:00:21 JST 2019 : sub-task12-2 end   : 32
Mon Nov 04 16:00:21 JST 2019 : sub-task13-4 end   : 21
Mon Nov 04 16:00:21 JST 2019 : sub-task13-2 start : 31
Mon Nov 04 16:00:21 JST 2019 : sub-task13-3 start : 32
Mon Nov 04 16:00:21 JST 2019 : task13 end   : 21 
Mon Nov 04 16:00:21 JST 2019 : task14 start : 21 
Mon Nov 04 16:00:21 JST 2019 : sub-task14-4 start : 21
Mon Nov 04 16:00:23 JST 2019 : sub-task13-1 end   : 24
Mon Nov 04 16:00:23 JST 2019 : sub-task12-5 end   : 25
Mon Nov 04 16:00:23 JST 2019 : sub-task13-5 start : 24
Mon Nov 04 16:00:23 JST 2019 : sub-task14-1 start : 25
Mon Nov 04 16:00:24 JST 2019 : sub-task13-3 end   : 32
Mon Nov 04 16:00:24 JST 2019 : sub-task14-4 end   : 21
Mon Nov 04 16:00:24 JST 2019 : sub-task13-2 end   : 31
Mon Nov 04 16:00:24 JST 2019 : sub-task14-2 start : 32
Mon Nov 04 16:00:24 JST 2019 : task14 end   : 21 
Mon Nov 04 16:00:24 JST 2019 : sub-task14-3 start : 31
Mon Nov 04 16:00:24 JST 2019 : task15 start : 21 
Mon Nov 04 16:00:24 JST 2019 : sub-task15-4 start : 21
Mon Nov 04 16:00:26 JST 2019 : sub-task14-1 end   : 25
Mon Nov 04 16:00:26 JST 2019 : sub-task13-5 end   : 24
Mon Nov 04 16:00:26 JST 2019 : sub-task15-1 start : 24
Mon Nov 04 16:00:26 JST 2019 : sub-task14-5 start : 25
Mon Nov 04 16:00:27 JST 2019 : sub-task14-2 end   : 32
Mon Nov 04 16:00:27 JST 2019 : sub-task15-2 start : 32
Mon Nov 04 16:00:27 JST 2019 : sub-task14-3 end   : 31
Mon Nov 04 16:00:27 JST 2019 : sub-task15-4 end   : 21
Mon Nov 04 16:00:27 JST 2019 : sub-task15-3 start : 31
Mon Nov 04 16:00:27 JST 2019 : task15 end   : 21 
Mon Nov 04 16:00:27 JST 2019 : task16 start : 21 
Mon Nov 04 16:00:27 JST 2019 : sub-task16-4 start : 21
Mon Nov 04 16:00:29 JST 2019 : sub-task14-5 end   : 25
Mon Nov 04 16:00:29 JST 2019 : sub-task15-1 end   : 24
Mon Nov 04 16:00:29 JST 2019 : sub-task15-5 start : 25
Mon Nov 04 16:00:29 JST 2019 : sub-task16-1 start : 24
Mon Nov 04 16:00:30 JST 2019 : sub-task15-2 end   : 32
Mon Nov 04 16:00:30 JST 2019 : sub-task16-2 start : 32
Mon Nov 04 16:00:30 JST 2019 : sub-task16-4 end   : 21
Mon Nov 04 16:00:30 JST 2019 : sub-task15-3 end   : 31
Mon Nov 04 16:00:30 JST 2019 : task16 end   : 21 
Mon Nov 04 16:00:30 JST 2019 : sub-task16-3 start : 31
Mon Nov 04 16:00:30 JST 2019 : task17 start : 21 
Mon Nov 04 16:00:30 JST 2019 : sub-task17-4 start : 21
Mon Nov 04 16:00:32 JST 2019 : sub-task16-1 end   : 24
Mon Nov 04 16:00:32 JST 2019 : sub-task15-5 end   : 25
Mon Nov 04 16:00:32 JST 2019 : sub-task16-5 start : 24
Mon Nov 04 16:00:32 JST 2019 : sub-task17-1 start : 25
Mon Nov 04 16:00:33 JST 2019 : sub-task16-2 end   : 32
Mon Nov 04 16:00:33 JST 2019 : sub-task17-2 start : 32
Mon Nov 04 16:00:33 JST 2019 : sub-task17-4 end   : 21
Mon Nov 04 16:00:33 JST 2019 : sub-task16-3 end   : 31
Mon Nov 04 16:00:33 JST 2019 : task17 end   : 21 
Mon Nov 04 16:00:33 JST 2019 : sub-task17-3 start : 31
Mon Nov 04 16:00:33 JST 2019 : task18 start : 21 
Mon Nov 04 16:00:33 JST 2019 : sub-task18-4 start : 21
Mon Nov 04 16:00:35 JST 2019 : sub-task16-5 end   : 24
Mon Nov 04 16:00:35 JST 2019 : sub-task17-1 end   : 25
Mon Nov 04 16:00:35 JST 2019 : sub-task18-1 start : 25
Mon Nov 04 16:00:35 JST 2019 : sub-task17-5 start : 24
Mon Nov 04 16:00:36 JST 2019 : sub-task17-2 end   : 32
Mon Nov 04 16:00:36 JST 2019 : sub-task18-2 start : 32
Mon Nov 04 16:00:36 JST 2019 : sub-task17-3 end   : 31
Mon Nov 04 16:00:36 JST 2019 : sub-task18-3 start : 31
Mon Nov 04 16:00:36 JST 2019 : sub-task18-4 end   : 21
Mon Nov 04 16:00:36 JST 2019 : task18 end   : 21 
Mon Nov 04 16:00:36 JST 2019 : task19 start : 21 
Mon Nov 04 16:00:36 JST 2019 : sub-task19-4 start : 21
Mon Nov 04 16:00:38 JST 2019 : sub-task17-5 end   : 24
Mon Nov 04 16:00:38 JST 2019 : sub-task18-1 end   : 25
Mon Nov 04 16:00:38 JST 2019 : sub-task18-5 start : 24
Mon Nov 04 16:00:38 JST 2019 : sub-task19-1 start : 25
Mon Nov 04 16:00:39 JST 2019 : sub-task18-2 end   : 32
Mon Nov 04 16:00:39 JST 2019 : sub-task18-3 end   : 31
Mon Nov 04 16:00:39 JST 2019 : sub-task19-3 start : 31
Mon Nov 04 16:00:39 JST 2019 : sub-task19-2 start : 32
Mon Nov 04 16:00:39 JST 2019 : sub-task19-4 end   : 21
Mon Nov 04 16:00:39 JST 2019 : task19 end   : 21 
Mon Nov 04 16:00:39 JST 2019 : task20 start : 21 
Mon Nov 04 16:00:39 JST 2019 : sub-task20-4 start : 21
Mon Nov 04 16:00:41 JST 2019 : sub-task19-1 end   : 25
Mon Nov 04 16:00:41 JST 2019 : sub-task19-5 start : 25
Mon Nov 04 16:00:41 JST 2019 : sub-task18-5 end   : 24
Mon Nov 04 16:00:41 JST 2019 : sub-task20-1 start : 24
Mon Nov 04 16:00:42 JST 2019 : sub-task20-4 end   : 21
Mon Nov 04 16:00:42 JST 2019 : sub-task19-3 end   : 31
Mon Nov 04 16:00:42 JST 2019 : sub-task19-2 end   : 32
Mon Nov 04 16:00:42 JST 2019 : task20 end   : 21 
Mon Nov 04 16:00:42 JST 2019 : sub-task20-3 start : 32
Mon Nov 04 16:00:42 JST 2019 : sub-task20-2 start : 31
Mon Nov 04 16:00:42 JST 2019 : task21 start : 21 
Mon Nov 04 16:00:42 JST 2019 : sub-task21-4 start : 21
Mon Nov 04 16:00:44 JST 2019 : sub-task19-5 end   : 25
Mon Nov 04 16:00:44 JST 2019 : sub-task20-1 end   : 24
Mon Nov 04 16:00:44 JST 2019 : sub-task20-5 start : 24

コンソールの出力を見ると以下のことが確認できる。

  • スレッドIDを列挙すると、21, 24, 25, 31, 32の5つが登場。このうち21が呼び出し元のスレッドで、それ以外の4つが@Asyncで実行されたスレッド。
  • @Asyncで実行されたスレッドは全部で4つあり、maxPoolSizeに一致する。
  • 約1分間の間に20個のタスクが完了。
  • 約1分間の間に97個のサブタスクが完了。

毎秒5個のサブタスクを実行しているが、実際には1.6個/秒くらいのタスクしかこなせていない。これは結局のところmaxPoolSizeを超えてメインタスク(スレッド21)がCallerRunsPolicyによって毎回サブタスクを実行する羽目になり、そこで3秒間のスリープが入り、3秒に1個くらいのタスクしか実行できないため。結果として、5個/3秒くらいしかサブタスクがこなせないということらしい。

試しにcorePoolSizeやmaxPoolSizeを十分大きい値(10とか)にしてみると、5個/秒くらいのパフォーマンスになることが確認できる。ということで、設定値(Reject時のポリシー含む)やタスクのsleep時間などを色々と変更しながら挙動を追いかけると理解が深まりそう。

Spring CloudのRibbonによるロードバランシング

Spring Cloudとは

マイクロサービスでよく見られる共通パターンを迅速に構築するためのツール群。

  • Distributed/versioned configuration (Spring Cloud Config)
  • Service registration and discovery (Eureka)
  • Routing (Zuul)
  • Service-to-service calls (Eureka)
  • Load balancing (Ribbon)
  • Circuit Breakers (Hystrix)
  • Global locks (Spring Cloud Cluster?)
  • Leadership election and cluster state (Spring Cloud Cluster?)
  • Distributed messaging (Spring Cloud Bus?)

括弧内には書いたのは対応するであろうライブラリ。

Ribbonとは

クライアントサイドロードバランサー。その名の通り、呼び出し元で呼び出し先のサービスをどのようにロードバランシングするかを定義する。 ドキュメントとしては以下のあたりを参考に。

実装例(kotlin)

getting startedをほぼそのままの実装例。以下の2つのサービスが登場人物。

  • say-hello-service
    • GET / -> 「Hi!」と返すだけ。ヘルスチェックのためのエンドポイントっぽい。
    • GET /greeting -> 挨拶を返す(挨拶の種類はランダム)
  • user-service
    • GET /hi?name=xxx -> 受け取ったnameに対して挨拶を返すサービス。どんな挨拶をするかはsay-hello-serviceを呼び出して決める。

user-serviceがクライアントであり、Ribbonの実装はこちらに記述する。say-hello-serviceが複数立ち上がっている状態でuser-serviceを経由して呼び出し、ロードバランシングされることが確認できればOK。

コードは https://github.com/suzukimit/spring-cloud-demo にあります。

say-hello-serviceの実装

application.yml

spring:
  application:
    name: say-hello

server:
  port: 8090

アプリ名に say-hello を指定。

build.gradle

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

とりあえずspring-boot-starter-webがあればOK。

Controller

@RestController
class SayHelloController {
    companion object {
        private val log = LoggerFactory.getLogger(SayHelloController::class.java)
    }

    @Value("\${server.port}")
    lateinit var port: String

    @RequestMapping(value = "/greeting")
    fun greet(): String {
        log.info("Access /greeting")

        val greetings = listOf("Hi there", "Greetings", "Salutations")
        val rand = Random()

        val randomNum = rand.nextInt(greetings.size)
        return greetings[randomNum] + "(from $port)"
    }

    @RequestMapping(value = "/")
    fun home(): String {
        log.info("Access /")
        return "Hi!"
    }
}

/greeting を呼び出すことで適当な挨拶を返す。ルート( / )はクライアントサイドからのpingに応答するヘルチェック用のもので、アクセスされた際にログに出力。

user-serviceの実装

application.yml

spring:
  application:
    name: user

server:
  port: 8888

say-hello:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:8090,localhost:9092,localhost:9999
    ServerListRefreshInterval: 15000

ロードバランシングの対象となるサービスのアプリ名(say-hello)に対してribbonの設定を記述する。

ここではeurekaを使用をOFFにしている。そのため listOfServers で静的にリクエスト先を指定している(eurekaを使う場合は不要と思われる)。

build.gradle

dependencies {
    implementation("org.springframework.cloud:spring-cloud-starter-netflix-ribbon")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

dependencyManagement {
    imports {
        mavenBom("org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2")
    }
}

spring-boot-starter-webに加え、spring-cloud-starter-netflix-ribbonが必要。

Configuration

@Configuration
class RibbonConfiguration {

    @Bean
    fun ribbonPing(): IPing {
        return PingUrl()
    }

    @Bean
    fun ribbonRule(): IRule {
        return AvailabilityFilteringRule()
    }
}

ping(ヘルスチェック)のurlや、ロードバランシングルールの設定など。

Controller

@RestController
@RibbonClient(name = "say-hello", configuration = [RibbonConfiguration::class])
class UserController {
    @LoadBalanced
    @Bean
    fun restTemplate() = RestTemplate()

    @Autowired
    lateinit var restTemplate: RestTemplate

    @RequestMapping("/hi")
    fun hi(@RequestParam(value = "name", defaultValue = "Artaban") name: String): String {
        val greeting = this.restTemplate.getForObject("http://say-hello/greeting", String::class.java)
        return String.format("%s, %s!", greeting, name)
    }
}

RestTemplateに @LoadBalanced が付与されており、これによって http://say-hello/greeting のようにアプリケーション名でリクエストを送ることができる。

動作確認

say-hello-service は全部で三つ立ち上げる。ポートは何も指定しないと8090になるので、コマンドライン引数なり環境変数なりで9092, 9999ポートを指定して起動する。

あとは、curlでuser-serviceにリクエストを投げ、毎回ポート番号が異なることを確認できればOK。

$ curl http://localhost:8888/hi
Salutations(from 9092), Artaban!

Windowsユーザーでもawkを使いたい

最近会社でDB-Unitを使ってガッツリとテストをするのが流行っている(自分の中で)。DB-Unitを使う場合、データベースに入れる前提データや、assertする正解データをcsvの形式で扱うことができる。しかし、テーブルに列追加・列削除が入った場合に、これらのcsvファイルも更新する必要がある。

csvは結局ただのテキストデータなので、こういったファイルを編集するのにはやはりawkが便利だ。しかし、悲しいことに会社のPCはWindowsなので、気軽にawkを使えない。色々と探した結果、JavaのVM上でawkを実行できる"Jawk"が便利そうだ。

Jawkに決めた理由は、awk本体をインストールする必要がないためだ。Windows版のawkをインストールするのはもちろん構わないのだが、自分以外のメンバーに使ってもらうと思った時に、わざわざインストールしてもらう手間を考えると、Jawkがあればいっか、と思ったのだ。

Jawkの使い方

この辺りを参考にさせて頂きました。すごく簡単。jawkのjarをダウンロードして、以下のようにターミナルから実行する。

java -cp jawk.1_02.jar org.jawk.Awk -f awkProgram.awk test.csv

awkProgram.awkってのは適当なawkファイル。スクリプトファイルからawkを実行するのではなく、コマンド直打ちでももちろん使える。

awkCSVファイルの特定列を削除

awkで出来ることをJavaで書こうとすると、ソースの分量が一気に増えてしまって、正直やってられない。やりたかったことはcsvファイルの列追加や列削除であり、awkだとこんな感じにシンプルに書ける。

BEGIN {
  FS   = ","
  sepa = ","
}

{
  cnt = 0;

  for (i = 1; i <= NF; i++) {
    #引数で指定した番号は出力しない
    if (i == para) {
      continue
    }
    #一番最初の列はセパレータを付けない
    if (cnt == 0) {
      printf($i)
    } else {
      printf(sepa $i)
    }
    #カウントのインクリ
    cnt++
  }

  printf("\n");
}

実行するときは、-vオプションで引数を指定する。

java -cp jawk.1_02.jar org.jawk.Awk -v para=3 -f awkProgram.awk test.csv

paraという引数(任意に変数名は指定できる)に、例えば3を指定すると、test.csvの三列目がガッツリ削除される。おそらくもっとシンプルに、それこそ1〜3行程度で書けるはずだが、割と丁寧目に書くとこんな感じになった。

awkは時代遅れ?

awkは研究室時代にちょっと触れた程度だったので、正月に暇つぶしがてらにドットインストールでawkの復習をしたのだが、こんなに使いやすかったのか!と非常に興奮した。相当古い言語(というかただのLinuxコマンド?)だが、思ったよりずっと洗練されていて、比較的新しい言語と同様の実装がなされていた。連想配列が使えるなんて知らなかった。

用途が限定されてはいるが、Windowsユーザーでも学ぶ価値は十分にあるので、是非色んな人に触ってほしい。あと、ドットインストールはすごくわかりやすくていつもお世話になってます。

2013年に(そこそこちゃんと)読んだ本たち

今年もそろそろ終わりそうなので、Amazonの履歴とEvernoteのログを辿って2013年に読んだ本たちをまとめてみる。(そこそこちゃんと)とあるのは、ちょっとだけ読んで放置した本たちがけっこういるためである。

  • パーフェクトJava

去年から持っていた本だけど、Javaの業務コードをガツガツ書く機会がなく、あまり活用されてなかった。今年はガッツリとJavaに付き合うことが出来たので、パーフェクトJavaにはとてもお世話になった。Javaの初級者向けの本は他にもたくさんあるが、この本は「どういうときにこのコードを書くべき」という指針を与えてくれるので、経験の浅い私にはとてもありがたかった。

パーフェクトJava (PERFECT SERIES) (PERFECT SERIES 2)

パーフェクトJava (PERFECT SERIES) (PERFECT SERIES 2)

ファウラーのリファクタリングと一緒に購入した本。ファウラーの方を読む前に、まず使い慣れたJavaに特化された方を読みたいから買った。あと、結城さんの本は読みやすいので好きだったため。思った通りとても理解しやすい本だった。情報はファウラーの方に比べると圧倒的に足りないので、ファウラーの方もコツコツ読み進めていきたい。

Java言語で学ぶリファクタリング入門

Java言語で学ぶリファクタリング入門

誰かのブログで、「Java使いなら必読」みたいなことが書いてあったので買った。恥ずかしながら、テストコードを一切書かずに開発をしていたので、まずはJUnitの使い方を覚えるところから始まった。Quick JUnitを入れるとテストコードを楽に書けるとあったので、入れてみたら本当に楽で感動した。そこから、少しずつテストを書いて行くようになると、開発するときの「安心感」がぜんぜん違うことに驚いた。ビクビクしながら開発するのは、本当にストレスが溜まるので、もう昔には戻れないです。テストの有り難みに気づかせてくれる本になってくれた、とても良い本だった。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

最近ガツガツ読んでいる。恥ずかしながら、読んでいてよく分からないところがけっこうよくあり、自分の未熟さを思い知る。付箋を張りながら読み進め、何度も読み返したい。

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

  • 反復学習ソフト付き 正規表現書き方ドリル

正規表現を使いこなせるプログラマはカッコいいだろうと、そんな下心から買った本。ひたすらドリルを解いた。しかし、どういうわけか意外と頭に残っていない。正規表現を使いたくなる場面ってVimを使っているときが多いのだが、そもそもVim正規表現がちょっと特殊なところがあり、そういう意味で思い通りに行っていない。

反復学習ソフト付き 正規表現書き方ドリル (WEB+DB PRESS plus)

反復学習ソフト付き 正規表現書き方ドリル (WEB+DB PRESS plus)

  • Vimテクニックバイブル ~作業効率をカイゼンする150の技

Vimmerなら読めみたいな空気が漂っているので買った本。色んなプラグインが紹介されており、Vimの世界の広大さに驚く。リファレンス的に利用することが多い。ただなあ、Vimの情報ってネットで検索すると何でも出るので、リファレンスとしてなかなか出番がない。

Vimテクニックバイブル ?作業効率をカイゼンする150の技

Vimテクニックバイブル ?作業効率をカイゼンする150の技

  • 実践Vim 思考のスピードで編集しよう!

Vimを使うとき、どんなことを考えて編集すればよいのかを丁寧に解説してくれる。スポーツや音楽と同じで、「悪い癖」がついてるのを矯正するのにとても役立つと思う。私の場合は、ヴィジュアルモードを多用する癖が付いているのだが、Vimの繰り返し作業の強みを活かすためには、ヴィジュアルモードの多様は避けるべきということ気付き、感動した。初心者の人は、テクニックバイブルよりまずはこっちを大人しく読んだほうがよいでしょう。

実践Vim 思考のスピードで編集しよう!

実践Vim 思考のスピードで編集しよう!

  • 体系的に学ぶ 安全なWebアプリケーションの作り方

会社の勉強会で輪読している本。そもそもWebアプリケーションを自分で作ったことがないので、ドットインストールでPHPJavaScriptや仮想環境の構築を学ぶところから始まった。今まで何となく聞いたことのあるXSSやCSRFというのを頭と体で理解出来た。あと、会社で作っている製品にポツポツと脆弱性があることに気がついた。「こういうときに脆弱性が生まれる」という危険信号みたいなのを意識しながら開発できるようになったのは大きいと思う。まあ、今の部署だとWebアプリケーションとの関わりが薄いので、なかなか業務には活かせないのですが。

  • Passionate Programmer

前に記事を書いた。幸い、プログラミングに関して自分の中には情熱がある(はず)ので、情熱の赴くままに活動したい。といっても、口だけになっちゃうんだよなあ。

情熱プログラマー ソフトウェア開発者の幸せな生き方

情熱プログラマー ソフトウェア開発者の幸せな生き方

  • すごいHaskellたのしく学ぼう!

転職する先輩からHaskellを強く勧められ、頂いた本。関数型言語に触れるのは初めてだった。非関数型言語で開発するときにも、関数型言語のように、副作用のでないコードを書くように意識するようになった(気がする)。実は途中で止まっているので、おいおい再開したい。後半が難しそう。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • リーダブルコード

この本自体がリーダブルな良書だった。命名やスコープを意識できていない新人には全員に読んで欲しい本。ただ、会社でコードを書くときは、色んな人が入り乱れてコードを書くので、この本に書いてあることが通用していない場合がある。改めて、大勢の人数で開発をするのは難しいことだなと、ときどきめまいを起こしそうになる。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

色んなところで絶賛されていたので読んだ。書いてあることはとても良くわかったけど、実際にはそんなに上手くいかないでしょ・・・みたいな無力感を感じることが多かった。しかし、諦めちゃ駄目だなとも思う。幸い今の職場は、開発方法が悪いと思ったらいくらでも手を上げて改善できる場所なので。

アジャイルサムライ−達人開発者への道−

アジャイルサムライ−達人開発者への道−

ラインナップを見てみると、偏りがひどいし、初級者向けの本ばかりなのでちょっと公開するのが恥ずかしい。来年はインプットよりアウトプットを重視していきたい。

3-2受動的攻撃と同一生成元ポリシーのまとめ(安全なwebアプリケーションの作り方)

社内で最近、徳丸さんの「安全なwebアプリケーションの作り方」という本の輪読会が始まった。 正直なところ、そこまでセキュリティ云々の話に心が惹かれるわけではないのだが、webアプリケーションを勉強する良いきっかけになりそう!ということで参加している。甘い気持ちで入ったが、やはりwebの技術の前知識が乏しく、実際にアプリケーションを作ったことももなく、かなり苦戦している。いやでもね、webアプリケーションめちゃくちゃ楽しいです。エンタープライズJavaな人間なので仕事では使い道はあまりないけど、今後のためにしっかり食いついて行きたいと思う次第です。

以下、3-2節のまとめ。

3-2受動的攻撃と同一生成元ポリシー

簡単なまとめ:
受動的攻撃という攻撃手法と、受動的攻撃に対するブラウザの防御戦略である同一生成元ポリシーについて説明。 同一生成元ポリシーの元でも、Webアプリケーションに脆弱性があると攻撃されることがある(→4章)。



受動的攻撃

Webサイトに罠を仕掛けることにより、罠を回覧した利用者を通してアプリケーションを攻撃する手法。

受動的攻撃のパターン
  1. 単純な受動的攻撃:罠サイトを回覧させる(図3-23)
  2. 正規サイトを悪用する受動的攻撃:正規サイトに罠が仕掛ける(図3-24)
  3. サイトをまたがった受動的攻撃:罠サイトを回覧したユーザに正規サイトへの攻撃リクエストを送信させる(図3-25)

サンドボックス

ブラウザ側でプログラムの「できること」を制限し、安全性を高めるという考え方(子供の遊ぶ「砂場」という意味)。

  • ローカルファイルへのアクセス
  • プリンタなどの資源
  • ネットワークアクセス(同一生成元ポリシー)

同一生成元ポリシー

クロスドメイン(サイトをまたがったアクセス)を禁止する、JavaScriptの機能。

同一生成元である条件
  • URLのホスト(FQDN: Fully Qualified Domain Name)が一致している
  • スキーム(プロトコル)が一致している
  • ポート番号が一致している
JavaScriptによるiframeアクセスの実験

※iframeタグ:HTML文書の中にフレームを埋め込むもの。

iframeに「http://example.jp/32/32-002.html」の内容を表示し、それをJavaScriptで参照出来るかどうかを試す。

  1. http://example.jp/32/32-001.html」の場合→参照可
  2. http://trap.example.com/32/32-900.html」の場合→参照不可(ホストが異なるため)
クロスドメインが許可されているもの
  • frame要素・iframe要素
  • img要素
  • script要素
  • CSS
  • form要素のaction属性

情熱プログラマーを読んだ

GWの前半、奥多摩の静かな旅館に一人で泊まりながら、「情熱プログラマー ソフトウェア開発者の幸せな生き方」を読んだ。タイトル的に精神論的な内容かと思っていたが、実践的なアドバイスに溢れている有益な本だった。仕事でプログラムを書く機会のある全ての人に読んで欲しいと思える本です。

情熱プログラマー ソフトウェア開発者の幸せな生き方

情熱プログラマー ソフトウェア開発者の幸せな生き方

全部で53のエッセイが、五つの章に分けられて書かれている。気に入ったものをいくつか切り抜いてみる。

(余談だけど、著者のChad FowlerさんはIT業界に入る前はプロのジャズSax奏者で、本の至るところで音楽活動の経験を元にした記述がある。ジャズFlute奏者*1の自分にとっては、共感出来る点が多く、そこも気に入ってるポイントだ。)

3. コーディングはもう武器にならない

君が「ただのプログラマ」だとしても、顧客に彼らのビジネスの言葉で話しかけることができれば、それはもう立派なスキルだ。君だって、一緒に仕事をしなければならない全員がソフトウェア開発について熟知してたら仕事が楽になるって思うだろう?

序盤からいきなり、コーディングの技術だけでは生きていけないという当たり前の現実を突きつけられる。それはわかっているのに、(我々)プログラマは技術が好きだからそっちに走ってしまう。

でも、確かに、会社のチーム内で誰よりも仕事が出来る先輩は、会計の知識がずば抜けて豊富で、色んな人がその先輩に相談しに行っている。この人がいなかったら全体の生産性はどれほど落ちてしまうのだろう。

4. 一番の下手くそでいよう

バンドの中で一番下手くそというのは、いつも自分より優れた人たちと一緒に演奏するという意味だ。僕はミュージシャンとして、この教訓を早いうちに学び、忠実に守るという幸運を得た。

一番下手という状況にいると、必ず変化が起こる。天才の隣にいるだけで天才に変身出来る。これは、経験的に理解できる。近づきたいという憧れの気持ちと、下手くそである悔しさが成長を促進させてくれる。

ただ、自分が一番へたという状況は、心情的にすごくしんどい。自信を失うし、周りに迷惑をかけてしまう不安もある。それでも、それを乗り切るだけの価値があるのだろう。

7. 万能選手になろう

こんな変化が激しい環境で生き残るのは柔軟性だ

万能選手になろうとするなら、特定の役割やテクノロジで自分自身を規定しないことだ

会社で仕事をしていて、色んなトラブルに見舞われる。デプロイが上手く回らなかったり、DBサーバーにアクセス出来なくなったり。こういった場面では、「◯◯さんが詳しいから任せよう」という流れになる。◯◯さんは誰かから命令されて何かを学んだのではなく、自発的に学んだのだと思う。

「仕事で使わないから/命令されていないから」というのを何かを学ばない言い訳にするのではなく、自分にとって本当に必要な技術を汎用的に学ぼうというお話。これも耳に痛い。同期で活躍しているアイツは、こういうところを頑張ってるから偉いと思う。

15. 一に練習、二に練習

僕らの業界でも、もっと練習の時間が必要だ。(中略)実務で練習するという考え方は捨てなければいけない。自分の技術に対して時間を投資しなければ。

ミュージシャンやアスリートは鍛錬を積んでいるように、エンジニアも練習しなさいという厳しいお話。それも、仕事の中だけではなく仕事の外でも技術を学ぶ必要性がある。

それじゃあいったい何を練習すればいいんだろう?自分の限界を押し広げるには何をしたらいい?

具体的に何を練習すれば良いのか?という点についても、ジャズの経験を元に以下のカテゴリで紹介されている。

  • 身体的技術
  • 初見
  • 即興

プログラミングの練習をジャズの練習で例えるところが面白い。具体的な内容は省きます(面倒)。

28. 8時間燃焼

これは、どう頑張っても8時間以上働き続けるのは無理なくらい容赦なく働くべき、という考え方だ。

ダラダラ長時間働かないで、8時間を全力で集中して働けというお話。自分は平均すると一日12時間以上実作業をしている。正直なところ、たまにネットサーフィンもしてたり、集中しているとはとても言えない。

作業は、短くするほうが多くできる場合がある。(中略)燃え尽きた状態では、想像力も乏しくなって、作業の質は大幅に低下する。

その通りだと思います。

(前略)まだ午前10時半だし、どうせみんな帰った後も何時間か仕事を続けるんだから、少しくらい技術系の最新ニュースをチェックしてもいいだろうなんて考えがちだ。

あるある。ありすぎて困る。

プロジェクトはマラソンであり、短距離走ではない

わかっているけど、どうしても短期的な視点で取り組んでしまう。マラソンでは、スタートからゴールまで一定速度で走ることが最も効率的だと聞いたことがあるが、この感覚を仕事にも適用すべきなんだろうね。

39 業界で名前を売ろう

業界で名前を売るのに出版や講演より効果的な手段はない。とはいえ、平凡なプログラマがほんの著者になったり講演者になったりするのは難しいよね。そこでWebからスタートしよう。

この辺りも、会社に自分を縛るなというメッセージに聞こえる。まずは、技術ブログを書いたり身近な勉強会から初める。

こういう方法で名前を売り出すときに一番大事なことは、時期尚早かなと思うくらいでスタートすることだ。たいていの人は自分を売り込むときに消極的になる。君は教えるべきものを持っている。もう準備万端だと感じることは決してない。この瞬間に始めてもいいくらいだ。

プロシュートの兄貴も同じことを言ってた。

まとめ

何か変わらなければいけないのに、何をすれば良いのかわからない。そんな風に感じている若手のエンジニアはけっこう自分以外にもいるんじゃないかと予想する。そんな人にこそオススメしたい。

この本に書いてあることを全て実行することは、正直なところ難しい。でも、背中を押す手助けにはなってくれて、具体的にやりたいことがいくつも生まれた。こうしてはてなブログを開こうと思ったし、Markdown記法も学んだ。

今後もこうやって少しずつ学んで、アウトプットしていきたいと考える次第です。三日坊主にならないことを祈る。

*1:当然アマチュアというか初心者