Create a gist now

Instantly share code, notes, and snippets.

@takuseno /report.md Secret
Last active Oct 26, 2017

What would you like to do?
[osakana] 対戦数が人によって異なる不具合の修正

この度はシステムの不具合により、ランキングが正しく表示されていなかった不具合を修正したことを報告いたします。

現象

現象としては、全対戦は正しく行われていたが、結果のデータベースへの書き込みが一部失敗していたのでランキングへ反映されていないという不具合でした。

原因

実は今皆さんに遊んでいただいているOsakana Heavenは今年リニューアルしたものです。

以前はRubyで動いていましたが、リニューアルしたOsakana Heavenのバックエンドは

  • Node.js
  • PostgreSQL

で動いており、今回の不具合は主に以下の要因からなっています。

  • Node.jsはプログラムをシングルスレッドで実行する
  • データベースへの書き込みを非同期に行っていた

Node.jsはプログラムをシングルスレッドで実行する

Node.jsはJavaScriptをブラウザではなく、Chromeに使われているJavaScript向けのVMを使うことで直接実行するものです。 これにより、JavaScriptでフロントとバックエンドを実装することが可能です。

このNode.jsを使ってサーバー側で処理を行なっているのですが、最大の特徴は単一スレッドで実行される点です。以下のコードを見てください。

// emit 'world' after 1000 ms
setTimeout(() => console.log('world'), 1000)
// emit 'hello' immediately
console.log('hello')

このコードの実行結果は

helloworld

となります。

一見するとconsole.log('world')が非同期(上から順番ではなく)に呼び出されたように見えます。

しかしながら、実際にはスレッドは1つしか作っておらず、呼び出される関数の順番をキューで管理することにより、並行処理を行なっています。 上の例の場合は以下のような感じで関数がキューに入って順番に実行されます。

  1. setTimeout
  2. console.log('hello')
  3. console.log('world')

このように実行する順番を管理することで、あたかもマルチスレッドで動いているかのように振る舞うことができます。

データベースへの接続を非同期に行っていた

JavaScriptは上の性質を使うことで簡単に非同期にプログラムを実行することができます。 試合を行うときも各試合は非同期に実行されて、対戦結果がデータベースへと書き込まれるように実装していました。

本来の想定では、非同期に対戦が行われて、対戦が終了したものからデータベースへと書き込まれる予定でした。

しかし、64名2048試合を非同期で行うと以下のようなことが起きました。

  1. 試合Aの対戦結果を書き込む関数が実行されて、データベースからの応答を待つ関数がキューに追加される
  2. その間にも試合B、C、D....と次々にキューに関数が追加されていく
  3. データベースからの応答を待つ関数がかなり後回しとなり、それが実行されるときにはタイムアウトとなってしまった
  4. タイムアウトにならなかった試合のみデータベースへの書き込みが行われた
  5. 結果的に、人によって試合数が異なる結果となった

解決方法

非同期に試合を実行するのをやめて、Node.jsのv7.6.0から導入されたasync/awaitを使うことで同期的に実行されるようにしました。

終わりに

この度の不具合、申し訳ありませんでした。

今後も皆さんに楽しく遊んでいただけるようにいたしますので、どうぞよろしくお願いします。

今井研一同

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment