kakts-log

programming について調べたことを整理していきます

kubernetes: Deploymentリソースを作成してレプリカPodが稼働するまでの流れを整理する

Kubernetes3 Advent Calendar 2019の17日目の記事となります。

qiita.com
今回はDeploymentリソースが作成されてから、実際にNode上でPodのコンテナが起動されるまでの流れをざっくり整理したいと思います。 自分の理解を深めるため、kubernetes自体のソースコード(kubernetes v1.16.4)を取り上げつつ整理します。

Deploymentが作成されてからの流れ

Deploymentリソースの登録

kubectlまたは何かしらのクライアントによって、kube-apiコンポーネントを介してDeploymentリソースを登録します。 kube-apiによって、登録されたDeploymentリソースはetcdに登録されます。

DeploymentリソースによってReplicaSetの作成

Deploymentリソースの制御を行うDeploymentコントローラーが、Deploymentリソースの作成を検知します。 Deploymentが管理するReplicaSetリソースの存在を確認し、Pod Templateのハッシュ値に一致するラベルを持つReplicaSetがない場合、Deploymentコントローラーが新規にReplicaSetリソースを作成します。

https://github.com/kubernetes/kubernetes/blob/v1.16.4/pkg/controller/deployment/deployment_controller.go#L598

このとき、replicasの値はDeploymentリソースで指定されている値となります。

ReplicaSetによって指定した数のPodの作成

ReplicaSetコントローラーがクラスター内のReplicaSetリソースをチェックしていて、ReplicaSetリソースが管理しているPodが、replicasの値(理想状態)に一致しているかをチェックします。この理想状態に一致していない場合、Podの数をreplicasの数だけ稼働させるように、Podの作成・削除を行います。

今回はPodの作成の場合のみ考えます。 ReplicaSetコントローラーによって作成されたPodリソースは、etcdに初期状態で登録されます。ここでいう初期状態とは、PodがまだどのNodeにも割り当てられておらず、起動もしていない状態を意味します。

kube-schedulerによるPodのNodeへの割り当て

前項で初期状態のPodが作成されましたが、このPodを稼働させるために、どのNodeで稼働させるかを決めなくてはなりません。ここでMaster上で稼働しているkube-schedulerが、その割り当ての役割を担います。

kube-schedulerは、まず割り当て対象となるNodeのフィルタリングを行います。Podのリソース要求を満たすのに十分なリソースを持つNodeを候補として洗い出します。 その後、候補となったNodeに対してスコア付を行います。スコアが高いNodeを対象とします。このスコア付の処理は、かなり複雑なのでこの記事では省略します。

Podのスケジューリング処理 https://github.com/kubernetes/kubernetes/blob/v1.16.4/pkg/scheduler/scheduler.go#L515-L676

Podの割り当て対象となるNodeの洗い出し https://github.com/kubernetes/kubernetes/blob/v1.16.4/pkg/scheduler/scheduler.go#L337-L347

kubernetesの公式ドキュメントの方にkube-schedulerの記事があり、先日こちらの日本語訳のページを行いましたので紹介します。こちらを読んでいただけるとおおまかな流れが少しでもわかるかと思います。 kubernetes.io

割り当て対象のNodeが決まると、kube-schedulerは、PodのNodeNameという値に、このNodeの名前を設定します。

注意するのは、kube-schedulerは、あくまでもPodのNodeへの割り当てを決めるのみで、実際にPodを稼働させません。

kubeletによるPodのコンテナーの起動

ここで、Podに対してNodeが割り当てられ、稼働させる準備ができました。まだ稼働していません。 各Node上で稼働しているkubeletというコンポーネントが、Pod内のコンテナーの起動処理を担います。

Podリソースのボリュームのマウント設定や、initContainersなどの設定はkubeletによって行われます。

kubeletは、etcdのリソースを確認して、自分自身のNodeに割り当てられているが、まだ稼働していないPodを探します。 もし稼働されていないPodがあったら、このPodを稼働させます。

Deploymentによって指定されたreplicasの数だけ、同様にPodを稼働させていきます。

最後に

これでDeploymentリソースが作られてからPodが稼働するまでの流れをざっくりと説明しました。 ソースコードもかなり量が多く、全部追いきれなかったため、後々詳細を読んでこちらの記事に追記していく予定です。

Node.js のWASIモジュールについて

この記事は Node.js Advent Calendar 2019の16日目の記事となります。

qiita.com

今回は Node.js 13.3.0から導入されたWebAssembly System Interface (WASI)モジュールに関して紹介します。

WASIについて

wasi.dev

github.com

WASI APIはWebAssembly System Interface(WASI)の仕様の実装を提供するものです。WASIはPOSIXライクな関数を介してOSへのアクセスを行うためのサンドボックスWASMアプリケーションを提供します。

WebAssemblyおよびWASIに関しては、あまり詳しくなかったのですが、下記の記事を読んで理解が深まりました。 inzkyk.github.io hacks.mozilla.org

Node.js v13.3.0から、コード上ではrequire('wasi')で利用可能です。しかし使用するためには起動時に--experimental-wasi-unstablre-preview0オプションをつける必要があります。

WASI クラスについて

WASI classはWASIシステムコールAPIと、WASIベースのアプリケーションでの利用に便利な追加のメソッドを提供します。各WASIインスタンスは独立したサンドボックス環境を表現します。セキュリティの観点から、各WASIインスタンスはそれぞれコマンドライン引数、環境変数、そして明示的に指定されるサンドボックスディレクトリーを指定します。

new WASI([options])

new WASIによってWASIインスタンスを作成する際に、引数としてオプションのオブジェクトを渡します。 このオプションの項目は以下の通りです。

  • args : WebAssemblyアプリケーションがコマンドライン引数として扱うための文字列の配列です。引数の最初の要素はWASIコマンド自体に対する仮想パスとなります。 デフォルト: []

  • env : process.envと同様に、WebAssemblyアプリケーションが環境変数として扱うためのオブジェクトです。 デフォルト: {}

  • preopens このオブジェクトはWebAssemblyアプリケーションのサンドボックスディレクトリを表現します。preopensオブジェクトの文字列のキーは、サンドボックス内のディレクトリーとして扱われます。そのキーに対応する値は、ホストマシン上のディレクトリに対する実際のパスとなります。

wasi.start(instance)

インスタンスの_start()メソッドの実行によって処理の実行を開始するメソッドとなります。もしインスタンス_start()というメソッドが公開されていない場合、代わりに__wasi_unstable_reactor_start()を実行します。インスタンスに上記の両方のメソッドがない場合、このメソッドは何も行いません。

引数は WebAssembly.Instanceとなります - instance <WebAssembly.Instance> WebAssembly.Instance - JavaScript | MDN

wasi.wasiImport

wasiImportはWASIシステムコールAPIを実装したオブジェクトです。WebAssembly.Instanceのインスタンス化においては、このオブジェクトにはwasi_unstableを渡す必要があります。

WASIクラスについての説明は以上です。 まだExperimentalな機能なので、随時変更があるかと思うので、公式のドキュメントを参照ください。

nodejs.org

WASIモジュールの使用

WASIモジュールの使用と、それに必要な簡単なWASMアプリケーションのビルドを行います。

  • cargoのインストール cargoは、rustもしくはrustupのインストールすると一緒にインストールされるかと思います。 rustupは、rustupの公式ページを参照してcurlでインストールします。 rustup.rs
# rustupのインストール 
$ curl https://sh.rustup.rs -sSf | sh

このあと、バイナリのcrateを作成します。

$ cargo new --bin demo


$ tree demo
├── Cargo.lock
├── Cargo.toml
├── Makefile
├── sandbox
 │ 
├── src
   └── main.rs

ここでdemo/src/main.rsに下記のようにrustのコードを記述します。 今回は簡単にコマンドライン引数を受け取って、それを表示するだけのコードを書きます。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("Hello, {}, {}, {}, {}, {}", args[0], args[1], args[2], args[3], args[4]);
}

これでrustのコードをかけました。これをビルドするため、先ほどインストールしたrustupによって WASIに対応したRustのツールチェインをインストールする必要があります。

$ rustup target add wasm32-wasi

# ビルド
$ cargo build --target wasm32-wasi

これでtarget/wasm32-wasi/debug/ 配下に demo.wasm が作成されました。

wasmtimeでwasmを実行してみる

ためしにwasmtimeと呼ばれるWASMのランタイムで、このwasmアプリを実行してみます。

github.com curlでインストールして、ターミナルを再起動するとwasmtimeコマンドが使えるようになります。

curl https://wasmtime.dev/install.sh -sSf | bash

先ほどビルドされた.wasmファイルを実行してみます。実行時に引数も渡してみます。

$ wasmtime demo/target/wasm32-wasi/debug/demo.wasm a b c d
Hello, demo.wasm, a, b, c

渡した引数の内容を表示することができました。

Node.jsでwasiを使ってwasmアプリを動かす

次に、Node.jsのwasiモジュールを使って先ほどビルドしたdemo.wasmを実行させてみます。

'use strict';
const fs = require('fs');
const { WASI } = require('wasi');

// wasi.startの実行時にここで指定したargs env preopensの情報がwasmアプリに渡される。
const wasi = new WASI({
  args: process.argv,
  env: process.env,
  preopens: {
  }
});
const importObject = { wasi_unstable: wasi.wasiImport };

(async () => {
  try {
    // 先ほどビルドした.wasmファイルのパスを指定する。
    const wasm = await WebAssembly.compile(fs.readFileSync('./demo/target/wasm32-wasi/debug/demo.wasm'));
    const instance = await WebAssembly.instantiate(wasm, importObject);
    wasi.start(instance);
  } catch (e) {
    console.error(e)
  }
})();

WebAssembly.compileの引数に、先ほどビルドしたwasmファイルを読み込んだファイルディスクリプタを足します。 そのあとWebAssembly.instantiateでWASIインスタンスを作成し、wasi.start(instance)で該当のインスタンスの処理を実行します。

node --experimental-wasi-unstable-preview0 --experimental-wasm-bigint wasi.js a b
(node:24220) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
Hello, /Users/myuser/.nodebrew/node/v13.3.0/bin/node, /Users/myuser/myrepo/wasm-wasi-sample/wasi.js, a, b

rustのwasmアプリに渡されるコマンド引数の1つ目はnode,2つ目は.jsのファイルで、その後独自の引数を渡すことになります。

これでnode.jsからwasmアプリをwasiモジュールによって実行できました。

今回は以上となります。 wasmファイルを読み込んで、wasi.startするだけで実行可能となりました。WASIインスタンス作成時のpreopensオプションなど、今回は紹介しきれませんでしたが、今後取り上げれればと思います。

まだ出たばかりの機能なので、今後APIなど大きく変わる可能性があるので、使用する際は公式ドキュメントを参照ください。

worker_threadsを使ったNode.js マルチスレッドプログラミング

qiita.com
この記事は Qiita Node.js Advent Calendar 2018 14日目の記事となります。

今回は Node.js のWorker Threadsに関してまとめます。

本記事では、前提としてNode.js v10.14.1 における内容をまとめています。
worker_threadはexperimentalな機能なので、今後仕様が変わりうるので注意が必要です。
Worker Threads | Node.js v11.4.0 Documentation

worker_threadsとは

Node.js 10.5.0より、Node.jsでのスレッドプログラミングを可能にするworker_threadsモジュールが追加されました。
現時点Node.js10.14.1 では、 Node.jsのプログラム実行時に --experimental-workerフラグを指定することで worker_threadsモジュールを利用可能です。

worker_threadsを取り入れたPR や、contributorによるFAQを読んでみると、このモジュールが取り入れられた背景を知ることができます。 github.com

workers-faq.md · GitHub

worker_threadを使う際の注意点として、 I/Oバウンドな処理でなく、CPUバウンドな処理に用いると効率的になります。

また、Workerスレッド間では、child processやclusterモジュールと違って、ArrayBufferインスタンスの転送や、SharedArrayBufferインスタンスの共有によって、メモリ空間を効率的に共有できます。

サンプルコード

Node.js のドキュメントに記載されているサンプルコードを抜粋すると以下となります。

const {
  Worker, isMainThread, parentPort, workerData
} = require('worker_threads');

if (isMainThread) {
  module.exports = async function parseJSAsync(script) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, {
        workerData: script
      });
      worker.on('message', resolve);
      worker.on('error', reject);
      worker.on('exit', (code) => {
        if (code !== 0)
          reject(new Error(`Worker stopped with exit code ${code}`));
      });
    });
  };
} else {
  const { parse } = require('some-js-parsing-library');
  const script = workerData;
  parentPort.postMessage(parse(script));
}

mainスレッド、workerスレッドで行う処理は、 isMainThreadがtrueかどうかで判定して分岐させています。
もちろん、Workerインスタンスを生成する際の引数で別のファイルを指定することで、mainスレッド、Workerスレッドの処理を別ファイルで分けてかけます。

ここではmainスレッドでの実行時に Promise内でWorkerインスタンスを生成し、 Workerスレッド側で parentPort.postMessageによってmainスレッドにメッセージを送信しています。

続いて、worker_threadsを理解する上で重要なWorker, MessagePortクラスについて説明します。

worker_thread モジュールについて詳しく

ここでは worker_threadを使う際の各機能の解説をします。
詳細を知りたい方は公式ドキュメントを参照ください。
Worker Threads | Node.js v11.4.0 Documentation

worker.isMainThread

実行中のプログラムがmainスレッドで動作している場合はtrue, workerスレッド内で実行されている場合はfalseとなります。
前述したように、main, workerで処理を分けたい場合はこれで判定します。

worker.parentPort

parentPortはMessagePortクラスのインスタンスで、mainスレッドとコミュニケーションを可能にします。 parentPort.postMessage() を使用して送信されたメッセージは mainスレッド環境でworker.on('message')イベントのハンドラ内で使うことができます。

MessagePortクラスについては後ほど説明します。

worker.threadId

現在のスレッドを識別するための整数idです。
mainスレッドでは0 workerスレッドでは生成した順に1から増えていきます。

https://github.com/nodejs/node/blob/v10.14.1/src/node_worker.cc#L35

uint64_t next_thread_id = 1;

https://github.com/nodejs/node/blob/v10.14.1/src/node_worker.cc#L70-L74

  // Generate a new thread id.
  {
    Mutex::ScopedLock next_thread_id_lock(next_thread_id_mutex);
    thread_id_ = next_thread_id++;
  }

ちなみに、worker.isMainThreadは、 このthreadIdが0かどうかで判定しています。
https://github.com/nodejs/node/blob/v10.14.1/lib/internal/worker.js#L31

const isMainThread = threadId === 0;

worker.workerData

Workerインスタンスの生成時にコンストラクタに渡されたデータを、workerスレッドでの実行時に使うことができます。

Workerインスタンス生成時にここでセットされ https://github.com/nodejs/node/blob/v10.14.1/lib/internal/worker.js#L311

workerスレッドでの実行時に workerDataとして使える様になっています。
node/worker.js at v10.14.1 · nodejs/node · GitHub

ここで、上記のプロパティをつかって簡単なプログラムを書いてみます。

const {Worker, isMainThread, workerData, parentPort, threadId} = require('worker_threads');

if (isMainThread) {
  // mainスレッド
  console.log(`Main Thread!!! isMainThread: ${isMainThread}, threadId: ${threadId}`);

  // 4つ作成する
  for (let i = 0; i < 4; i++) {
    const worker = new Worker(__filename, { workerData: `hello: ${i}` });
    worker.on('message', (message) => {
      console.log(`[main] message got from worker : ${message}`);
    });

    worker.postMessage("from tokyo")

  }
} else {
  // workerスレッド
  console.log(`[worker] workerData: ${workerData} isMainThread: ${isMainThread}`)
  parentPort.postMessage(`ThreadId: ${threadId}`)
}

実行結果は以下のとおりです。

$ node --experimental-worker app.js
Main Thread!!! isMainThread: true, threadId: 0
[worker] workerData: hello: 0 isMainThread: false
[main] message got from worker : ThreadId: 1
[worker] workerData: hello: 1 isMainThread: false
[main] message got from worker : ThreadId: 2
[worker] workerData: hello: 2 isMainThread: false
[main] message got from worker : ThreadId: 3
[worker] workerData: hello: 3 isMainThread: false
[main] message got from worker : ThreadId: 4

Workerクラス

Worker クラスは 独立したJavascript実行スレッドを表現するものです。 worker_threadモジュールにおいておそらく一番重要なので、詳しくまとめます。

多くのNode.jsAPiはこの実行スレッド内で利用可能です

Worker 環境内での注目すべき違いは以下のとおりです。

- process.stdin, process.stdout, process.stderr はmainスレッドにリダイレクトされる  
- require('worker_threads').isMainThread プロパティはfalse  
- require('worker_threads').parentPort メッセージポートは利用可能  
- process.exit() はプログラム全体を停止せず、ただ単一のスレッドのみ停止します  
- process.abort()は利用不可
- process.chdire()やファイルグループやユーザを設定するprocess系のメソッドは利用不可  
- process.env は環境変数に対して読み込み専用  
- process.title は変更不能  
- シグナルは process.on()経由では転送されない  
- worker.terminate()が読み出された時点で 実行が停止する  
- 親プロセスからのIPC channelはアクセス不能
- trace_eventsモジュールはサポートされない

現時点で 下記の項目関しては対応待ちとなっているようです。

- inspector モジュールの使用
- Nativeアドオンの使用

なお、Workerスレッド内で workerインスタンスを作成することは可能です。

Workerクラスの内部実装

WorkerクラスについてのNode.jsの実装を見ていきます。
Workerクラスは、EventEmitterを継承しています。
https://github.com/nodejs/node/blob/v10.14.1/lib/internal/worker.js#L252

スレッド間でのメッセージ受信や、スレッド終了のタイミングでイベントを発火します。
Workerクラスのコンストラクタでインスタンス生成処理の最後でスレッドを開始します。 https://github.com/nodejs/node/blob/v10.14.1/lib/internal/worker.js#L316

new Workerによってインスタンスが生成されるタイミングで、Workerスレッドが始動します。
node/src/node_worker.ccの方で、livuvのuv_thread_createをつかってスレッド開始しているのがわかります。

https://github.com/nodejs/node/blob/v10.14.1/src/node_worker.cc#L417-L419

void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
  Worker* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Mutex::ScopedLock lock(w->mutex_);

  w->env()->add_sub_worker_context(w);
  w->stopped_ = false;
  w->thread_joined_ = false;

  w->thread_exit_async_.reset(new uv_async_t);
  w->thread_exit_async_->data = w;
  CHECK_EQ(uv_async_init(w->env()->event_loop(),
                         w->thread_exit_async_.get(),
                         [](uv_async_t* handle) {
    static_cast<Worker*>(handle->data)->OnThreadStopped();
  }), 0);

  CHECK_EQ(uv_thread_create(&w->tid_, [](void* arg) {
    static_cast<Worker*>(arg)->Run();
  }, static_cast<void*>(w)), 0);
}

Node.jsでのプログラム実行時に、Workerコンテキストの場合は 下記の場所でWorkerスレッドに関するセットアップが行われます。

https://github.com/nodejs/node/blob/v10.14.1/lib/internal/bootstrap/node.js#L220-L223
https://github.com/nodejs/node/blob/v10.14.1/lib/internal/worker.js#L438-L511

さらにworker_threadの挙動について知るためには、 このlivuvのuv_thread_createについて調べる必要がありますが、今回は説明しません。

MessageChannelクラス

MessageChannelクラスのインスタンスは 非同期の2-wayコミュニケーションチャネルを提供します。 javascriptのMessageChannelクラスを踏襲しているようです。

developer.mozilla.org

MessageChannelのクラス自体はメッセージを持っておらず、 new MessageChannel()生成したMessageChannelインスタンスは、port1 port2プロパティを持っています。
それぞれがMessagePortクラスのインスタンスであり、双方向にやりとりできます

const { MessageChannel } = require('worker_threads');

const { port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log('received', message));
port2.postMessage({ foo: 'bar' });

MessagePortクラス

MessagePortクラスのインスタンスは非同期の2wayコミュニケーションチャンネルの片方のチャンネルを表現するものとなります。

Workerクラスと同様にEventEmitterを継承しています。
https://github.com/nodejs/node/blob/v10.14.1/lib/internal/worker.js#L69-L71

// Set up the new inheritance chain.
Object.setPrototypeOf(MessagePort, EventEmitter);
Object.setPrototypeOf(MessagePort.prototype, EventEmitter.prototype);

前述したコードでも書いているように、MessagePort#postMessage()によって、スレッド間でデータをやり取りできます。
SharedArrayBufferを使えば、main worker間でcloneせずにメモリを共有してやり取りできます。

worker_threadで大事なものに関しては以上のとおりです。 実際にプログラムを書きつつ調べると理解が深まるかと思います。

Workerスレッドでcpuバウンドな処理をさせてみる。

一通り説明できましたので、ここでmainスレッドと\workerスレッドでプログラムを分けた上で、5つのWorkerスレッドで重いループ処理を行うプログラムを書きます。

main workerで行う処理をざっくり説明すると以下のとおりです。
- mainスレッド: 5 つのWorkerスレッドを起動し、それぞれのイベントハンドラを設定する
- workerスレッド: 2重ループの重い処理を行う 終了後mainスレッドにメッセージを投げる

まず、mainスレッド用の実装を main_thread.jsに記述します。
main_thread.js

const {isMainThread, Worker} = require('worker_threads');

function createWorker(path, wd) {
  const w = new Worker(path, {workerData: wd});

  w.on('error', (err) => {
    console.error(`Worker ${w.workerData} error`)
    console.error(err);
  });

  w.on('exit', (exitCode) => {
    console.log(`exitted! : ${wd}`);
  });

  w.on('message', (msg) => {
    // workerスレッドから送信されたメッセージ
    console.log(`[Main] Message got from worker ${msg}`)
  });
  return w;
}

console.log(`[Main] isMainThread: ${isMainThread}`);
for (let i = 0; i < 5; i++) {
  const worker = createWorker('./heavy_thread.js', i);
}

ここでは worker_threadsモジュールをrequireしてisMainThreadとWorkerを読み込んでます。

今回は Workerスレッドの生成処理を createWorker関数にまとめていて、内部でnew Workerしてインスタンスを生成します。
Workerスレッドで行う処理は heavy_thread.js というファイルにまとめていて、new Workerの第1引数にパスを指定します。

heavy_thread.jsの実装は下記のとおりです。
heavy_thread.js

const {parentPort, workerData, isMainThread, threadId} = require('worker_threads');


console.log(`[Worker] isMainThread: ${isMainThread}, workerData: ${workerData}, threadId: ${threadId}`);
let count = 0;
for (let i = 0; i < 10000; i++) {
  for (let j = 0; j < 10000; j++) {
    count++;
    count /= 3
    count = count * count * count
  }
}

parentPort.postMessage(`[Worker] Finished ${workerData}`)

これを実行すると以下のようになります
前述したように、実行時に--experimental-workerフラグを付ける必要があります

$ node --experimental-worker parent_thread.js
[Main] isMainThread: true
[Worker] isMainThread: false, workerData: 0, threadId: 1
[Worker] isMainThread: false, workerData: 1, threadId: 2
[Worker] isMainThread: false, workerData: 2, threadId: 3
[Worker] isMainThread: false, workerData: 3, threadId: 4
[Worker] isMainThread: false, workerData: 4, threadId: 5
[Main] Message got from worker [Worker] Finished 0
[Main] Message got from worker [Worker] Finished 1
exitted! : 0
exitted! : 1
[Main] Message got from worker [Worker] Finished 2
exitted! : 2
[Main] Message got from worker [Worker] Finished 3
exitted! : 3
[Main] Message got from worker [Worker] Finished 4
exitted! : 4

これでmain・workerスレッド間でメッセージをやりとりできました。

worker_threadを使わない場合との速度比較

次に、先程書いたworkerスレッドを使った処理と、 単一ファイルでworkerスレッドを使わずシングルスレッドで処理を行った際の 速度について調べます。
厳密に同じ処理ではないので正しく計測はできていないですが。 シングルスレッドでの実行プログラムは以下のように書きました。
10000 * 10000のループ処理を5回行うプログラムです。

single.js

console.log('[Single] start')
// cpuバウンドの重い処理
let count = 0;
for (let k = 0; k < 5; k++) {
  for (let i = 0; i < 10000; i++) {
    for (let j = 0; j < 10000; j++) {
      count++;
      count /= 3
      count = count * count * count
    }
  }
}
console.log('[Single] finish')

上述のsingle.jsと、 --experimental-workerフラグを付けて実行したmain_thread.jsの実行時間を比較します。

シングルスレッド・workerを使ったマルチスレッドでともに10回ずつ実行した際の平均実行時間は以下のとおりです 実行時にtimeコマンドを使って算出したものです。

real(s) user(s) sys(s)
シングルスレッド(single.js) 5.4545 5.4207 0.0289
マルチスレッド 1.294 5.8129 0.1398

結果を見てみると、workerを使ったマルチスレッド環境では 完了時間が 約1/4となり、かなり実行時間を短縮できたことがわかりました。

補足ですが、worker_threadを使った実行において、 real < user となっていて気になったので調べたのですが、 この場合、マルチコア・マルチスレッドでの並列実行の恩恵をうまく受けていること示唆する結果とのことでした。

参考 unix.stackexchange.com

The rule of thumb is:
real < user: The process is CPU bound and takes advantage of parallel execution on multiple cores/CPUs.
real ≈ user: The process is CPU bound and takes no advantage of parallel exeuction.
real > user: The process is I/O bound. Execution on multiple cores would be of little to no advantage.

さいごに

worker_threadモジュールによって、Node.jsでのマルチスレッドプログラミングが可能になり、cpuバウンドな処理をさせたいときには活用したいと思いました。

多少仕組みを学ぶのに労力がかかったのですが、worker_threadのラッパーであるmicrojobというモジュールも人気なので、こちらを使うと楽に書けるのではと思います。 github.com

TypeScript: A difference of type-checking behavior for passing Object directly and indirectly.

TypeScript interface

I'm a newbie of TypeScript. I was confused by an error for the type-checking of interface.

This is just a memorandum for understanding the mechanism of TypeScript

I created the SquareConfig interface same as the TypeScript official document. Interfaces · TypeScript

In SquareConfig interface. color and width are both optional. and according to the document, if passing an Object having more properties than the compiler expected, it doesn't emit an error.

Notice that our object actually has more properties than this, but the compiler only checks that at least the ones required are present and match the types required. There are some cases where TypeScript isn’t as lenient, which we’ll cover in a bit.

But I change the code a little, it emits an error

Good

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
  return {
    color: 'tset',
    area: 1
  }
}
const test = {color: 'test', width: 100, test: 'test'}
createSquare(test)

If I pass the Object argument to createSquare function indirectly, it's completely through the build process. In this case, the "test" parameter is But, see below, if I pass the Object directly, it occurs an error.

Bad

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
  return {
    color: 'tset',
    area: 1
  }
}
// const test = {color: 'test', width: 100, test: 'test'}
// passing the 
createSquare({color: 'test', width: 100, test: 'test'})

when build it, TypeScript emits an error.

[01:13:06] Using gulpfile ~/Documents/code/nodejs-sandbox/ts-app/gulpfile.js
[01:13:06] Starting 'default'...
src/subfolder/squareConfig.ts(13,42): error TS2345: Argument of type '{ color: string; width: number; test: string; }' is not assignable to parameter of type 'SquareConfig'.
  Object literal may only specify known properties, and 'test' does not exist in type 'SquareConfig'.
TypeScript: 1 semantic error

Why does this error occur?

How to resolve vue-loader error 'vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.'

Preconditions

dependencies for npm

  "dependencies": {
    "@webpack-cli/init": "^0.1.2",
    "vue": "^2.5.17",
    "webpack": "^4.20.2"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/preset-env": "^7.1.0",
    "babel-loader": "^8.0.4",
    "css-loader": "^1.0.0",
    "style-loader": "^0.23.0",
    "vue-loader": "^15.4.2",
    "vue-template-compiler": "^2.5.17",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.9"
  },

about webpack config

When I make the Vue.js web app with using webpack, I wanted to compile Vue's SFC(simple file component) file,
I used vue-loader V15. I made webpack.config.js file for setting it as below.

const path = require('path');
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.vue'],
    alias: {
      vue$: 'vue/dist/vue.esm.js',
    }
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'public')
  }
}

In this case, if the file suffix name is ".vue", 'vue-loader' is going to be used.

After setting it, I run the web app by using webpack-dev-server. At that time, it occured an error same as below.

ERROR in ./src/components/app.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
 @ ./src/index.js 2:0-35 6:9-12
 @ multi (webpack)-dev-server/client?http://localhost:8080 (webpack)/hot/dev-server.js ./src/index.js

ERROR in ./src/index.js
Module not found: Error: Can't resolve 'vue' in '/Users/test/Documents/code/webpack-test/src'
 @ ./src/index.js 1:0-22 3:4-7
 @ multi (webpack)-dev-server/client?http://localhost:8080 (webpack)/hot/dev-server.js ./src/index.js

ERROR in ./src/components/app.vue
Module not found: Error: Can't resolve 'vue' in '/Users/test/Documents/code/webpack-test/src/components'
 @ ./src/components/app.vue 21:14-28
 @ ./src/index.js
 @ multi (webpack)-dev-server/client?http://localhost:8080 (webpack)/hot/dev-server.js ./src/index.js

In this case, to use vue-loader v15, we should set webpack plugin called "VueLoaderPlugin"

vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

In my webpack.config.js file, the plugin was not set. So I added plugins param in this file

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.vue'],
    alias: {
      vue$: 'vue/dist/vue.esm.js',
    }
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'public')
  },
  plugins: [
     new VueLoaderPlugin()
  ]
}

After setting it, I resolved the error.

VueLoaderPlugin()

From vue-loader V15, there were so many changes in this plugin. The VueLoaderPlugin plugin is required from V15. If wanted to know the details, see below. It's the official document for migration from v14 Migrating from v14 | Vue Loader

References

github.com

top-men.hatenablog.com

Node.js: UnhandledPromiseRejectionWarning

In node.js script, without attaching any error handlers to the Promise within a event loop, "unhandledRejection" is emitted.

The error log is as follows.

(node:57974) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:57974) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:57974) UnhandledPromiseRejectionWarning: Error: Request failed with status code 429
    at createError (/Users/test/Documents/my-repo/test-module/node_modules/axios/lib/core/createError.js:16:15)
    at settle (/Users/test/Documents/my-repo/test-module/node_modules/axios/lib/core/settle.js:18:12)
    at IncomingMessage.handleStreamEnd (/Users/test/Documents/my-repo/test-module/node_modules/axios/lib/adapters/http.js:201:11)
    at emitNone (events.js:111:20)
    at IncomingMessage.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)

In this case, we can't know the actual reason of this error.

To print the actual reason, we just only set handler for the "unhandledRejection".

process.on('unhandledRejection', (reason, p) => {
  console.log('Unhandled Rejection at:', p, 'reason:', reason);
  // application specific logging, throwing an error, or other logic here
});

Process | Node.js v14.5.0 Documentation

golang: failed to get golang.org/x/tools/cmd/oracle

preconditions

golang: 1.6

failed to get golang.org/x/tools/cmd/oracle

When I try to "go get golang.org/x/tools/cmd/oracle" in my golang project, it occures an error as wrote down below.

go get golang.org/x/tools/cmd/oracle
package golang.org/x/tools/cmd/oracle: cannot find package "golang.org/x/tools/cmd/oracle" in any of:
    /usr/local/Cellar/go/1.8/libexec/src/golang.org/x/tools/cmd/oracle (from $GOROOT)
    /Users/me/go_vendor/src/golang.org/x/tools/cmd/oracle (from $GOPATH)

The reason why this error occured

At the moment, this tool has been replaced to tools/cmd/guru godoc.org

So, you just get tools/cmd/guru, this problem can be solved.

go get golang.org/x/tools/cmd/oracle

go get golang.org/x/tools/cmd/oracle failed in v1.6.3 · Issue #18262 · golang/go · GitHub